diff --git a/.editorconfig b/.editorconfig index b725c5cce3..0e4883082c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,20 +1,373 @@ -# top-most EditorConfig file +############################################################################### +# EditorConfig is awesome: http://EditorConfig.org +############################################################################### + +############################################################################### +# Top-most EditorConfig file +############################################################################### root = true -[*.cs] +############################################################################### +# Set default behavior to: +# a UTF-8 encoding, +# Unix-style line endings, +# a newline ending the file, +# 4 space indentation, and +# trimming of trailing whitespace +############################################################################### +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true indent_style = space indent_size = 4 -csharp_style_var_for_built_in_types = false:warning -csharp_style_var_elsewhere = false:warning -csharp_style_var_when_type_is_apparent = true:warning +trim_trailing_whitespace = true + +############################################################################### +# Set file behavior to: +# 2 space indentation +############################################################################### +[*.{cmd,config,csproj,json,props,ps1,resx,sh,targets}] +indent_size = 2 + +############################################################################### +# Set file behavior to: +# Windows-style line endings, and +# tabular indentation +############################################################################### +[*.sln] end_of_line = crlf +indent_style = tab + +############################################################################### +# Set dotnet naming rules to: +# suggest async members be pascal case suffixed with Async +# suggest const declarations be pascal case +# suggest interfaces be pascal case prefixed with I +# suggest parameters be camel case +# suggest private and internal static fields be camel case +# suggest private and internal fields be camel case +# suggest public and protected declarations be pascal case +# suggest static readonly declarations be pascal case +# suggest type parameters be prefixed with T +############################################################################### +[*.cs] +dotnet_naming_rule.async_members_should_be_pascal_case_suffixed_with_async.severity = suggestion +dotnet_naming_rule.async_members_should_be_pascal_case_suffixed_with_async.style = pascal_case_suffixed_with_async +dotnet_naming_rule.async_members_should_be_pascal_case_suffixed_with_async.symbols = async_members + +dotnet_naming_rule.const_declarations_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.const_declarations_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.const_declarations_should_be_pascal_case.symbols = const_declarations + +dotnet_naming_rule.interfaces_should_be_pascal_case_prefixed_with_i.severity = suggestion +dotnet_naming_rule.interfaces_should_be_pascal_case_prefixed_with_i.style = pascal_case_prefixed_with_i +dotnet_naming_rule.interfaces_should_be_pascal_case_prefixed_with_i.symbols = interfaces + +dotnet_naming_rule.parameters_should_be_camel_case.severity = suggestion +dotnet_naming_rule.parameters_should_be_camel_case.style = camel_case +dotnet_naming_rule.parameters_should_be_camel_case.symbols = parameters + +dotnet_naming_rule.private_and_internal_static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.private_and_internal_static_fields_should_be_camel_case.style = camel_case +dotnet_naming_rule.private_and_internal_static_fields_should_be_camel_case.symbols = private_and_internal_static_fields + +dotnet_naming_rule.private_and_internal_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.private_and_internal_fields_should_be_camel_case.style = camel_case +dotnet_naming_rule.private_and_internal_fields_should_be_camel_case.symbols = private_and_internal_fields + +dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.symbols = public_and_protected_declarations +dotnet_naming_symbols.public_and_protected_declarations.applicable_kinds = method, field, event, property + +dotnet_naming_rule.static_readonly_declarations_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.static_readonly_declarations_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.static_readonly_declarations_should_be_pascal_case.symbols = static_readonly_declarations + +dotnet_naming_rule.type_parameters_should_be_pascal_case_prefixed_with_t.severity = suggestion +dotnet_naming_rule.type_parameters_should_be_pascal_case_prefixed_with_t.style = pascal_case_prefixed_with_t +dotnet_naming_rule.type_parameters_should_be_pascal_case_prefixed_with_t.symbols = type_parameters + +############################################################################### +# Set dotnet naming styles to define: +# camel case +# pascal case +# pascal case suffixed with Async +# pascal case prefixed with I +# pascal case prefixed with T +############################################################################### +[*.cs] +dotnet_naming_style.camel_case.capitalization = camel_case + +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case_suffixed_with_async.capitalization = pascal_case +dotnet_naming_style.pascal_case_suffixed_with_async.required_suffix = Async + +dotnet_naming_style.pascal_case_prefixed_with_i.capitalization = pascal_case +dotnet_naming_style.pascal_case_prefixed_with_i.required_prefix = I + +dotnet_naming_style.pascal_case_prefixed_with_t.capitalization = pascal_case +dotnet_naming_style.pascal_case_prefixed_with_t.required_prefix = T + +############################################################################### +# Set dotnet naming symbols to: +# async members +# const declarations +# interfaces +# private and internal fields +# private and internal static fields +# public and protected declarations +# static readonly declarations +# type parameters +############################################################################### +[*.cs] +dotnet_naming_symbols.async_members.required_modifiers = async + +dotnet_naming_symbols.const_declarations.required_modifiers = const + +dotnet_naming_symbols.interfaces.applicable_kinds = interface + +dotnet_naming_symbols.parameters.applicable_kinds = parameter + +dotnet_naming_symbols.private_and_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_symbols.private_and_internal_fields.applicable_kinds = field + +dotnet_naming_symbols.private_and_internal_static_fields.applicable_accessibilities = private, internal +dotnet_naming_symbols.private_and_internal_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_and_internal_static_fields.required_modifiers = static + +dotnet_naming_symbols.public_and_protected_declarations.applicable_accessibilities = public, protected + +dotnet_naming_symbols.static_readonly_declarations.required_modifiers = static, readonly + +dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter + +############################################################################### +# Set dotnet sort options to: +# do not separate import directives into groups, and +# sort system directives first +############################################################################### +[*.cs] +dotnet_separate_import_directive_groups = false dotnet_sort_system_directives_first = true + +############################################################################### +# Set dotnet style options to: +# suggest null-coalescing expressions, +# suggest collection-initializers, +# suggest explicit tuple names, +# suggest null-propogation +# suggest object-initializers, +# generate parentheses in arithmetic binary operators for clarity, +# generate parentheses in other binary operators for clarity, +# don't generate parentheses in other operators if unnecessary, +# generate parentheses in relational binary operators for clarity, +# warn when not using predefined-types for locals, parameters, and members, +# generate predefined-types of type names for member access, +# generate auto properties, +# suggest compound assignment, +# generate conditional expression over assignment, +# generate conditional expression over return, +# suggest inferred anonymous types, +# suggest inferred tuple names, +# suggest 'is null' checks over '== null', +# don't generate 'this.' and 'Me.' for events, +# warn when not using 'this.' and 'Me.' for fields, +# warn when not using 'this.' and 'Me.' for methods, +# warn when not using 'this.' and 'Me.' for properties, +# suggest readonly fields, and +# generate accessibility modifiers for non interface members +############################################################################### +[*.cs] +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion + +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + dotnet_style_predefined_type_for_locals_parameters_members = true:warning -dotnet_style_predefined_type_for_member_access = true:warning +dotnet_style_predefined_type_for_member_access = true:silent + +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion + +dotnet_style_qualification_for_event = false:silent dotnet_style_qualification_for_field = true:warning dotnet_style_qualification_for_method = true:warning dotnet_style_qualification_for_property = true:warning -[*.tt] -indent_style = space -indent_size = 4 \ No newline at end of file +dotnet_style_readonly_field = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +############################################################################### +# Set dotnet style options to: +# suggest removing all unused parameters +############################################################################### +[*.cs] +dotnet_code_quality_unused_parameters = all:suggestion + +############################################################################### +# Set csharp indent options to: +# indent block contents, +# not indent braces, +# indent case contents, +# not indent case contents when block, +# indent labels one less than the current, and +# indent switch labels +############################################################################### +[*.cs] +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +############################################################################### +# Set csharp new-line options to: +# insert a new-line before "catch", +# insert a new-line before "else", +# insert a new-line before "finally", +# insert a new-line before members in anonymous-types, +# insert a new-line before members in object-initializers, and +# insert a new-line before all open braces +############################################################################### +[*.cs] +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true + +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true + +csharp_new_line_before_open_brace = all + +############################################################################### +# Set csharp preserve options to: +# preserve single-line blocks, and +# preserve single-line statements +############################################################################### +[*.cs] +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +############################################################################### +# Set csharp space options to: +# remove any space after a cast, +# add a space after the colon in an inheritance clause, +# add a space after a comma, +# remove any space after a dot, +# add a space after keywords in control flow statements, +# add a space after a semicolon in a "for" statement, +# add a space before and after binary operators, +# remove space around declaration statements, +# add a space before the colon in an inheritance clause, +# remove any space before a comma, +# remove any space before a dot, +# remove any space before an open square-bracket, +# remove any space before a semicolon in a "for" statement, +# remove any space between empty square-brackets, +# remove any space between a method call's empty parameter list parenthesis, +# remove any space between a method call's name and its opening parenthesis, +# remove any space between a method call's parameter list parenthesis, +# remove any space between a method declaration's empty parameter list parenthesis, +# remove any space between a method declaration's name and its openening parenthesis, +# remove any space between a method declaration's parameter list parenthesis, +# remove any space between parentheses, and +# remove any space between square brackets +############################################################################### +[*.cs] +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true + +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore + +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false + +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_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_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +############################################################################### +# Set csharp style options to: +# generate braces, +# suggest simple default expressions, +# generate a preferred modifier order, +# suggest conditional delegate calls, +# suggest deconstructed variable declarations, +# generate expression-bodied accessors, +# generate expression-bodied constructors, +# generate expression-bodied indexers, +# generate expression-bodied lambdas, +# generate expression-bodied methods, +# generate expression-bodied operators, +# generate expression-bodied properties, +# suggest inlined variable declarations, +# suggest local over anonymous functions, +# suggest pattern-matching over "as" with "null" check, +# suggest pattern-matching over "is" with "cast" check, +# suggest throw expressions, +# generate a discard variable for unused value expression statements, +# suggest a discard variable for unused assignments, +# warn when using var for built-in types, +# warn when using var when the type is not apparent, and +# warn when not using var when the type is apparent +# warn when using simplified "using" declaration +############################################################################### +[*.cs] +csharp_prefer_braces = true:silent +csharp_prefer_simple_default_expression = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent + +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion + +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent + +csharp_style_inlined_variable_declaration = true:suggestion + +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion + +csharp_style_throw_expression = true:suggestion + +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:suggestion + +csharp_style_var_for_built_in_types = never +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = false:warning diff --git a/.gitattributes b/.gitattributes index 2a7d04dc7b..c0bff6e189 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,50 +1,100 @@ -*.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 +############################################################################### +# Set default behavior to: +# treat as text and +# normalize to Unix-style line endings +* text eol=lf -*.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 +# Set explicit file behavior to: +*.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 +# treat as text +# normalize to Unix-style line endings and +# diff as csharp +*.cs text eol=lf diff=csharp +# use a union merge when resoling 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 +# normalize to Windows-style line endings and +*.sln text eol=crlf merge=union +# treat as binary +*.basis binary +*.bmp binary +*.dds binary +*.dll binary +*.eot binary +*.exe binary +*.gif binary +*.jpg binary +*.ktx binary +*.otf binary +*.pbm binary +*.pdf binary +*.png binary +*.ppt binary +*.pptx binary +*.pvr binary +*.snk binary +*.tga binary +*.ttc binary +*.ttf binary +*.webp binary +*.woff binary +*.woff2 binary +*.xls binary +*.xlsx binary +# 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 *.jpg filter=lfs diff=lfs merge=lfs -text *.jpeg filter=lfs diff=lfs merge=lfs -text *.bmp filter=lfs diff=lfs merge=lfs -text diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 01c09d2231..89d1a75f27 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# How to contribute to ImageSharp +# How to contribute to SixLabors.ImageSharp #### **Did you find a bug?** @@ -12,14 +12,20 @@ * 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 practise as demonstrated in the repository. These follow strict Stylecop rules :cop:. +* 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:. #### **Do you intend to add a new feature or change an existing one?** -* Suggest your change in the [ImageSharp Gitter Chat Room](https://gitter.im/ImageSharp/General) and start writing code. +* Suggest your change in the [Ideas Discussions Channel](https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas) 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/master/ReferenceOutput). To succesfully run tests, make sure that you have updated the submodules! @@ -27,10 +33,12 @@ #### **Do you have questions about consuming the library or the source code?** -* Ask any question about how to use ImageSharp in the [ImageSharp Gitter Chat Room](https://gitter.im/ImageSharp/General). +* Ask any question about how to use SixLabors.ImageSharp in the [Help Discussions Channel](https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AHelp). -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. +#### 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). -Thanks for reading! +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. -James Jackson-South :heart: +Thanks for reading! diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..ac30c6f1e2 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +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 deleted file mode 100644 index c8313fba9f..0000000000 --- a/.github/ISSUE_TEMPLATE/ask-question.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -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 deleted file mode 100644 index 58a31246a9..0000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -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/commercial-bug-report.md b/.github/ISSUE_TEMPLATE/commercial-bug-report.md new file mode 100644 index 0000000000..024de8e19e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/commercial-bug-report.md @@ -0,0 +1,33 @@ +--- +name: "Commercial License : Bug Report" +about: | + Create a report to help us improve the project. For Commercial License holders only. + Please contact help@sixlabors.com for issues requiring private support. +labels: commercial, needs triage + +--- + + +### 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 new file mode 100644 index 0000000000..cf9f787526 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a Question + url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AHelp + about: Ask a question about this project. + - name: Feature Request + url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas + 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 deleted file mode 100644 index be1e593be4..0000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -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.md b/.github/ISSUE_TEMPLATE/oss-bug-report.md new file mode 100644 index 0000000000..e0d37de538 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/oss-bug-report.md @@ -0,0 +1,30 @@ +--- +name: "OSS : Bug Report" +about: Create a report to help us improve the project. +labels: needs triage + +--- + +### 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/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000000..a83e194234 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,112 @@ +name: Build + +on: + push: + branches: + - master + tags: + - "v*" + pull_request: + branches: + - master +jobs: + Build: + strategy: + matrix: + options: + - os: ubuntu-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: false + - os: windows-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: true + - os: windows-latest + framework: netcoreapp2.1 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net472 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net472 + runtime: -x86 + codecov: false + + runs-on: ${{matrix.options.os}} + if: "!contains(github.event.head_commit.message, '[skip ci]')" + + steps: + - uses: actions/checkout@v2 + + - name: Install NuGet + uses: NuGet/setup-nuget@v1 + + - name: Setup Git + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true + git fetch --prune --unshallow + git submodule -q update --init --recursive + + - name: Setup DotNet SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "3.1.x" + + - name: Build + shell: pwsh + run: ./ci-build.ps1 "${{matrix.options.framework}}" + + - name: Test + shell: pwsh + run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" + env: + CI: True + XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit + + - name: Update Codecov + uses: codecov/codecov-action@v1.0.7 + if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') + with: + flags: unittests + + Publish: + needs: [Build] + + runs-on: windows-latest + + if: (github.event_name == 'push') + + steps: + - uses: actions/checkout@v2 + + - name: Install NuGet + uses: NuGet/setup-nuget@v1 + + - name: Setup Git + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true + git fetch --prune --unshallow + git submodule -q update --init --recursive + + - name: Setup DotNet SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "3.1.x" + + - name: Pack + shell: pwsh + run: ./ci-pack.ps1 + + - name: Publish to MyGet + shell: pwsh + run: | + nuget.exe push .\artifacts\*.nupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v2/package + nuget.exe push .\artifacts\*.snupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v3/index.json + # TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org diff --git a/.gitignore b/.gitignore index 4942818972..a89cfcf104 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,8 +204,6 @@ FakesAssemblies/ **/node_modules **/node_modules/* -**/Images/ActualOutput -**/Images/ReferenceOutput # ASP.NET 5 project.lock.json @@ -218,7 +216,8 @@ artifacts/ *.csproj.bak #CodeCoverage -**/CodeCoverage/* -docs/ -/samples/AvatarWithRoundedCorner/output -/ImageSharp.Coverage.xml +*.lcov + +# Tests +**/Images/ActualOutput +**/Images/ReferenceOutput diff --git a/.gitmodules b/.gitmodules index 37ef701cdf..55389121f2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,6 @@ 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 +[submodule "shared-infrastructure"] + path = shared-infrastructure + url = https://github.com/SixLabors/SharedInfrastructure diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2515ca82a9..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,43 +0,0 @@ -language: csharp -solution: ImageSharp.sln - -matrix: - include: - - os: linux # Ubuntu 14.04 - dist: trusty - sudo: required - dotnet: 2.1.401 - 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 deleted file mode 100644 index c772e647ce..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - // 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 deleted file mode 100644 index 82aaa2f8d0..0000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - // 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 new file mode 100644 index 0000000000..b34bbb41a3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# 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 deleted file mode 100644 index d9c0848f13..0000000000 --- a/CodeCoverage.runsettings +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - .*ImageSharp.dll - - - .*tests* - .*Tests* - - - - - - - - \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000000..0f9c5bdde2 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,140 @@ + + + + + + + + $(MSBuildThisFileDirectory)artifacts/ + $(SixLaborsProjectCategory)/$(MSBuildProjectName) + https://github.com/SixLabors/ImageSharp/ + + + + + true + $(BaseArtifactsPath)obj/$(BaseArtifactsPathSuffix)/ + portable + full + disable + true + true + + + + + + $(DefineConstants);SUPPORTS_MATHF + $(DefineConstants);SUPPORTS_HASHCODE + $(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS + $(DefineConstants);SUPPORTS_SPAN_STREAM + $(DefineConstants);SUPPORTS_ENCODING_STRING + $(DefineConstants);SUPPORTS_RUNTIME_INTRINSICS + $(DefineConstants);SUPPORTS_CODECOVERAGE + $(DefineConstants);SUPPORTS_HOTPATH + $(DefineConstants);SUPPORTS_CREATESPAN + + + $(DefineConstants);SUPPORTS_MATHF + $(DefineConstants);SUPPORTS_HASHCODE + $(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS + $(DefineConstants);SUPPORTS_SPAN_STREAM + $(DefineConstants);SUPPORTS_ENCODING_STRING + $(DefineConstants);SUPPORTS_CODECOVERAGE + $(DefineConstants);SUPPORTS_CREATESPAN + + + $(DefineConstants);SUPPORTS_MATHF + $(DefineConstants);SUPPORTS_CODECOVERAGE + $(DefineConstants);SUPPORTS_CREATESPAN + + + $(DefineConstants);SUPPORTS_MATHF + $(DefineConstants);SUPPORTS_HASHCODE + $(DefineConstants);SUPPORTS_SPAN_STREAM + $(DefineConstants);SUPPORTS_ENCODING_STRING + $(DefineConstants);SUPPORTS_CODECOVERAGE + $(DefineConstants);SUPPORTS_CREATESPAN + + + $(DefineConstants);SUPPORTS_CODECOVERAGE + + + $(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS + $(DefineConstants);SUPPORTS_CODECOVERAGE + + + + + Six Labors and contributors + $(BaseArtifactsPath)bin/$(BaseArtifactsPathSuffix)/ + Six Labors + $(BaseArtifactsPath)pkg/$(BaseArtifactsPathSuffix)/$(Configuration)/ + SixLabors.ImageSharp + 0.0.1 + $(PackageVersion) + + + + + + v + normal + + + + + Copyright © Six Labors + strict;IOperation + true + 8.0 + en + true + sixlabors.imagesharp.128.png + Apache-2.0 + $(RepositoryUrl) + true + git + + https://www.myget.org/F/sixlabors/api/v3/index.json; + https://api.nuget.org/v3/index.json; + + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json; + + true + $(MSBuildThisFileDirectory)shared-infrastructure/SixLabors.snk + 00240000048000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97 + true + + + + + + + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000000..4e7ab9e6b7 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,43 @@ + + + + + + + + $(DefineConstants);$(OS) + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageSharp.ruleset b/ImageSharp.ruleset deleted file mode 100644 index d318b75c2e..0000000000 --- a/ImageSharp.ruleset +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/ImageSharp.sln b/ImageSharp.sln index 41117c7a8d..3ebc9453fe 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,53 +1,341 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28902.138 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" - ProjectSection(SolutionItems) = preProject - 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 - CodeCoverage.runsettings = CodeCoverage.runsettings +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + .gitmodules = .gitmodules + ci-build.ps1 = ci-build.ps1 + ci-pack.ps1 = ci-pack.ps1 + ci-test.ps1 = ci-test.ps1 + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + GitVersion.yml = GitVersion.yml + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799C43E-5C54-4A8F-8D64-B1475241DB0D}" + ProjectSection(SolutionItems) = preProject .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}") = "Source", "Source", "{815C0625-CD3D-440F-9F80-2D83856AB7AE}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{FBE8C1AD-5AEC-4514-9B64-091D8E145865}" ProjectSection(SolutionItems) = preProject + .github\ISSUE_TEMPLATE\commercial-bug-report.md = .github\ISSUE_TEMPLATE\commercial-bug-report.md + .github\ISSUE_TEMPLATE\config.yml = .github\ISSUE_TEMPLATE\config.yml + .github\ISSUE_TEMPLATE\oss-bug-report.md = .github\ISSUE_TEMPLATE\oss-bug-report.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{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 EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}" +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 + EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E919DF0B-2607-4462-8FC0-5C98FE50F8C9}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{FA55F5DE-11A6-487D-ABA4-BC93A02717DD}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9E574A07-F879-4811-9C41-5CBDC6BAFDB7}" +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 - src\Shared\AssemblyInfo.Common.cs = src\Shared\AssemblyInfo.Common.cs + 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\F.bmp = tests\Images\Input\Bmp\F.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\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\pal8v4.bmp = tests\Images\Input\Bmp\pal8v4.bmp + tests\Images\Input\Bmp\pal8v5.bmp = tests\Images\Input\Bmp\pal8v5.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\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\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\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\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 EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}" +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\kumin.gif = tests\Images\Input\Gif\kumin.gif + tests\Images\Input\Gif\leo.gif = tests\Images\Input\Gif\leo.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\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}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Drawing", "src\ImageSharp.Drawing\ImageSharp.Drawing.csproj", "{2E33181E-6E28-4662-A801-E2E7DC206029}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "baseline", "baseline", "{195BA3D3-3E9F-4BC5-AB40-5F9FEB638146}" + ProjectSection(SolutionItems) = preProject + 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\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\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 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}" + 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 + 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\big-corrupted-chunk.png = tests\Images\Input\Png\big-corrupted-chunk.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\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\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\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\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\zlib-overflow.png = tests\Images\Input\Png\zlib-overflow.png + EndProjectSection 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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}" +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 + 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 Global + 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(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 @@ -69,18 +357,6 @@ Global {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 @@ -105,29 +381,44 @@ Global {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 + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.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 + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.ActiveCfg = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.Build.0 = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.ActiveCfg = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {9E574A07-F879-4811-9C41-5CBDC6BAFDB7} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} + {FBE8C1AD-5AEC-4514-9B64-091D8E145865} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} - {2E33181E-6E28-4662-A801-E2E7DC206029} = {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} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {561B880A-D9EE-44EF-90F5-817C54A9D9AB} = {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings deleted file mode 100644 index 432f4524a0..0000000000 --- a/ImageSharp.sln.DotSettings +++ /dev/null @@ -1,390 +0,0 @@ - - <?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 - \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2eeb57968e..8d5852d374 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2018 Six Labors + Copyright (c) Six Labors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NuGet.config b/NuGet.config deleted file mode 100644 index 322105d4d4..0000000000 --- a/NuGet.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index a72074f8f3..adf9647cc2 100644 --- a/README.md +++ b/README.md @@ -1,208 +1,133 @@ -

+

-SixLabors.ImageSharp +SixLabors.ImageSharp
SixLabors.ImageSharp

-
-[![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) +[![Build Status](https://img.shields.io/github/workflow/status/SixLabors/ImageSharp/Build/master)](https://github.com/SixLabors/ImageSharp/actions) +[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp) +[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![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. -Designed to democratize image processing, ImageSharp brings you an incredibly powerful yet beautifully simple API. - -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. +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. -Built against .NET Standard 1.3 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. +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. -### Documentation -For all SixLabors projects, including ImageSharp: -https://sixlabors.github.io/docs/ +Built against [.NET Standard 1.3](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios. -### Installation -Install stable releases via Nuget; development releases are available via MyGet. +## License + +- ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0) +- An alternative Commercial License can be purchased for projects and applications requiring support. +Please visit https://sixlabors.com/pricing for details. -| 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/master/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. - -### Build Status - -| |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)| - -### Questions? - -- 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! +- Do you have questions? We are happy to help! Please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/category_choices), or ask them on [Stack Overflow](https://stackoverflow.com) using the `ImageSharp` tag. Please 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! -### 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. +## 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). -On platforms supporting netstandard 1.3+ +## Installation -```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. +Install stable releases via Nuget; development releases are available via MyGet. -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/) +| 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) | -### Manual build +## Manual build If you prefer, you can compile ImageSharp yourself (please do and help!) -- Using [Visual Studio 2017](https://visualstudio.microsoft.com/vs/) +- Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) - Make sure you have the latest version installed - - Make sure you have [the .NET Core 2.1 SDK](https://www.microsoft.com/net/core#windows) installed + - Make sure you have [the .NET Core 3.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 Windows" button above or run the following git commands. +To clone ImageSharp locally, click the "Clone in [YOUR_OS]" button above or run the following git commands: ```bash git clone https://github.com/SixLabors/ImageSharp ``` -### How can you help? +If working with Windows please ensure that you have enabled log file paths in git (run as Administrator). + +```bash +git config --system core.longpaths true +``` + +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? 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. -### 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) + +## Sponsor Six Labors + +Support the efforts of the development of the Six Labors projects. [[Become a sponsor :heart:](https://opencollective.com/sixlabors#sponsor)] + +### Platinum Sponsors +Become a platinum sponsor with a monthly donation of $2000 (providing 32 hours of maintenance and development) and get 2 hours of dedicated support (remote support available through chat or screen-sharing) per month. + +In addition you get your logo (large) on our README on GitHub and the home page (large) of sixlabors.com + + + +### Gold Sponsors +Become a gold sponsor with a monthly donation of $1000 (providing 16 hours of maintenance and development) and get 1 hour of dedicated support (remote support available through chat or screen-sharing) per month. + +In addition you get your logo (large) on our README on GitHub and the home page (medium) of sixlabors.com + + + +### Silver Sponsors +Become a silver sponsor with a monthly donation of $500 (providing 8 hours of maintenance and development) and get your logo (medium) on our README on GitHub and the product pages of sixlabors.com + + + +### Bronze Sponsors +Become a bronze sponsor with a monthly donation of $100 and get your logo (small) on our README on GitHub. -### 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)] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT new file mode 100644 index 0000000000..eaf9f73f07 --- /dev/null +++ b/THIRD-PARTY-NOTICES.TXT @@ -0,0 +1,32 @@ +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 deleted file mode 100644 index 2cc5182d39..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,69 +0,0 @@ -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 deleted file mode 100644 index 6372b41253..0000000000 --- a/build.cmd +++ /dev/null @@ -1,17 +0,0 @@ -@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 deleted file mode 100644 index 215b551170..0000000000 --- a/build.ps1 +++ /dev/null @@ -1,122 +0,0 @@ - -# 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 deleted file mode 100644 index 5c2079144f..0000000000 --- a/build/icons/imagesharp-logo-128.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:148a268c589b628f5d0b5af0e86911a0b393c35b8b25233c71553657c88e0b96 -size 7568 diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png deleted file mode 100644 index e38807ae1e..0000000000 --- a/build/icons/imagesharp-logo-256.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e4b2ff72aef1979500cd130c28490a00be116bb833bc96ca30c85dc0596099c -size 15413 diff --git a/build/icons/imagesharp-logo-32.png b/build/icons/imagesharp-logo-32.png deleted file mode 100644 index 273b171eb2..0000000000 --- a/build/icons/imagesharp-logo-32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:021c12313afbdc65f58bfea8c7b436d5c2102513bb63d9e64ee2b61a1344c56a -size 1799 diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png deleted file mode 100644 index 707dc9a35b..0000000000 --- a/build/icons/imagesharp-logo-512.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ae54ae0035df1f8f1459081e2f1d5cceda6f88cca6ec015d8c0209bf0d34edf -size 32534 diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png deleted file mode 100644 index 17577772eb..0000000000 --- a/build/icons/imagesharp-logo-64.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:92896854265693f28f9a503b9093cb2c9a4a9b329f310732efdd9c6f6c3761bc -size 3736 diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png deleted file mode 100644 index 707dc9a35b..0000000000 --- a/build/icons/imagesharp-logo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ae54ae0035df1f8f1459081e2f1d5cceda6f88cca6ec015d8c0209bf0d34edf -size 32534 diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg deleted file mode 100644 index 620287457a..0000000000 --- a/build/icons/imagesharp-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ci-build.ps1 b/ci-build.ps1 new file mode 100644 index 0000000000..d45af6ff4d --- /dev/null +++ b/ci-build.ps1 @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000000..09f45347ef --- /dev/null +++ b/ci-pack.ps1 @@ -0,0 +1,6 @@ +dotnet clean -c Release + +$repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY" + +# Building for packing and publishing. +dotnet pack -c Release --output "$PSScriptRoot/artifacts" /p:RepositoryUrl=$repositoryUrl diff --git a/ci-test.ps1 b/ci-test.ps1 new file mode 100644 index 0000000000..3915ae4ccd --- /dev/null +++ b/ci-test.ps1 @@ -0,0 +1,37 @@ +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 +} diff --git a/codecov.yml b/codecov.yml index ae6dd5f6bf..833fc0a51a 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,11 @@ -ignore: - "src/ImageSharp/Common/Helpers/DebugGuard.cs" +# Documentation: https://docs.codecov.io/docs/codecov-yaml - \ No newline at end of file +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 diff --git a/run-tests.ps1 b/run-tests.ps1 deleted file mode 100644 index 4aeaa14908..0000000000 --- a/run-tests.ps1 +++ /dev/null @@ -1,112 +0,0 @@ -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 new file mode 160000 index 0000000000..b0d4cd9864 --- /dev/null +++ b/shared-infrastructure @@ -0,0 +1 @@ +Subproject commit b0d4cd98647996265a668e852574d901b27f22d6 diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000000..bdf1ff49cb --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,52 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.props + src + + + + + + $(MSBuildThisFileDirectory)..\shared-infrastructure\SixLabors.ruleset + true + + + + true + + + + + true + true + + + + true + + true + snupkg + + + + + + + + + + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000000..1eeedecd2c --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,86 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.targets + + + + + + $(IntermediateOutputPath)$(MSBuildProjectName).InternalsVisibleTo$(DefaultLanguageSourceExtension) + + + + + + + <_LocalTopLevelSourceRoot Include="@(SourceRoot)" Condition="'%(SourceRoot.NestedRoot)' == ''"/> + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj deleted file mode 100644 index f4906d25e3..0000000000 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ /dev/null @@ -1,53 +0,0 @@ - - - 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 - 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 deleted file mode 100644 index 27f039f122..0000000000 --- a/src/ImageSharp.Drawing/Primitives/Region.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 deleted file mode 100644 index a4fef66a67..0000000000 --- a/src/ImageSharp.Drawing/Primitives/ShapePath.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index 812744b895..0000000000 --- a/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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 deleted file mode 100644 index 0c6e0d3b40..0000000000 --- a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 deleted file mode 100644 index c5e7a3e9ff..0000000000 --- a/src/ImageSharp.Drawing/Processing/Brushes.cs +++ /dev/null @@ -1,256 +0,0 @@ -// 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 deleted file mode 100644 index 7fd0ba7cd3..0000000000 --- a/src/ImageSharp.Drawing/Processing/ColorStop{TPixel}.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 deleted file mode 100644 index 782f5d4d73..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawBezierExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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 deleted file mode 100644 index a8ee4d90bc..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs +++ /dev/null @@ -1,137 +0,0 @@ -// 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 deleted file mode 100644 index 9084b30efe..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawLineExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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 deleted file mode 100644 index 0d3abf297e..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawPathCollectionExtensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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 deleted file mode 100644 index 4dbe942f2b..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawPathExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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 deleted file mode 100644 index 4dcfe00aa3..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawPolygonExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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 deleted file mode 100644 index 918fb1e738..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawRectangleExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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 deleted file mode 100644 index 46061ce9bc..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawTextExtensions.cs +++ /dev/null @@ -1,150 +0,0 @@ -// 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 deleted file mode 100644 index 8af01564c4..0000000000 --- a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush{TPixel}.cs +++ /dev/null @@ -1,170 +0,0 @@ -// 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 Point center; - - private readonly Point 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( - Point center, - Point 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 Point center; - - private readonly Point 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, - Point center, - Point 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(int xt, int 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 deleted file mode 100644 index ff4de3ff8f..0000000000 --- a/src/ImageSharp.Drawing/Processing/FillPathBuilderExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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 deleted file mode 100644 index da2dd35b6a..0000000000 --- a/src/ImageSharp.Drawing/Processing/FillPathCollectionExtensions.cs +++ /dev/null @@ -1,71 +0,0 @@ -// 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 deleted file mode 100644 index da10621113..0000000000 --- a/src/ImageSharp.Drawing/Processing/FillPathExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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 deleted file mode 100644 index 970ca22644..0000000000 --- a/src/ImageSharp.Drawing/Processing/FillPolygonExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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 deleted file mode 100644 index 26bf214f71..0000000000 --- a/src/ImageSharp.Drawing/Processing/FillRectangleExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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 deleted file mode 100644 index e566d03231..0000000000 --- a/src/ImageSharp.Drawing/Processing/FillRegionExtensions.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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 deleted file mode 100644 index 00141a8d8d..0000000000 --- a/src/ImageSharp.Drawing/Processing/GradientBrushBase{TPixel}.cs +++ /dev/null @@ -1,177 +0,0 @@ -// 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, y); - - 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; - - // 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 pixel. - /// This method is abstract as it's content depends on the shape of the gradient. - /// - /// The x coordinate of the pixel - /// The y coordinate of the pixel - /// - /// The position the given pixel 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(int x, int 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 deleted file mode 100644 index c156153be5..0000000000 --- a/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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 deleted file mode 100644 index a3c94a1b5a..0000000000 --- a/src/ImageSharp.Drawing/Processing/IBrush.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100644 index 6f63dcfd0f..0000000000 --- a/src/ImageSharp.Drawing/Processing/IPen.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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 deleted file mode 100644 index 1ef4bb9ec9..0000000000 --- a/src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs +++ /dev/null @@ -1,153 +0,0 @@ -// 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() - { - this.source.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 deleted file mode 100644 index 765bf5499d..0000000000 --- a/src/ImageSharp.Drawing/Processing/LinearGradientBrush{TPixel}.cs +++ /dev/null @@ -1,155 +0,0 @@ -// 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 Point p1; - - private readonly Point p2; - - /// - /// Initializes a new instance of the class. - /// - /// Start point - /// End point - /// defines how colors are repeated. - /// - public LinearGradientBrush( - Point p1, - Point 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 Point start; - - private readonly Point 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, - Point start, - Point 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(int x, int y) - { - if (this.acrossX == 0) - { - return (x - this.start.X) / (float)(this.end.X - this.start.X); - } - else if (this.acrossY == 0) - { - return (y - this.start.Y) / (float)(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 deleted file mode 100644 index 46ed36f687..0000000000 --- a/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs +++ /dev/null @@ -1,183 +0,0 @@ -// 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, 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) - { - return 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, 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 deleted file mode 100644 index 90253a3cb8..0000000000 --- a/src/ImageSharp.Drawing/Processing/Pens.cs +++ /dev/null @@ -1,129 +0,0 @@ -// 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 deleted file mode 100644 index 26c21a0e51..0000000000 --- a/src/ImageSharp.Drawing/Processing/Pen{TPixel}.cs +++ /dev/null @@ -1,79 +0,0 @@ -// 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 deleted file mode 100644 index 0957904c62..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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.Width); - 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); - - 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 deleted file mode 100644 index ed6c869511..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs +++ /dev/null @@ -1,124 +0,0 @@ -// 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 deleted file mode 100644 index 550c021caa..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs +++ /dev/null @@ -1,219 +0,0 @@ -// 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 deleted file mode 100644 index 266d842bfa..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs +++ /dev/null @@ -1,483 +0,0 @@ -// 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 deleted file mode 100644 index 16380fc34b..0000000000 --- a/src/ImageSharp.Drawing/Processing/RadialGradientBrush{TPixel}.cs +++ /dev/null @@ -1,105 +0,0 @@ -// 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 Point 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( - Point 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 Point 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, - Point 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(int x, int 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 deleted file mode 100644 index 09a1ff71fb..0000000000 --- a/src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs +++ /dev/null @@ -1,171 +0,0 @@ -// 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 deleted file mode 100644 index 20a6833c40..0000000000 --- a/src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs +++ /dev/null @@ -1,133 +0,0 @@ -// 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 deleted file mode 100644 index 3c682a761b..0000000000 --- a/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs +++ /dev/null @@ -1,169 +0,0 @@ -// 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 deleted file mode 100644 index 2891598b9b..0000000000 --- a/src/ImageSharp.Drawing/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -// 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 deleted file mode 100644 index ca1da5505a..0000000000 --- a/src/ImageSharp.Drawing/Utils/QuickSort.cs +++ /dev/null @@ -1,84 +0,0 @@ -// 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/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 63ccea4e66..c5abbda61b 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -1,12 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Runtime.InteropServices; - +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Advanced { @@ -16,112 +19,119 @@ namespace SixLabors.ImageSharp.Advanced public static class AdvancedImageExtensions { /// - /// Gets the configuration for the image. + /// For a given file path find the best encoder to use via its extension. /// - /// 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; + /// The source image. + /// The target file path to save the image to. + /// The matching encoder. + public static IImageEncoder DetectEncoder(this Image source, string filePath) + { + Guard.NotNull(filePath, nameof(filePath)); + + 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.AppendFormat(" - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); + } + + throw new NotSupportedException(sb.ToString()); + } + + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); + + if (encoder is null) + { + 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.AppendFormat(" - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); + } + + throw new NotSupportedException(sb.ToString()); + } + + return encoder; + } /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. + /// Accepts a to implement a double-dispatch pattern in order to + /// apply pixel-specific operations on non-generic instances /// - /// The type of the pixel. - /// The source. - /// The - public static Span GetPixelSpan(this Image source) - where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelSpan(); + /// The source image. + /// The image visitor. + public static void AcceptVisitor(this Image source, IImageVisitor visitor) + => source.Accept(visitor); /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the the first pixel on that row. + /// Accepts a to implement a double-dispatch pattern in order to + /// apply pixel-specific operations on non-generic instances /// - /// 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); + /// 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 of of contiguous memory - /// at row beginning from the the first pixel on that row. + /// Gets the configuration for the image. /// - /// 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); + /// The source image. + /// Returns the configuration. + public static Configuration GetConfiguration(this Image source) + => GetConfiguration((IConfigurationProvider)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 contiguous memory area containing Width*Height TPixel elements laid out in row-major order. + /// Gets the configuration for the image frame. /// - /// 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); + /// The source image. + /// Returns the configuration. + public static Configuration GetConfiguration(this ImageFrame source) + => GetConfiguration((IConfigurationProvider)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. + /// Gets the configuration. /// - /// 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(); + /// Returns the bounds of the image + private static Configuration GetConfiguration(IConfigurationProvider source) + => source?.Configuration ?? Configuration.Default; /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. + /// 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 Pixel format. - /// The source - /// The - internal static Memory GetPixelMemory(this ImageFrame source) - where TPixel : struct, IPixel - { - return source.PixelBuffer.MemorySource.Memory; - } + /// 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 . + /// + 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 of contiguous memory in the source image's pixel format - /// stored in row major order. + /// 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 Pixel format. - /// The source - /// The - internal static Memory GetPixelMemory(this Image source) - where TPixel : struct, IPixel - { - return source.Frames.RootFrame.GetPixelMemory(); - } + /// 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 . + /// + 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 @@ -131,9 +141,15 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - internal static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) - where TPixel : struct, IPixel - => source.PixelBuffer.GetRowMemory(rowIndex); + public static Memory GetPixelRowMemory(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 of contiguous memory @@ -143,70 +159,22 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - internal static Memory GetPixelRowMemory(this Image source, int rowIndex) - where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelRowMemory(rowIndex); + public static Memory GetPixelRowMemory(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 assigned to 'source'. /// - /// The source image + /// The source image. /// Returns the configuration. - internal static MemoryAllocator GetMemoryAllocator(this IConfigurable source) + internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider 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 eb6991e6a1..2ea456286f 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -1,10 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Diagnostics.CodeAnalysis; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -15,8 +19,11 @@ namespace SixLabors.ImageSharp.Advanced /// 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.) /// - public static class AotCompilerTools + [ExcludeFromCodeCoverage] + internal static class AotCompilerTools { static AotCompilerTools() { @@ -25,23 +32,63 @@ namespace SixLabors.ImageSharp.Advanced 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(); } + /// + /// 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. + /// + private static void SeedEverything() + { + 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(); + } + /// /// Seeds the compiler using the given pixel format. /// /// The pixel format. - public static void Seed() - where TPixel : struct, IPixel + private static void Seed() + where TPixel : unmanaged, IPixel { // This is we actually call all the individual methods you need to seed. AotCompileOctreeQuantizer(); AotCompileWuQuantizer(); + AotCompilePaletteQuantizer(); AotCompileDithering(); + AotCompilePixelOperations(); - System.Runtime.CompilerServices.Unsafe.SizeOf(); + Unsafe.SizeOf(); AotCodec(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder()); AotCodec(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder()); @@ -51,39 +98,11 @@ namespace SixLabors.ImageSharp.Advanced // TODO: Do the discovery work to figure out what works and what doesn't. } - /// - /// 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(); - } - - /// - /// 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(); - } - /// /// 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: + /// 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 haevy use of generics, which are too confusing for the AoT + /// 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 @@ -91,10 +110,13 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileOctreeQuantizer() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var test = new OctreeFrameQuantizer(new OctreeQuantizer(false)); - test.AotGetPalette(); + using (var test = new OctreeQuantizer(Configuration.Default, new OctreeQuantizer().Options)) + { + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); + } } /// @@ -102,11 +124,27 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileWuQuantizer() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var test = new WuFrameQuantizer(new WuQuantizer(false)); - test.QuantizeFrame(new ImageFrame(Configuration.Default, 1, 1)); - test.AotGetPalette(); + using (var test = new WuQuantizer(Configuration.Default, new WuQuantizer().Options)) + { + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); + } + } + + /// + /// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS. + /// + /// The pixel format. + private static void AotCompilePaletteQuantizer() + where TPixel : unmanaged, IPixel + { + using (var test = (PaletteQuantizer)new PaletteQuantizer(Array.Empty()).CreatePixelSpecificQuantizer(Configuration.Default)) + { + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); + } } /// @@ -114,11 +152,16 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileDithering() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var test = new FloydSteinbergDiffuser(); + ErrorDither errorDither = ErrorDither.FloydSteinberg; + OrderedDither orderedDither = OrderedDither.Bayer2x2; TPixel pixel = default; - test.Dither(new ImageFrame(Configuration.Default, 1, 1), pixel, pixel, 0, 0, 0, 0, 0, 0); + using (var image = new ImageFrame(Configuration.Default, 1, 1)) + { + errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0); + orderedDither.Dither(pixel, 0, 0, 0, 0); + } } /// @@ -128,7 +171,7 @@ namespace SixLabors.ImageSharp.Advanced /// The image encoder to seed. /// The pixel format. private static void AotCodec(IImageDecoder decoder, IImageEncoder encoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { try { @@ -146,5 +189,16 @@ namespace SixLabors.ImageSharp.Advanced { } } + + /// + /// This method pre-seeds the PixelOperations engine for the AoT compiler on iOS. + /// + /// The pixel format. + private static void AotCompilePixelOperations() + where TPixel : unmanaged, IPixel + { + var pixelOp = new PixelOperations(); + pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear); + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Advanced/IConfigurable.cs b/src/ImageSharp/Advanced/IConfigurable.cs deleted file mode 100644 index 38fc83ae1d..0000000000 --- a/src/ImageSharp/Advanced/IConfigurable.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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 new file mode 100644 index 0000000000..9c9d2a942b --- /dev/null +++ b/src/ImageSharp/Advanced/IConfigurationProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for objects that can provide access to configuration. + /// + internal 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 new file mode 100644 index 0000000000..ccff180266 --- /dev/null +++ b/src/ImageSharp/Advanced/IImageVisitor.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Threading; +using System.Threading.Tasks; +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 19616d7427..948abe0be1 100644 --- a/src/ImageSharp/Advanced/IPixelSource.cs +++ b/src/ImageSharp/Advanced/IPixelSource.cs @@ -1,22 +1,32 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; 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 : struct, IPixel + where TPixel : unmanaged, 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 new file mode 100644 index 0000000000..cc7072ff9d --- /dev/null +++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..a76624e1ac --- /dev/null +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 + { + /// + /// 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 new file mode 100644 index 0000000000..122296172c --- /dev/null +++ b/src/ImageSharp/Advanced/IRowOperation.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..5e1562a794 --- /dev/null +++ b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 + { + /// + /// 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 new file mode 100644 index 0000000000..5415249d23 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading.Tasks; + +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 new file mode 100644 index 0000000000..3c2b9fd2c8 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors. +// 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; + +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(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 width; + private readonly MemoryAllocator allocator; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperationWrapper( + int minY, + int maxY, + int stepY, + int width, + MemoryAllocator allocator, + in T action) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.width = width; + 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.width); + + Span span = buffer.Memory.Span; + + for (int y = yMin; y < yMax; y++) + { + Unsafe.AsRef(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); + var rows = new RowInterval(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 width; + private readonly MemoryAllocator allocator; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperationWrapper( + int minY, + int maxY, + int stepY, + int width, + MemoryAllocator allocator, + in T operation) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.width = width; + 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); + var rows = new RowInterval(yMin, yMax); + + using IMemoryOwner buffer = this.allocator.Allocate(this.width); + + 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 new file mode 100644 index 0000000000..e787b7cfc5 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -0,0 +1,288 @@ +// Copyright (c) Six Labors. +// 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; + +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 + { + var 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 * 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(operation).Invoke(y); + } + + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowOperationWrapper(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 + { + var 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 * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + using IMemoryOwner buffer = allocator.Allocate(width); + Span span = buffer.Memory.Span; + + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(operation).Invoke(y, span); + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, width, 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 + { + var 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 * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + Unsafe.AsRef(in operation).Invoke(in rows); + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowIntervalOperationWrapper(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 + { + var 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 * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + using IMemoryOwner buffer = allocator.Allocate(width); + + Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + [MethodImpl(InliningOptions.ShortMethod)] + 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)}"); + } + } +} diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs new file mode 100644 index 0000000000..cd3fc8fd9c --- /dev/null +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// Contains constructors and implicit conversion methods. + /// + public readonly partial struct Color + { + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Rgba64 pixel) => this.data = pixel; + + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Rgba32 pixel) => this.data = new Rgba64(pixel); + + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Argb32 pixel) => this.data = new Rgba64(pixel); + + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Bgra32 pixel) => this.data = new Rgba64(pixel); + + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Rgb24 pixel) => this.data = new Rgba64(pixel); + + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Bgr24 pixel) => this.data = new Rgba64(pixel); + + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Vector4 vector) => this.data = new Rgba64(vector); + + /// + /// Converts a to . + /// + /// The . + /// The . + public static explicit operator Vector4(Color color) => color.data.ToVector4(); + + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static explicit operator Color(Vector4 source) => new Color(source); + + [MethodImpl(InliningOptions.ShortMethod)] + internal Rgba32 ToRgba32() => this.data.ToRgba32(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal Bgra32 ToBgra32() => this.data.ToBgra32(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal Argb32 ToArgb32() => this.data.ToArgb32(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal Rgb24 ToRgb24() => this.data.ToRgb24(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal Bgr24 ToBgr24() => this.data.ToBgr24(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal Vector4 ToVector4() => this.data.ToVector4(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Color/Color.NamedColors.cs b/src/ImageSharp/Color/Color.NamedColors.cs new file mode 100644 index 0000000000..609191d5ed --- /dev/null +++ b/src/ImageSharp/Color/Color.NamedColors.cs @@ -0,0 +1,918 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp +{ + /// + /// Contains static named color values. + /// + /// + public readonly partial struct Color + { + private static readonly Lazy> NamedColorsLookupLazy = new Lazy>(CreateNamedColorsLookup, true); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. + /// + public static readonly Color AliceBlue = FromRgba(240, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly Color AntiqueWhite = FromRgba(250, 235, 215, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Color Aqua = FromRgba(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly Color Aquamarine = FromRgba(127, 255, 212, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly Color Azure = FromRgba(240, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly Color Beige = FromRgba(245, 245, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly Color Bisque = FromRgba(255, 228, 196, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly Color Black = FromRgba(0, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. + /// + public static readonly Color BlanchedAlmond = FromRgba(255, 235, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly Color Blue = FromRgba(0, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. + /// + public static readonly Color BlueViolet = FromRgba(138, 43, 226, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A52A2A. + /// + public static readonly Color Brown = FromRgba(165, 42, 42, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DEB887. + /// + public static readonly Color BurlyWood = FromRgba(222, 184, 135, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. + /// + public static readonly Color CadetBlue = FromRgba(95, 158, 160, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFF00. + /// + public static readonly Color Chartreuse = FromRgba(127, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2691E. + /// + public static readonly Color Chocolate = FromRgba(210, 105, 30, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF7F50. + /// + public static readonly Color Coral = FromRgba(255, 127, 80, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6495ED. + /// + public static readonly Color CornflowerBlue = FromRgba(100, 149, 237, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. + /// + public static readonly Color Cornsilk = FromRgba(255, 248, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DC143C. + /// + public static readonly Color Crimson = FromRgba(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 = FromRgba(0, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008B8B. + /// + public static readonly Color DarkCyan = FromRgba(0, 139, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B8860B. + /// + public static readonly Color DarkGoldenrod = FromRgba(184, 134, 11, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly Color DarkGray = FromRgba(169, 169, 169, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #006400. + /// + public static readonly Color DarkGreen = FromRgba(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 = FromRgba(189, 183, 107, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B008B. + /// + public static readonly Color DarkMagenta = FromRgba(139, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #556B2F. + /// + public static readonly Color DarkOliveGreen = FromRgba(85, 107, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF8C00. + /// + public static readonly Color DarkOrange = FromRgba(255, 140, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9932CC. + /// + public static readonly Color DarkOrchid = FromRgba(153, 50, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B0000. + /// + public static readonly Color DarkRed = FromRgba(139, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E9967A. + /// + public static readonly Color DarkSalmon = FromRgba(233, 150, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8FBC8F. + /// + public static readonly Color DarkSeaGreen = FromRgba(143, 188, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #483D8B. + /// + public static readonly Color DarkSlateBlue = FromRgba(72, 61, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly Color DarkSlateGray = FromRgba(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 = FromRgba(0, 206, 209, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9400D3. + /// + public static readonly Color DarkViolet = FromRgba(148, 0, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF1493. + /// + public static readonly Color DeepPink = FromRgba(255, 20, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00BFFF. + /// + public static readonly Color DeepSkyBlue = FromRgba(0, 191, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly Color DimGray = FromRgba(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 = FromRgba(30, 144, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B22222. + /// + public static readonly Color Firebrick = FromRgba(178, 34, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. + /// + public static readonly Color FloralWhite = FromRgba(255, 250, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #228B22. + /// + public static readonly Color ForestGreen = FromRgba(34, 139, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Color Fuchsia = FromRgba(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. + /// + public static readonly Color Gainsboro = FromRgba(220, 220, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. + /// + public static readonly Color GhostWhite = FromRgba(248, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFD700. + /// + public static readonly Color Gold = FromRgba(255, 215, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DAA520. + /// + public static readonly Color Goldenrod = FromRgba(218, 165, 32, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Color Gray = FromRgba(128, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly Color Green = FromRgba(0, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. + /// + public static readonly Color GreenYellow = FromRgba(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 = FromRgba(240, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF69B4. + /// + public static readonly Color HotPink = FromRgba(255, 105, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. + /// + public static readonly Color IndianRed = FromRgba(205, 92, 92, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4B0082. + /// + public static readonly Color Indigo = FromRgba(75, 0, 130, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. + /// + public static readonly Color Ivory = FromRgba(255, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0E68C. + /// + public static readonly Color Khaki = FromRgba(240, 230, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. + /// + public static readonly Color Lavender = FromRgba(230, 230, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. + /// + public static readonly Color LavenderBlush = FromRgba(255, 240, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7CFC00. + /// + public static readonly Color LawnGreen = FromRgba(124, 252, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFACD. + /// + public static readonly Color LemonChiffon = FromRgba(255, 250, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. + /// + public static readonly Color LightBlue = FromRgba(173, 216, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F08080. + /// + public static readonly Color LightCoral = FromRgba(240, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. + /// + public static readonly Color LightCyan = FromRgba(224, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. + /// + public static readonly Color LightGoldenrodYellow = FromRgba(250, 250, 210, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly Color LightGray = FromRgba(211, 211, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #90EE90. + /// + public static readonly Color LightGreen = FromRgba(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 = FromRgba(255, 182, 193, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA07A. + /// + public static readonly Color LightSalmon = FromRgba(255, 160, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #20B2AA. + /// + public static readonly Color LightSeaGreen = FromRgba(32, 178, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEFA. + /// + public static readonly Color LightSkyBlue = FromRgba(135, 206, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly Color LightSlateGray = FromRgba(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 = FromRgba(176, 196, 222, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. + /// + public static readonly Color LightYellow = FromRgba(255, 255, 224, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly Color Lime = FromRgba(0, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #32CD32. + /// + public static readonly Color LimeGreen = FromRgba(50, 205, 50, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. + /// + public static readonly Color Linen = FromRgba(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 = FromRgba(128, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #66CDAA. + /// + public static readonly Color MediumAquamarine = FromRgba(102, 205, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000CD. + /// + public static readonly Color MediumBlue = FromRgba(0, 0, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BA55D3. + /// + public static readonly Color MediumOrchid = FromRgba(186, 85, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9370DB. + /// + public static readonly Color MediumPurple = FromRgba(147, 112, 219, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #3CB371. + /// + public static readonly Color MediumSeaGreen = FromRgba(60, 179, 113, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7B68EE. + /// + public static readonly Color MediumSlateBlue = FromRgba(123, 104, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FA9A. + /// + public static readonly Color MediumSpringGreen = FromRgba(0, 250, 154, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #48D1CC. + /// + public static readonly Color MediumTurquoise = FromRgba(72, 209, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C71585. + /// + public static readonly Color MediumVioletRed = FromRgba(199, 21, 133, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #191970. + /// + public static readonly Color MidnightBlue = FromRgba(25, 25, 112, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. + /// + public static readonly Color MintCream = FromRgba(245, 255, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. + /// + public static readonly Color MistyRose = FromRgba(255, 228, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. + /// + public static readonly Color Moccasin = FromRgba(255, 228, 181, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. + /// + public static readonly Color NavajoWhite = FromRgba(255, 222, 173, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly Color Navy = FromRgba(0, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. + /// + public static readonly Color OldLace = FromRgba(253, 245, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly Color Olive = FromRgba(128, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6B8E23. + /// + public static readonly Color OliveDrab = FromRgba(107, 142, 35, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA500. + /// + public static readonly Color Orange = FromRgba(255, 165, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF4500. + /// + public static readonly Color OrangeRed = FromRgba(255, 69, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DA70D6. + /// + public static readonly Color Orchid = FromRgba(218, 112, 214, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. + /// + public static readonly Color PaleGoldenrod = FromRgba(238, 232, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #98FB98. + /// + public static readonly Color PaleGreen = FromRgba(152, 251, 152, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. + /// + public static readonly Color PaleTurquoise = FromRgba(175, 238, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DB7093. + /// + public static readonly Color PaleVioletRed = FromRgba(219, 112, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. + /// + public static readonly Color PapayaWhip = FromRgba(255, 239, 213, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. + /// + public static readonly Color PeachPuff = FromRgba(255, 218, 185, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD853F. + /// + public static readonly Color Peru = FromRgba(205, 133, 63, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. + /// + public static readonly Color Pink = FromRgba(255, 192, 203, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. + /// + public static readonly Color Plum = FromRgba(221, 160, 221, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. + /// + public static readonly Color PowderBlue = FromRgba(176, 224, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly Color Purple = FromRgba(128, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #663399. + /// + public static readonly Color RebeccaPurple = FromRgba(102, 51, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly Color Red = FromRgba(255, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. + /// + public static readonly Color RosyBrown = FromRgba(188, 143, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4169E1. + /// + public static readonly Color RoyalBlue = FromRgba(65, 105, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B4513. + /// + public static readonly Color SaddleBrown = FromRgba(139, 69, 19, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FA8072. + /// + public static readonly Color Salmon = FromRgba(250, 128, 114, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F4A460. + /// + public static readonly Color SandyBrown = FromRgba(244, 164, 96, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2E8B57. + /// + public static readonly Color SeaGreen = FromRgba(46, 139, 87, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. + /// + public static readonly Color SeaShell = FromRgba(255, 245, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A0522D. + /// + public static readonly Color Sienna = FromRgba(160, 82, 45, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly Color Silver = FromRgba(192, 192, 192, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEEB. + /// + public static readonly Color SkyBlue = FromRgba(135, 206, 235, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. + /// + public static readonly Color SlateBlue = FromRgba(106, 90, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly Color SlateGray = FromRgba(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 = FromRgba(255, 250, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF7F. + /// + public static readonly Color SpringGreen = FromRgba(0, 255, 127, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4682B4. + /// + public static readonly Color SteelBlue = FromRgba(70, 130, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2B48C. + /// + public static readonly Color Tan = FromRgba(210, 180, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly Color Teal = FromRgba(0, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. + /// + public static readonly Color Thistle = FromRgba(216, 191, 216, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF6347. + /// + public static readonly Color Tomato = FromRgba(255, 99, 71, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00000000. + /// + public static readonly Color Transparent = FromRgba(0, 0, 0, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #40E0D0. + /// + public static readonly Color Turquoise = FromRgba(64, 224, 208, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EE82EE. + /// + public static readonly Color Violet = FromRgba(238, 130, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. + /// + public static readonly Color Wheat = FromRgba(245, 222, 179, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Color White = FromRgba(255, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. + /// + public static readonly Color WhiteSmoke = FromRgba(245, 245, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly Color Yellow = FromRgba(255, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9ACD32. + /// + public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255); + + private static Dictionary CreateNamedColorsLookup() + { + return new Dictionary(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 new file mode 100644 index 0000000000..cad6553c01 --- /dev/null +++ b/src/ImageSharp/Color/Color.WebSafePalette.cs @@ -0,0 +1,166 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp +{ + /// + /// Contains the definition of . + /// + public partial struct Color + { + private static readonly Lazy WebSafePaletteLazy = new Lazy(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() => new[] + { + 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 + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs new file mode 100644 index 0000000000..52299ae8fd --- /dev/null +++ b/src/ImageSharp/Color/Color.WernerPalette.cs @@ -0,0 +1,135 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp +{ + /// + /// Contains the definition of . + /// + public partial struct Color + { + private static readonly Lazy WernerPaletteLazy = new Lazy(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() => new[] + { + 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 new file mode 100644 index 0000000000..554fcb8354 --- /dev/null +++ b/src/ImageSharp/Color/Color.cs @@ -0,0 +1,268 @@ +// Copyright (c) Six Labors. +// 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 +{ + /// + /// 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 Rgba64 data; + + [MethodImpl(InliningOptions.ShortMethod)] + private Color(byte r, byte g, byte b, byte a) + { + this.data = new Rgba64( + ImageMaths.UpscaleFrom8BitTo16Bit(r), + ImageMaths.UpscaleFrom8BitTo16Bit(g), + ImageMaths.UpscaleFrom8BitTo16Bit(b), + ImageMaths.UpscaleFrom8BitTo16Bit(a)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private Color(byte r, byte g, byte b) + { + this.data = new Rgba64( + ImageMaths.UpscaleFrom8BitTo16Bit(r), + ImageMaths.UpscaleFrom8BitTo16Bit(g), + ImageMaths.UpscaleFrom8BitTo16Bit(b), + ushort.MaxValue); + } + + /// + /// 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(InliningOptions.ShortMethod)] + public static bool operator ==(Color left, Color right) + { + return 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. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Color left, Color right) + { + return !left.Equals(right); + } + + /// + /// Creates a from RGBA bytes. + /// + /// The red component (0-255). + /// The green component (0-255). + /// The blue component (0-255). + /// The alpha component (0-255). + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Color FromRgba(byte r, byte g, byte b, byte a) => new Color(r, g, b, a); + + /// + /// Creates a from RGB bytes. + /// + /// The red component (0-255). + /// The green component (0-255). + /// The blue component (0-255). + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b); + + /// + /// Creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Color ParseHex(string hex) + { + var rgba = Rgba32.ParseHex(hex); + + return new Color(rgba); + } + + /// + /// Attempts to creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool TryParseHex(string hex, out Color result) + { + result = default; + + if (Rgba32.TryParseHex(hex, out Rgba32 rgba)) + { + result = new Color(rgba); + return true; + } + + return false; + } + + /// + /// Creates 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 arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static Color Parse(string input) + { + Guard.NotNull(input, nameof(input)); + + if (!TryParse(input, out Color color)) + { + throw new ArgumentException("Input string is not in the correct format.", nameof(input)); + } + + return color; + } + + /// + /// Attempts to creates 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 arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + public static bool TryParse(string input, out Color result) + { + result = default; + + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + if (NamedColorsLookupLazy.Value.TryGetValue(input, out result)) + { + return true; + } + + return TryParseHex(input, out result); + } + + /// + /// 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. + public Color WithAlpha(float alpha) + { + var v = (Vector4)this; + v.W = alpha; + return new Color(v); + } + + /// + /// Gets the hexadecimal representation of the color instance in rrggbbaa form. + /// + /// A hexadecimal string representation of the value. + [MethodImpl(InliningOptions.ShortMethod)] + public string ToHex() => this.data.ToRgba32().ToHex(); + + /// + public override string ToString() => this.ToHex(); + + /// + /// Converts the color instance to a specified type. + /// + /// The pixel type to convert to. + /// The pixel value. + [MethodImpl(InliningOptions.ShortMethod)] + public TPixel ToPixel() + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + pixel.FromRgba64(this.data); + return pixel; + } + + /// + /// Bulk converts a span of to a span of a specified type. + /// + /// The pixel type to convert to. + /// The configuration. + /// The source color span. + /// The destination pixel span. + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToPixel( + Configuration configuration, + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source); + PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Color other) + { + return this.data.PackedValue == other.data.PackedValue; + } + + /// + public override bool Equals(object obj) + { + return obj is Color other && this.Equals(other); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + { + return this.data.PackedValue.GetHashCode(); + } + } +} diff --git a/src/ImageSharp/ColorSpaces/CieLab.cs b/src/ImageSharp/ColorSpaces/CieLab.cs index 146acf12ee..4d25836ec1 100644 --- a/src/ImageSharp/ColorSpaces/CieLab.cs +++ b/src/ImageSharp/ColorSpaces/CieLab.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorSpaces/CieLch.cs index 074bc1516b..3e94790bb6 100644 --- a/src/ImageSharp/ColorSpaces/CieLch.cs +++ b/src/ImageSharp/ColorSpaces/CieLch.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,15 +13,15 @@ namespace SixLabors.ImageSharp.ColorSpaces /// public readonly struct CieLch : 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.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). diff --git a/src/ImageSharp/ColorSpaces/CieLchuv.cs b/src/ImageSharp/ColorSpaces/CieLchuv.cs index ab6f639a2b..272c535567 100644 --- a/src/ImageSharp/ColorSpaces/CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/CieLchuv.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/ColorSpaces/CieLuv.cs b/src/ImageSharp/ColorSpaces/CieLuv.cs index d54d92b62a..b11447fa76 100644 --- a/src/ImageSharp/ColorSpaces/CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/CieLuv.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs deleted file mode 100644 index 49c1da9f10..0000000000 --- a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs +++ /dev/null @@ -1,79 +0,0 @@ -// 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 index fff296945e..526c03831e 100644 --- a/src/ImageSharp/ColorSpaces/CieXyy.cs +++ b/src/ImageSharp/ColorSpaces/CieXyy.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/ColorSpaces/CieXyz.cs b/src/ImageSharp/ColorSpaces/CieXyz.cs index 30521476e5..aaf48c0b9b 100644 --- a/src/ImageSharp/ColorSpaces/CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/CieXyz.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.ColorSpaces public readonly float Y; /// - /// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response + /// 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; diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs index 04901126c1..0aab295548 100644 --- a/src/ImageSharp/ColorSpaces/Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.ColorSpaces [MethodImpl(InliningOptions.ShortMethod)] public Cmyk(Vector4 vector) { - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); this.C = vector.X; this.M = vector.Y; this.Y = vector.Z; @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.ColorSpaces public static bool operator ==(Cmyk left, Cmyk right) => left.Equals(right); /// - /// Compares two objects for inequality + /// Compares two objects for inequality. /// /// The on the left side of the operand. /// The on the right side of the operand. diff --git a/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs index 13cca1582d..b72332ebe7 100644 --- a/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -7,7 +7,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.ColorSpaces.Companding { /// - /// Implements gamma companding + /// Implements gamma companding. /// /// /// diff --git a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs index 9e2cf8ad86..719565fd81 100644 --- a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.ColorSpaces.Conversion; namespace SixLabors.ImageSharp.ColorSpaces.Companding { /// - /// Implements L* companding + /// Implements L* companding. /// /// /// For more info see: @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// The representing the linear channel value. [MethodImpl(InliningOptions.ShortMethod)] public static float Expand(float channel) - => channel <= 0.08 ? 100 * channel / CieConstants.Kappa : ImageMaths.Pow3((channel + 0.16F) / 1.16F); + => channel <= 0.08F ? (100F * channel) / CieConstants.Kappa : ImageMaths.Pow3((channel + 0.16F) / 1.16F); /// /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. @@ -33,6 +33,6 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// The representing the nonlinear channel value. [MethodImpl(InliningOptions.ShortMethod)] public static float Compress(float channel) - => channel <= CieConstants.Epsilon ? channel * CieConstants.Kappa / 100F : MathF.Pow(1.16F * channel, 0.3333333F) - 0.16F; + => 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 index a3a9121727..2eb2537fc5 100644 --- a/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -7,14 +7,19 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.ColorSpaces.Companding { /// - /// Implements Rec. 2020 companding function (for 12-bits). + /// Implements Rec. 2020 companding function. /// /// /// - /// For 10-bits, companding is identical to /// 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. /// @@ -22,7 +27,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// The representing the linear channel value. [MethodImpl(InliningOptions.ShortMethod)] public static float Expand(float channel) - => channel < 0.08145F ? channel / 4.5F : MathF.Pow((channel + 0.0993F) / 1.0993F, 2.222222F); + => channel < InverseBeta ? channel / 4.5F : MathF.Pow((channel + AlphaMinusOne) / Alpha, Epsilon); /// /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. @@ -31,6 +36,6 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// The representing the nonlinear channel value. [MethodImpl(InliningOptions.ShortMethod)] public static float Compress(float channel) - => channel < 0.0181F ? 4500F * channel : (1.0993F * channel) - 0.0993F; + => 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 index e2e802d08a..cf6f97e44d 100644 --- a/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,6 +14,8 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// public static class Rec709Companding { + private const float Epsilon = 1 / 0.45F; + /// /// Expands a companded channel to its linear equivalent with respect to the energy. /// @@ -21,7 +23,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// 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, 2.222222F); + => channel < 0.081F ? channel / 4.5F : MathF.Pow((channel + 0.099F) / 1.099F, Epsilon); /// /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. @@ -30,6 +32,6 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// The representing the nonlinear channel value. [MethodImpl(InliningOptions.ShortMethod)] public static float Compress(float channel) - => channel < 0.018F ? 4500F * channel : (1.099F * channel) - 0.099F; + => 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 index 5ae4629137..2e212ad19f 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.ColorSpaces.Companding { /// - /// Implements sRGB companding + /// Implements sRGB companding. /// /// /// For more info see: @@ -30,9 +30,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding for (int i = 0; i < vectors.Length; i++) { ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - v.X = Expand(v.X); - v.Y = Expand(v.Y); - v.Z = Expand(v.Z); + Expand(ref v); } } @@ -48,9 +46,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding for (int i = 0; i < vectors.Length; i++) { ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - v.X = Compress(v.X); - v.Y = Compress(v.Y); - v.Z = Compress(v.Z); + Compress(ref v); } } @@ -58,17 +54,25 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// Expands a companded vector to its linear equivalent with respect to the energy. /// /// The vector. - /// The representing the linear channel values. [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Expand(Vector4 vector) => new Vector4(Expand(vector.X), Expand(vector.Y), Expand(vector.Z), vector.W); + 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. - /// The representing the nonlinear channel values. [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Compress(Vector4 vector) => new Vector4(Compress(vector.X), Compress(vector.Y), Compress(vector.Z), vector.W); + 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. diff --git a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs index 2bcdc5127f..a81845f219 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.ColorSpaces.Conversion diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs index 892c0d5e38..f39d5049c4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -157,4 +155,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion 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 index 3c197673d3..86075e0023 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { @@ -19,21 +18,20 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private static readonly CieLchToCieLabConverter CieLchToCieLabConverter = new CieLchToCieLabConverter(); /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The public CieLab ToCieLab(in CieLch color) { - // Conversion (perserving white point) + // Conversion (preserving white point) CieLab unadapted = CieLchToCieLabConverter.Convert(color); - // Adaptation return this.Adapt(unadapted); } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -54,7 +52,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -66,7 +64,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -87,7 +85,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -99,7 +97,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -120,7 +118,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -132,7 +130,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -159,10 +157,8 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in CieXyz color) { - // Adaptation CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLabWhitePoint); - // Conversion return this.cieXyzToCieLabConverter.Convert(adapted); } @@ -199,7 +195,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -220,7 +216,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -232,7 +228,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -253,7 +249,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -264,7 +260,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -285,7 +281,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -297,7 +293,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -318,7 +314,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -330,7 +326,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -351,7 +347,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -363,7 +359,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -384,7 +380,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -396,7 +392,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -429,7 +425,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -449,4 +445,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs index 0a8607e3bc..da2e808444 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -26,15 +24,13 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in CieLab color) { - // Adaptation CieLab adapted = this.Adapt(color); - // Conversion return CieLabToCieLchConverter.Convert(adapted); } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -55,7 +51,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -67,7 +63,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -88,7 +84,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -100,7 +96,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -121,7 +117,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -133,7 +129,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -166,7 +162,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -187,7 +183,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -198,7 +194,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -219,7 +215,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -231,7 +227,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -252,7 +248,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -264,7 +260,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -285,7 +281,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -297,7 +293,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -318,7 +314,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -330,7 +326,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -351,7 +347,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -363,7 +359,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -384,7 +380,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -396,7 +392,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -417,7 +413,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -429,7 +425,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -449,4 +445,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs index 3a779ee722..7f100428bc 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -32,7 +30,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -53,7 +51,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -65,7 +63,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -86,21 +84,19 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The public CieLchuv ToCieLchuv(in CieLuv color) { - // Adaptation CieLuv adapted = this.Adapt(color); - // Conversion return CieLuvToCieLchuvConverter.Convert(adapted); } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -121,7 +117,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -133,7 +129,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -154,7 +150,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -166,7 +162,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -187,7 +183,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -199,7 +195,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -220,7 +216,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -253,7 +249,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -265,7 +261,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -286,7 +282,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -298,7 +294,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -319,7 +315,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -331,7 +327,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -352,7 +348,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -364,7 +360,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -385,7 +381,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -397,7 +393,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -418,7 +414,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -429,7 +425,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -449,4 +445,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs index 90eb8e34d7..1c831f7144 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -17,7 +15,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private static readonly CieLchuvToCieLuvConverter CieLchuvToCieLuvConverter = new CieLchuvToCieLuvConverter(); /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -49,7 +47,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -81,13 +79,13 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The public CieLuv ToCieLuv(in CieLchuv color) { - // Conversion (perserving white point) + // Conversion (preserving white point) CieLuv unadapted = CieLchuvToCieLuvConverter.Convert(color); // Adaptation @@ -95,7 +93,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -116,7 +114,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -127,7 +125,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -148,7 +146,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -162,7 +160,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -183,7 +181,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -194,7 +192,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -226,7 +224,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -247,7 +245,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -258,7 +256,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -279,7 +277,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -290,7 +288,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -311,7 +309,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -322,7 +320,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -343,7 +341,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -354,7 +352,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -375,7 +373,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -386,7 +384,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -407,7 +405,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -418,10 +416,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -438,4 +436,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs index d03c10a01d..0adac22019 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -440,4 +438,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs index fada6d9c59..b069bb72c6 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -24,7 +22,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private LinearRgbToCieXyzConverter linearRgbToCieXyzConverter; /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -38,7 +36,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -73,7 +71,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -94,7 +92,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -108,7 +106,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -143,7 +141,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -164,7 +162,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -175,7 +173,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -196,20 +194,19 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The public CieXyz ToCieXyz(in Cmyk color) { - // Conversion var rgb = this.ToRgb(color); return this.ToCieXyz(rgb); } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -230,23 +227,22 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The public CieXyz ToCieXyz(in Hsl color) { - // Conversion var rgb = this.ToRgb(color); return this.ToCieXyz(rgb); } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -264,7 +260,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -277,7 +273,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -298,21 +294,19 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The public CieXyz ToCieXyz(in HunterLab color) { - // Conversion CieXyz unadapted = HunterLabToCieXyzConverter.Convert(color); - // Adaptation return this.Adapt(unadapted, color.WhitePoint); } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -333,7 +327,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -343,15 +337,14 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion LinearRgbToCieXyzConverter converter = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace); CieXyz unadapted = converter.Convert(color); - // Adaptation return this.Adapt(unadapted, color.WorkingSpace.WhitePoint); } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -369,18 +362,17 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The public CieXyz ToCieXyz(in Lms color) { - // Conversion return this.cieXyzAndLmsConverter.Convert(color); } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -440,7 +432,6 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieXyz ToCieXyz(in YCbCr color) { - // Conversion var rgb = this.ToRgb(color); return this.ToCieXyz(rgb); @@ -472,7 +463,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// /// The source working space /// The - private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(RgbWorkingSpaceBase workingSpace) + private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(RgbWorkingSpace workingSpace) { if (this.linearRgbToCieXyzConverter?.SourceWorkingSpace.Equals(workingSpace) == true) { @@ -482,4 +473,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion 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 index b798516359..5fa5ec8b13 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -17,7 +15,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private static readonly CmykAndRgbConverter CmykAndRgbConverter = new CmykAndRgbConverter(); /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -29,7 +27,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -50,7 +48,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -62,7 +60,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -149,7 +147,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -161,7 +159,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -182,7 +180,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -215,7 +213,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -227,7 +225,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -248,7 +246,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -260,7 +258,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -281,7 +279,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -293,7 +291,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -314,7 +312,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -326,7 +324,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -347,7 +345,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -359,9 +357,9 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors + /// The span to the source colors, /// The span to the destination colors public void Convert(ReadOnlySpan source, Span destination) { @@ -380,7 +378,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -408,7 +406,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -420,7 +418,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -440,4 +438,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs index a7080b9749..f880767069 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -17,7 +15,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private static readonly HslAndRgbConverter HslAndRgbConverter = new HslAndRgbConverter(); /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -29,7 +27,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -50,7 +48,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -62,7 +60,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -83,7 +81,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -95,7 +93,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -116,7 +114,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -128,7 +126,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -149,7 +147,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -281,7 +279,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -293,7 +291,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -314,7 +312,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -326,7 +324,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -347,7 +345,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -359,7 +357,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -387,7 +385,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public Hsl ToHsl(in Rgb color) => HslAndRgbConverter.Convert(color); /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -408,7 +406,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -420,7 +418,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -440,4 +438,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs index a2121203c1..6d784575c6 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -263,7 +261,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// Performs the bulk conversion from into /// /// The span to the source colors - /// The span to the destination colors + /// The span to the destination colors. public void Convert(ReadOnlySpan source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); @@ -440,4 +438,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs index e5996c238e..17cbcbbd5e 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,10 +13,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public partial class ColorSpaceConverter { /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -34,10 +34,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -97,10 +97,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -118,10 +118,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -160,10 +160,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors - /// The span to the destination colors + /// The span to the destination colors. public void Convert(ReadOnlySpan source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -265,7 +265,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -286,7 +286,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -297,7 +297,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -308,7 +308,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -319,7 +319,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -330,7 +330,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -341,21 +341,19 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The public HunterLab ToHunterLab(in CieXyz color) { - // Adaptation CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetHunterLabWhitePoint); - // Conversion return this.cieXyzToHunterLabConverter.Convert(adapted); } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -366,7 +364,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -377,7 +375,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -388,7 +386,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -399,7 +397,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -421,7 +419,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs index eef626be2f..7156ac82fa 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -17,10 +15,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private static readonly RgbToLinearRgbConverter RgbToLinearRgbConverter = new RgbToLinearRgbConverter(); /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -38,10 +36,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -59,10 +57,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -80,10 +78,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -101,10 +99,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -122,10 +120,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -143,10 +141,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -164,10 +162,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -185,10 +183,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -206,10 +204,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -227,10 +225,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -248,7 +246,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -269,7 +267,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -290,7 +288,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -301,7 +299,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -312,7 +310,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -323,7 +321,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -345,7 +343,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -359,7 +357,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -370,7 +368,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -381,7 +379,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -392,7 +390,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -403,7 +401,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -425,7 +423,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -435,4 +433,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion 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 index 3b8638f7d2..cb59074240 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs index fc5665e5c1..ce09b1148f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -438,4 +436,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion 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 index 5780f4f545..126f1eb21c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -17,10 +15,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private static readonly YCbCrAndRgbConverter YCbCrAndRgbConverter = new YCbCrAndRgbConverter(); /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -38,10 +36,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// - /// The span to the source colors - /// The span to the destination colors + /// 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)); @@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -101,7 +99,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -122,7 +120,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -143,7 +141,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -164,7 +162,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -185,7 +183,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -206,7 +204,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -227,7 +225,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -248,7 +246,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -269,7 +267,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -281,7 +279,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -293,7 +291,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -305,7 +303,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -317,7 +315,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -329,7 +327,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -341,7 +339,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -353,7 +351,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -365,7 +363,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -377,7 +375,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -389,7 +387,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -401,10 +399,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// 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 index fe6a57f7ac..f565e6aa69 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { @@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private readonly CieXyz targetLuvWhitePoint; private readonly CieXyz targetLabWhitePoint; private readonly CieXyz targetHunterLabWhitePoint; - private readonly RgbWorkingSpaceBase targetRgbWorkingSpace; + private readonly RgbWorkingSpace targetRgbWorkingSpace; private readonly IChromaticAdaptation chromaticAdaptation; private readonly bool performChromaticAdaptation; private readonly CieXyzAndLmsConverter cieXyzAndLmsConverter; @@ -58,4 +57,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion 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 index fcd031e263..e91c83624e 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { @@ -40,7 +39,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// 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; + public RgbWorkingSpace TargetRgbWorkingSpace { get; set; } = Rgb.DefaultWorkingSpace; /// /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed. @@ -52,4 +51,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// 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 deleted file mode 100644 index 69877d8b55..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs new file mode 100644 index 0000000000..36dd2445e1 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +// ReSharper disable CompareOfFloatsByEqualityOperator +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// 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); + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs index 40d8c5bc69..2b60b28614 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs index 2b859205a0..25542f5598 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . @@ -38,4 +38,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index dfbbc8f0c7..31c3f46330 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . @@ -41,4 +41,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index ba5b8bfb79..052db0e77b 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . @@ -30,4 +30,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index 3c7d356a5e..13644b0922 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . @@ -38,4 +38,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index 33f3ec3d3e..7f15fc77d8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs index f33d1ddcc9..2e048031b8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs @@ -1,13 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// - /// Color converter between CIE XYZ and CIE xyY + /// Color converter between CIE XYZ and CIE xyY. /// for formulas. /// internal sealed class CieXyzAndCieXyyConverter diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs index 1cd511e819..761558676f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// The base class for converting between and color spaces. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs index f860652b18..0a6ba15feb 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs index c155087ff5..7a9016261d 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs index 7f2bb0cf6a..45e7589cea 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs index f21235d06c..2bf1bb7205 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs index 3812cdbdd8..b14705a2d1 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and @@ -25,27 +25,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// Initializes a new instance of the class. /// /// The target working space. - public CieXyzToLinearRgbConverter(RgbWorkingSpaceBase workingSpace) + public CieXyzToLinearRgbConverter(RgbWorkingSpace workingSpace) { this.TargetWorkingSpace = workingSpace; - this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); + + // Gets the inverted Rgb -> Xyz matrix + Matrix4x4.Invert(GetRgbToCieXyzMatrix(workingSpace), out Matrix4x4 inverted); + + this.conversionMatrix = inverted; } /// - /// Gets the target working space + /// Gets the target working space. /// - public RgbWorkingSpaceBase TargetWorkingSpace { get; } + public RgbWorkingSpace TargetWorkingSpace { get; } /// /// Performs the conversion from the input to an instance of type. /// /// The input color instance. - /// The converted result + /// The converted result. [MethodImpl(InliningOptions.ShortMethod)] public LinearRgb Convert(in CieXyz input) { - Matrix4x4.Invert(this.conversionMatrix, out Matrix4x4 inverted); - var vector = Vector3.Transform(input.ToVector3(), inverted); + var vector = Vector3.Transform(input.ToVector3(), this.conversionMatrix); + return new LinearRgb(vector, this.TargetWorkingSpace); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs index 29fd32905b..38c03ca18e 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs @@ -1,14 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// - /// Color converter between and + /// Color converter between and . /// internal sealed class CmykAndRgbConverter { @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// Performs the conversion from the input to an instance of type. /// /// The input color instance. - /// The converted result + /// The converted result. [MethodImpl(InliningOptions.ShortMethod)] public Cmyk Convert(in Rgb input) { diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs index 761313b7e0..d0e0da756c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between HSL and Rgb @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation } else { - s = chroma / (2F - chroma); + s = chroma / (2F - max - min); } return new Hsl(h, s, l); @@ -157,4 +157,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index 20ada7e7dd..f005e025a1 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between HSV and Rgb @@ -127,4 +127,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index 4d6808e6c0..4c3cdba224 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and @@ -36,4 +36,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index a93773262c..556334b4dc 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Provides base methods for converting between and color spaces. @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// /// The Rgb working space. /// The based on the chromaticity and working space. - public static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpaceBase workingSpace) + public static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpace workingSpace) { DebugGuard.NotNull(workingSpace, nameof(workingSpace)); RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; @@ -74,4 +74,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation }; } } -} \ 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 index 1030ac9819..c52a91e6f0 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// Initializes a new instance of the class. /// /// The target working space. - public LinearRgbToCieXyzConverter(RgbWorkingSpaceBase workingSpace) + public LinearRgbToCieXyzConverter(RgbWorkingSpace workingSpace) { this.SourceWorkingSpace = workingSpace; this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// /// Gets the source working space /// - public RgbWorkingSpaceBase SourceWorkingSpace { get; } + public RgbWorkingSpace SourceWorkingSpace { get; } /// /// Performs the conversion from the input to an instance of type. @@ -50,4 +50,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index 1cc055bee2..7e9e3210a8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs @@ -1,12 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// - /// Color converter between and + /// Color converter between and . /// internal sealed class LinearRgbToRgbConverter { @@ -14,16 +14,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// Performs the conversion from the input to an instance of type. /// /// The input color instance. - /// The converted result + /// The converted result. [MethodImpl(InliningOptions.ShortMethod)] public Rgb Convert(in LinearRgb input) { - var vector = input.ToVector3(); - vector.X = input.WorkingSpace.Compress(vector.X); - vector.Y = input.WorkingSpace.Compress(vector.Y); - vector.Z = input.WorkingSpace.Compress(vector.Z); - - return new Rgb(vector, input.WorkingSpace); + 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 index 03912a421e..056f896088 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs @@ -1,12 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// - /// Color converter between Rgb and LinearRgb + /// Color converter between Rgb and LinearRgb. /// internal class RgbToLinearRgbConverter { @@ -14,16 +14,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// Performs the conversion from the input to an instance of type. /// /// The input color instance. - /// The converted result + /// The converted result. [MethodImpl(InliningOptions.ShortMethod)] public LinearRgb Convert(in Rgb input) { - var vector = input.ToVector3(); - vector.X = input.WorkingSpace.Expand(vector.X); - vector.Y = input.WorkingSpace.Expand(vector.Y); - vector.Z = input.WorkingSpace.Expand(vector.Z); - - return new LinearRgb(vector, input.WorkingSpace); + 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 index 4ac3ad3cf9..0ae244848e 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// Performs the conversion from the input to an instance of type. /// /// The input color instance. - /// The converted result + /// The converted result. [MethodImpl(InliningOptions.ShortMethod)] public Rgb Convert(in YCbCr input) { @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// Performs the conversion from the input to an instance of type. /// /// The input color instance. - /// The converted result + /// The converted result. [MethodImpl(InliningOptions.ShortMethod)] public YCbCr Convert(in Rgb input) { diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs new file mode 100644 index 0000000000..62833475d8 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// 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/LmsAdaptationMatrix.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs index 37e4b1a1a6..f69868760a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Matrices used for transformation from to , defining the cone response domain. @@ -131,4 +131,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index 4c69133e0f..2a03b54e7f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs @@ -1,22 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Represents the chromaticity coordinates of RGB primaries. - /// One of the specifiers of . + /// 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. + /// 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; @@ -25,17 +25,17 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation } /// - /// Gets the chomaticity coordinates of the red channel. + /// Gets the chromaticity coordinates of the red channel. /// public CieXyChromaticityCoordinates R { get; } /// - /// Gets the chomaticity coordinates of the green channel. + /// Gets the chromaticity coordinates of the green channel. /// public CieXyChromaticityCoordinates G { get; } /// - /// Gets the chomaticity coordinates of the blue channel. + /// Gets the chromaticity coordinates of the blue channel. /// public CieXyChromaticityCoordinates B { get; } @@ -88,4 +88,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// 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/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs new file mode 100644 index 0000000000..6dec4d735a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// 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.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); + } + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs index 639d0b2933..f9b268307c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs @@ -1,16 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// The gamma working space. /// - public class GammaWorkingSpace : RgbWorkingSpaceBase + public sealed class GammaWorkingSpace : RgbWorkingSpace { /// /// Initializes a new instance of the class. @@ -63,4 +63,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation 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 index 16617ea242..7d42759aee 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs @@ -1,15 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// L* working space. /// - public sealed class LWorkingSpace : RgbWorkingSpaceBase + public sealed class LWorkingSpace : RgbWorkingSpace { /// /// Initializes a new instance of the class. @@ -29,4 +29,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation [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 index 9ba1ff8811..d57936e354 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs @@ -1,15 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. /// - public sealed class Rec2020WorkingSpace : RgbWorkingSpaceBase + public sealed class Rec2020WorkingSpace : RgbWorkingSpace { /// /// Initializes a new instance of the class. @@ -29,4 +29,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation [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 index 88623e958d..5d556fa0db 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs @@ -1,15 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Rec. 709 (ITU-R Recommendation BT.709) working space. /// - public sealed class Rec709WorkingSpace : RgbWorkingSpaceBase + public sealed class Rec709WorkingSpace : RgbWorkingSpace { /// /// Initializes a new instance of the class. @@ -29,4 +29,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation [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/RgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs new file mode 100644 index 0000000000..996b72094f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// 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; } + + /// + /// 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 RgbWorkingSpace 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); + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs deleted file mode 100644 index 6051f52865..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs +++ /dev/null @@ -1,84 +0,0 @@ -// 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 index b44db06817..8140d24dba 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs @@ -1,15 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// The sRgb working space. /// - public sealed class SRgbWorkingSpace : RgbWorkingSpaceBase + public sealed class SRgbWorkingSpace : RgbWorkingSpace { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs deleted file mode 100644 index 85a36331b2..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs +++ /dev/null @@ -1,102 +0,0 @@ -// 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 -{ - /// - /// Basic implementation of the von Kries chromatic adaptation model - /// - /// - /// Transformation described here: - /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html - /// - public 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 index 04b3bea41f..9df5b46564 100644 --- a/src/ImageSharp/ColorSpaces/Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Hsl.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/ColorSpaces/Hsv.cs b/src/ImageSharp/ColorSpaces/Hsv.cs index 8ccc74ae09..40474621a6 100644 --- a/src/ImageSharp/ColorSpaces/Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Hsv.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/ColorSpaces/HunterLab.cs b/src/ImageSharp/ColorSpaces/HunterLab.cs index dcae65e425..4a0acadf4b 100644 --- a/src/ImageSharp/ColorSpaces/HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/HunterLab.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.ColorSpaces { /// /// Represents an Hunter LAB color. - /// + /// . /// public readonly struct HunterLab : IEquatable { @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.ColorSpaces public readonly float B; /// - /// Gets the reference white point of this color + /// Gets the reference white point of this color. /// public readonly CieXyz WhitePoint; @@ -117,10 +117,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() - { - return HashCode.Combine(this.L, this.A, this.B, this.WhitePoint); - } + 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.##})"); diff --git a/src/ImageSharp/ColorSpaces/Illuminants.cs b/src/ImageSharp/ColorSpaces/Illuminants.cs index ed385e02cd..11b66d43bb 100644 --- a/src/ImageSharp/ColorSpaces/Illuminants.cs +++ b/src/ImageSharp/ColorSpaces/Illuminants.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.ColorSpaces @@ -8,11 +8,9 @@ namespace SixLabors.ImageSharp.ColorSpaces /// 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 + /// Coefficients taken from: http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html ///
- /// Descriptions taken from: - /// http://en.wikipedia.org/wiki/Standard_illuminant + /// Descriptions taken from: http://en.wikipedia.org/wiki/Standard_illuminant ///
public static class Illuminants { diff --git a/src/ImageSharp/ColorSpaces/LinearRgb.cs b/src/ImageSharp/ColorSpaces/LinearRgb.cs index 46b2275968..245dbbd0f1 100644 --- a/src/ImageSharp/ColorSpaces/LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs @@ -1,15 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.ColorSpaces.Conversion; namespace SixLabors.ImageSharp.ColorSpaces { /// - /// Represents an linear Rgb color with specified working space + /// Represents an linear Rgb color with specified working space /// public readonly struct LinearRgb : IEquatable { @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The default LinearRgb working space. /// - public static readonly RgbWorkingSpaceBase DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + public static readonly RgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; /// /// Gets the red component. @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Gets the LinearRgb color space /// - public readonly RgbWorkingSpaceBase WorkingSpace; + public readonly RgbWorkingSpace WorkingSpace; /// /// Initializes a new instance of the struct. @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// 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) + public LinearRgb(float r, float g, float b, RgbWorkingSpace workingSpace) : this(new Vector3(r, g, b), workingSpace) { } @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The vector representing the r, g, b components. /// The LinearRgb working space. [MethodImpl(InliningOptions.ShortMethod)] - public LinearRgb(Vector3 vector, RgbWorkingSpaceBase workingSpace) + public LinearRgb(Vector3 vector, RgbWorkingSpace workingSpace) { // Clamp to 0-1 range. vector = Vector3.Clamp(vector, Min, Max); @@ -143,4 +143,4 @@ namespace SixLabors.ImageSharp.ColorSpaces && 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 index 0ee56abbc2..fa68003431 100644 --- a/src/ImageSharp/ColorSpaces/Lms.cs +++ b/src/ImageSharp/ColorSpaces/Lms.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs index cdc9e90ad4..900f71b2c4 100644 --- a/src/ImageSharp/ColorSpaces/Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Rgb.cs @@ -1,26 +1,26 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.ColorSpaces.Conversion; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.ColorSpaces { /// - /// Represents an RGB color with specified working space. + /// Represents an RGB color with specified working space. /// public readonly struct Rgb : IEquatable { - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = Vector3.One; - /// - /// The default rgb working space + /// The default rgb working space. /// - public static readonly RgbWorkingSpaceBase DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + public static readonly RgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = Vector3.One; /// /// Gets the red component. @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Gets the Rgb color space /// - public readonly RgbWorkingSpaceBase WorkingSpace; + public readonly RgbWorkingSpace WorkingSpace; /// /// Initializes a new instance of the struct. @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// 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) + public Rgb(float r, float g, float b, RgbWorkingSpace workingSpace) : this(new Vector3(r, g, b), workingSpace) { } @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The vector representing the r, g, b components. /// The rgb working space. [MethodImpl(InliningOptions.ShortMethod)] - public Rgb(Vector3 vector, RgbWorkingSpaceBase workingSpace) + public Rgb(Vector3 vector, RgbWorkingSpace workingSpace) { vector = Vector3.Clamp(vector, Min, Max); this.R = vector.X; @@ -164,4 +164,4 @@ namespace SixLabors.ImageSharp.ColorSpaces && 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 index ee3822c152..07f76e58c7 100644 --- a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs +++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs @@ -1,15 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.ColorSpaces.Companding; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using SixLabors.ImageSharp.ColorSpaces.Conversion; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.ColorSpaces { /// - /// Chromaticity coordinates taken from: - /// + /// Chromaticity coordinates based on: /// public static class RgbWorkingSpaces { @@ -20,97 +19,97 @@ namespace SixLabors.ImageSharp.ColorSpaces /// 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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))); + public static readonly RgbWorkingSpace 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 index b0563bb899..eaaf7f58f4 100644 --- a/src/ImageSharp/ColorSpaces/YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/YCbCr.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Common/Constants.cs b/src/ImageSharp/Common/Constants.cs index a8a693fa67..fd26361003 100644 --- a/src/ImageSharp/Common/Constants.cs +++ b/src/ImageSharp/Common/Constants.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 89877b1b66..6a54ce5485 100644 --- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -7,16 +7,16 @@ namespace SixLabors.ImageSharp { /// /// The exception that is thrown when the library tries to load - /// an image, which has an invalid format. + /// an image, which has format or content that is invalid or unsupported by ImageSharp. /// - public sealed class ImageFormatException : Exception + public 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) + internal ImageFormatException(string errorMessage) : base(errorMessage) { } @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp /// 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) + internal 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 3c75a6418a..dc4f4f1088 100644 --- a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp /// public sealed class ImageProcessingException : Exception { + /// + /// Initializes a new instance of the class. + /// + public ImageProcessingException() + { + } + /// /// Initializes a new instance of the class with the name of the /// parameter that causes this exception. @@ -32,4 +39,4 @@ namespace SixLabors.ImageSharp { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs new file mode 100644 index 0000000000..e4713e237c --- /dev/null +++ b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 new file mode 100644 index 0000000000..f3a1ea0f57 --- /dev/null +++ b/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 index 3c8570a2a4..ef3d1deac3 100644 --- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp /// /// Restricts a to be within a specified range. /// - /// The The value to clamp. + /// 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. /// @@ -137,4 +137,4 @@ namespace SixLabors.ImageSharp 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 6bb5adc060..9bf0a1fbe6 100644 --- a/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Threading.Tasks; @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp /// public static ParallelOptions GetParallelOptions(this Configuration configuration) { - return new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }; + 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 index 59c878485d..caef4ac928 100644 --- a/src/ImageSharp/Common/Extensions/EncoderExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EncoderExtensions.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -#if !NETCOREAPP2_1 +#if !SUPPORTS_ENCODING_STRING using System; using System.Text; @@ -32,4 +32,4 @@ namespace SixLabors.ImageSharp } } } -#endif \ No newline at end of file +#endif diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs index e97a2eaed0..c6560f8c3b 100644 --- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs @@ -1,43 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Common +namespace SixLabors.ImageSharp { /// /// Encapsulates a series of time saving extension methods to the interface. /// internal static class EnumerableExtensions { - /// - /// 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)); - } - - return RangeIterator(fromInclusive, i => i < toExclusive, step); - } - /// /// Generates a sequence of integral numbers within a specified range. /// @@ -45,7 +18,7 @@ namespace SixLabors.ImageSharp.Common /// The start index, inclusive. /// /// - /// A method that has one parameter and returns a calculating the end index + /// A method that has one parameter and returns a calculating the end index. /// /// /// The incremental step. @@ -61,15 +34,11 @@ namespace SixLabors.ImageSharp.Common /// /// Generates a sequence of integral numbers within a specified range. /// - /// - /// The start index, inclusive. - /// + /// The start index, inclusive. /// - /// A method that has one parameter and returns a calculating the end index - /// - /// - /// The incremental step. + /// 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. /// @@ -83,4 +52,4 @@ namespace SixLabors.ImageSharp.Common } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index a200ffebcc..f2367d488a 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -1,11 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; - using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; namespace SixLabors.ImageSharp { @@ -14,7 +13,6 @@ namespace SixLabors.ImageSharp /// internal static class StreamExtensions { -#if NETCOREAPP2_1 /// /// Writes data from a stream into the provided buffer. /// @@ -23,29 +21,24 @@ namespace SixLabors.ImageSharp /// 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)); - } + => stream.Write(buffer.Slice(offset, count)); /// /// Reads data from a stream into the provided buffer. /// /// The stream. - /// The buffer.. + /// 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) - { - return stream.Read(buffer.Slice(offset, count)); - } -#endif + => stream.Read(buffer.Slice(offset, count)); /// /// Skips the number of bytes in the given stream. /// /// The stream. - /// The count. + /// A byte offset relative to the origin parameter. public static void Skip(this Stream stream, int count) { if (count < 1) @@ -55,14 +48,16 @@ namespace SixLabors.ImageSharp if (stream.CanSeek) { - stream.Seek(count, SeekOrigin.Current); // Position += count; + stream.Seek(count, SeekOrigin.Current); + return; } - else + + byte[] buffer = ArrayPool.Shared.Rent(count); + try { - byte[] foo = new byte[count]; while (count > 0) { - int bytesRead = stream.Read(foo, 0, count); + int bytesRead = stream.Read(buffer, 0, count); if (bytesRead == 0) { break; @@ -71,16 +66,62 @@ namespace SixLabors.ImageSharp count -= bytesRead; } } + finally + { + ArrayPool.Shared.Return(buffer); + } } public static void Read(this Stream stream, IManagedByteBuffer buffer) + => stream.Read(buffer.Array, 0, buffer.Length()); + + public static void Write(this Stream stream, IManagedByteBuffer buffer) + => stream.Write(buffer.Array, 0, buffer.Length()); + +#if !SUPPORTS_SPAN_STREAM + // This is a port of the CoreFX implementation and is MIT Licensed: + // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 + public static int Read(this Stream stream, Span buffer) { - stream.Read(buffer.Array, 0, buffer.Length()); + // 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 + { + int numRead = stream.Read(sharedBuffer, 0, buffer.Length); + if ((uint)numRead > (uint)buffer.Length) + { + throw new IOException("Stream was too long."); + } + + new Span(sharedBuffer, 0, numRead).CopyTo(buffer); + return numRead; + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } } - public static void Write(this Stream stream, IManagedByteBuffer buffer) + // This is a port of the CoreFX implementation and is MIT Licensed: + // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L775 + public static void Write(this Stream stream, ReadOnlySpan buffer) { - stream.Write(buffer.Array, 0, buffer.Length()); + // 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/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs new file mode 100644 index 0000000000..f4811d6ca8 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs @@ -0,0 +1,109 @@ +// Copyright (c) Six Labors. +// 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; + +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 Buffer2DUtils + { + /// + /// Computes the sum of vectors in weighted by the kernel weight values. + /// + /// The pixel format. + /// The 1D convolution kernel. + /// The source frame. + /// The target row. + /// 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. + public static void Convolve4( + Span kernel, + Buffer2D sourcePixels, + Span targetRow, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn) + where TPixel : unmanaged, IPixel + { + ComplexVector4 vector = default; + int kernelLength = kernel.Length; + int radiusY = kernelLength >> 1; + int sourceOffsetColumnBase = column + minColumn; + ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel); + + for (int i = 0; i < kernelLength; i++) + { + int offsetY = (row + i - radiusY).Clamp(minRow, maxRow); + int offsetX = sourceOffsetColumnBase.Clamp(minColumn, maxColumn); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + + vector.Sum(Unsafe.Add(ref baseRef, i) * currentColor); + } + + targetRow[column] = vector; + } + + /// + /// Computes the sum of vectors in weighted by the kernel weight values and accumulates the partial results. + /// + /// The 1D convolution kernel. + /// The source frame. + /// The target row. + /// 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. + /// The weight factor for the real component of the complex pixel values. + /// The weight factor for the imaginary component of the complex pixel values. + public static void Convolve4AndAccumulatePartials( + Span kernel, + Buffer2D sourceValues, + Span targetRow, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn, + float z, + float w) + { + ComplexVector4 vector = default; + int kernelLength = kernel.Length; + int radiusX = kernelLength >> 1; + int sourceOffsetColumnBase = column + minColumn; + + int offsetY = row.Clamp(minRow, maxRow); + ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpan(offsetY)); + ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel); + + for (int x = 0; x < kernelLength; x++) + { + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); + vector.Sum(Unsafe.Add(ref baseRef, x) * Unsafe.Add(ref sourceRef, offsetX)); + } + + targetRow[column] += vector.WeightedSum(z, w); + } + } +} diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 43eebeac87..9ef7c01c61 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -1,168 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Diagnostics; // TODO: These should just call the guard equivalents -namespace SixLabors.ImageSharp +namespace SixLabors { /// /// Provides methods to protect against invalid parameters for a DEBUG build. /// - [DebuggerStepThrough] - internal static class DebugGuard + internal static partial class DebugGuard { - /// - /// 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 - { - if (value is null) - { - throw new ArgumentNullException(parameterName); - } - } - - /// - /// 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 - { - if (value.CompareTo(max) >= 0) - { - throw new ArgumentOutOfRangeException(parameterName, $"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. - /// - [Conditional("DEBUG")] - public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) - where TValue : IComparable - { - if (value.CompareTo(max) > 0) - { - throw new ArgumentOutOfRangeException(parameterName, $"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. - /// - [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. /// @@ -177,25 +26,6 @@ namespace SixLabors.ImageSharp } } - /// - /// 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. /// @@ -236,4 +66,4 @@ namespace SixLabors.ImageSharp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 2e700c9d67..61f90e23e1 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -1,94 +1,127 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -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 weighted by the kernel weight values. + /// 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 dense matrix. + /// The vertical dense matrix. + /// The horizontal dense matrix. /// The source frame. - /// The target row. + /// 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. - /// The column offset to apply to source sampling. - public static void Convolve( - in DenseMatrix matrix, + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve2D3( + in DenseMatrix matrixY, + in DenseMatrix matrixX, Buffer2D sourcePixels, - Span targetRow, + ref Vector4 targetRowRef, int row, int column, + int minRow, int maxRow, - int maxColumn, - int offsetColumn) - where TPixel : struct, IPixel + int minColumn, + int maxColumn) + where TPixel : unmanaged, IPixel { - Vector4 vector = default; - int matrixHeight = matrix.Rows; - int matrixWidth = matrix.Columns; - int radiusY = matrixHeight >> 1; - int radiusX = matrixWidth >> 1; - int sourceOffsetColumnBase = column + offsetColumn; - - for (int y = 0; y < matrixHeight; y++) - { - int offsetY = (row + y - radiusY).Clamp(0, maxRow); - Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); - - for (int x = 0; x < matrixWidth; x++) - { - int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn); - var currentColor = sourceRowSpan[offsetX].ToVector4(); - Vector4Utils.Premultiply(ref currentColor); + Convolve2DImpl( + in matrixY, + in matrixX, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + out Vector4 vector); - vector += matrix[y, x] * currentColor; - } - } - - ref Vector4 target = ref targetRow[column]; + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; - Vector4Utils.UnPremultiply(ref vector); + + Vector4Utilities.UnPremultiply(ref vector); target = vector; } /// - /// Computes the sum of vectors in weighted by the two kernel weight values. + /// 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. + /// 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. - /// The column offset to apply to source sampling. - public static void Convolve2D( + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve2D4( in DenseMatrix matrixY, in DenseMatrix matrixX, Buffer2D sourcePixels, - Span targetRow, + ref Vector4 targetRowRef, int row, int column, + int minRow, int maxRow, + int minColumn, + int maxColumn) + where TPixel : unmanaged, IPixel + { + Convolve2DImpl( + in matrixY, + in matrixX, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + out Vector4 vector); + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); + Vector4Utilities.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, - int offsetColumn) - where TPixel : struct, IPixel + out Vector4 vector) + where TPixel : unmanaged, IPixel { Vector4 vectorY = default; Vector4 vectorX = default; @@ -96,29 +129,151 @@ namespace SixLabors.ImageSharp int matrixWidth = matrixY.Columns; int radiusY = matrixHeight >> 1; int radiusX = matrixWidth >> 1; - int sourceOffsetColumnBase = column + offsetColumn; + int sourceOffsetColumnBase = column + minColumn; for (int y = 0; y < matrixHeight; y++) { - int offsetY = (row + y - radiusY).Clamp(0, maxRow); + 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(offsetColumn, maxColumn); + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); var currentColor = sourceRowSpan[offsetX].ToVector4(); - Vector4Utils.Premultiply(ref currentColor); + Vector4Utilities.Premultiply(ref currentColor); vectorX += matrixX[y, x] * currentColor; vectorY += matrixY[y, x] * currentColor; } } - var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - ref Vector4 target = ref targetRow[column]; + 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 : unmanaged, 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); + + Vector4Utilities.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 : unmanaged, 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); + Vector4Utilities.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 : unmanaged, 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(); + Vector4Utilities.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 new file mode 100644 index 0000000000..089aba337c --- /dev/null +++ b/src/ImageSharp/Common/Helpers/EnumUtils.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 : Enum + { + foreach (TEnum enumValue in Enum.GetValues(typeof(TEnum))) + { + TEnum current = enumValue; + if (value == Unsafe.As(ref current)) + { + return enumValue; + } + } + + 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 : Enum + { + uint flagValue = Unsafe.As(ref flag); + return (Unsafe.As(ref value) & flagValue) == flagValue; + } + } +} diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index d8cf69a52e..751920683e 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -1,297 +1,29 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.Reflection; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +namespace SixLabors { - /// - /// Provides methods to protect against invalid parameters. - /// - [DebuggerStepThrough] - internal static class Guard + internal static partial class Guard { /// - /// Ensures that the value is not null. + /// Ensures that the value is a value type. /// /// 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) - { - if (!target) - { - ThrowArgumentException(message, 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' + /// is not a value type. [MethodImpl(InliningOptions.ShortMethod)] - public static void DestinationShouldNotBeTooShort( - ReadOnlySpan source, - Span destination, - string destinationParamName) + public static void MustBeValueType(TValue value, string parameterName) { - if (destination.Length < source.Length) + if (!value.GetType().GetTypeInfo().IsValueType) { - ThrowArgumentException($"Destination span is too short!", destinationParamName); + ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName); } } - - /// - /// 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/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 0c5b051809..977432f8bb 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -1,12 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; namespace SixLabors.ImageSharp { @@ -15,6 +14,20 @@ namespace SixLabors.ImageSharp /// internal static class ImageMaths { + /// + /// Vector for converting pixel to gray value as specified by ITU-R Recommendation BT.709. + /// + private static readonly Vector4 Bt709 = new Vector4(.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(InliningOptions.ShortMethod)] + 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. /// @@ -24,7 +37,7 @@ namespace SixLabors.ImageSharp /// The . [MethodImpl(InliningOptions.ShortMethod)] public static byte Get8BitBT709Luminance(byte r, byte g, byte b) => - (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5f); + (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. @@ -35,7 +48,18 @@ namespace SixLabors.ImageSharp /// The . [MethodImpl(InliningOptions.ShortMethod)] public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) => - (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F)); + (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(InliningOptions.ShortMethod)] + 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 it's 8 bit equivalent. @@ -218,40 +242,6 @@ namespace SixLabors.ImageSharp 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. /// @@ -279,7 +269,7 @@ namespace SixLabors.ImageSharp /// The . /// public static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int width = bitmap.Width; int height = bitmap.Height; @@ -369,7 +359,7 @@ namespace SixLabors.ImageSharp } } - return height; + return width; } topLeft.Y = GetMinY(bitmap); @@ -380,4 +370,4 @@ namespace SixLabors.ImageSharp 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 069a426d75..4bc8ef3c87 100644 --- a/src/ImageSharp/Common/Helpers/InliningOptions.cs +++ b/src/ImageSharp/Common/Helpers/InliningOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // Uncomment this for verbose profiler results. DO NOT PUSH TO MAIN! @@ -13,10 +13,16 @@ namespace SixLabors.ImageSharp internal static class InliningOptions { #if PROFILING + public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; #else +#if SUPPORTS_HOTPATH + public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization; +#else + public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining; +#endif public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; #endif public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs new file mode 100644 index 0000000000..b56c92dab7 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +#if SUPPORTS_RUNTIME_INTRINSICS + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp +{ + internal static partial class SimdUtils + { + public static class Avx2Intrinsics + { + private static ReadOnlySpan PermuteMaskDeinterleave8x32 => new byte[] { 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 }; + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void NormalizedFloatToByteSaturateReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + if (Avx2.IsSupported) + { + int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + NormalizedFloatToByteSaturate( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + } + + /// + /// Implementation of , which is faster on new .NET runtime. + /// + /// + /// Implementation is based on MagicScaler code: + /// https://github.com/saucecontrol/PhotoSauce/blob/a9bd6e5162d2160419f0cf743fd4f536c079170b/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L453-L477 + /// + internal static void NormalizedFloatToByteSaturate( + ReadOnlySpan source, + Span dest) + { + VerifySpanInput(source, dest, Vector256.Count); + + int n = dest.Length / Vector256.Count; + + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + + var maxBytes = Vector256.Create(255f); + ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32); + Vector256 mask = Unsafe.As>(ref maskBase); + + for (int 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); + + Vector256 w0 = ConvertToInt32(f0, maxBytes); + Vector256 w1 = ConvertToInt32(f1, maxBytes); + Vector256 w2 = ConvertToInt32(f2, maxBytes); + Vector256 w3 = ConvertToInt32(f3, maxBytes); + + 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; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale) + { + vf = Avx.Multiply(vf, scale); + return Avx.ConvertToVector256Int32(vf); + } + } + } +} +#endif diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs index 85c9f00748..de6990db5b 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -18,13 +17,14 @@ namespace SixLabors.ImageSharp /// public static class BasicIntrinsics256 { - public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture; + public static bool IsAvailable { get; } = HasVector8; +#if !SUPPORTS_EXTENDED_INTRINSICS /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat( + ByteToNormalizedFloat( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -69,23 +69,24 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + NormalizedFloatToByteSaturate(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); source = source.Slice(adjustedCount); dest = dest.Slice(adjustedCount); } } +#endif /// - /// SIMD optimized implementation for . + /// 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) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertByteToNormalizedFloat)); + VerifyHasVector8(nameof(ByteToNormalizedFloat)); VerifySpanInput(source, dest, 8); var bVec = new Vector(256.0f / 255.0f); @@ -93,17 +94,17 @@ namespace SixLabors.ImageSharp 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 Octet sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Octet destBaseAsWideOctet = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - ref Vector destBaseAsFloat = ref Unsafe.As>(ref destBaseAsWideOctet); + ref Vector destBaseAsFloat = ref Unsafe.As, Vector>(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); + ref Octet s = ref Unsafe.Add(ref sourceBase, i); + ref Octet d = ref Unsafe.Add(ref destBaseAsWideOctet, i); d.LoadFrom(ref s); } @@ -123,11 +124,11 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of which is faster on older runtimes. + /// Implementation of which is faster on older runtimes. /// - internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) + internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByteClampOverflows)); + VerifyHasVector8(nameof(NormalizedFloatToByteSaturate)); VerifySpanInput(source, dest, 8); if (source.Length == 0) @@ -136,17 +137,17 @@ namespace SixLabors.ImageSharp } ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref Octet 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); + var magick = new Vector(32768.0f); + var 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) + // SimdUtils.Octet temp = Unsafe.As, SimdUtils.Octet>(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); + var temp = default(Octet); + ref Vector tempRef = ref Unsafe.As, Vector>(ref temp); for (int i = 0; i < n; i++) { @@ -160,7 +161,7 @@ namespace SixLabors.ImageSharp x = (x * scale) + magick; tempRef = x; - ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i); + ref Octet d = ref Unsafe.Add(ref destBase, i); d.LoadFrom(ref temp); } } @@ -176,7 +177,7 @@ namespace SixLabors.ImageSharp /// internal static void BulkConvertNormalizedFloatToByte(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByte)); + VerifyHasVector8(nameof(BulkConvertNormalizedFloatToByte)); VerifySpanInput(source, dest, 8); if (source.Length == 0) @@ -185,17 +186,17 @@ namespace SixLabors.ImageSharp } ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref Octet 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); + var magick = new Vector(32768.0f); + var 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) + // SimdUtils.Octet temp = Unsafe.As, SimdUtils.Octet>(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); + var temp = default(Octet); + ref Vector tempRef = ref Unsafe.As, Vector>(ref temp); for (int i = 0; i < n; i++) { @@ -206,10 +207,10 @@ namespace SixLabors.ImageSharp x = (x * scale) + magick; tempRef = x; - ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i); + ref Octet 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.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs index 83216aaa72..bd35d1583e 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + ByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); source = source.Slice(adjustedCount); dest = dest.Slice(adjustedCount); @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows( + NormalizedFloatToByteSaturate( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -99,9 +99,9 @@ namespace SixLabors.ImageSharp } /// - /// Implementation , which is faster on new RyuJIT runtime. + /// Implementation , which is faster on new RyuJIT runtime. /// - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { VerifySpanInput(source, dest, Vector.Count); @@ -132,9 +132,9 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of , which is faster on new .NET runtime. + /// Implementation of , which is faster on new .NET runtime. /// - internal static void BulkConvertNormalizedFloatToByteClampOverflows( + internal static void NormalizedFloatToByteSaturate( ReadOnlySpan source, Span dest) { @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector ConvertToUInt32(Vector vf) { - Vector maxBytes = new Vector(255f); + var maxBytes = new Vector(255f); vf *= maxBytes; vf += new Vector(0.5f); vf = Vector.Min(Vector.Max(vf, Vector.Zero), maxBytes); @@ -190,4 +190,4 @@ namespace SixLabors.ImageSharp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs index 565ea08f5d..1e89aaeb83 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -19,10 +19,10 @@ namespace SixLabors.ImageSharp public static class FallbackIntrinsics128 { /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat( + ByteToNormalizedFloat( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows( + NormalizedFloatToByteSaturate( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -67,10 +67,10 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of using . + /// Implementation of using . /// [MethodImpl(InliningOptions.ColdPath)] - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { VerifySpanInput(source, dest, 4); @@ -99,10 +99,10 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of using . + /// Implementation of using . /// [MethodImpl(InliningOptions.ColdPath)] - internal static void BulkConvertNormalizedFloatToByteClampOverflows( + internal static void NormalizedFloatToByteSaturate( ReadOnlySpan source, Span dest) { @@ -125,10 +125,7 @@ namespace SixLabors.ImageSharp 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); + s = Vector4Utilities.FastClamp(s, Vector4.Zero, maxBytes); ref ByteVector4 d = ref Unsafe.Add(ref dBase, i); d.X = (byte)s.X; @@ -148,4 +145,4 @@ namespace SixLabors.ImageSharp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 867e7b9de1..7f917648dc 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,9 +6,10 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tuples; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp { @@ -18,9 +19,10 @@ namespace SixLabors.ImageSharp internal static partial class SimdUtils { /// - /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte. + /// Gets a value indicating whether code is being JIT-ed to AVX2 instructions + /// where both float and integer registers are of size 256 byte. /// - public static bool IsAvx2CompatibleArchitecture { get; } = + public static bool HasVector8 { get; } = Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8; /// @@ -30,7 +32,7 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static Vector4 PseudoRound(this Vector4 v) { - var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1)); + Vector4 sign = Vector4Utilities.FastClamp(v, new Vector4(-1), new Vector4(1)); return v + (sign * 0.5f); } @@ -46,13 +48,24 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static Vector FastRound(this Vector v) { - 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 SUPPORTS_RUNTIME_INTRINSICS + + if (Avx2.IsSupported) + { + ref Vector256 v256 = ref Unsafe.As, Vector256>(ref v); + Vector256 vRound = Avx.RoundToNearestInteger(v256); + return Unsafe.As, Vector>(ref vRound); + } + else +#endif + { + var magic0 = new Vector(int.MinValue); // 0x80000000 + var sgn0 = Vector.AsVectorSingle(magic0); + var and0 = Vector.BitwiseAnd(sgn0, v); + var or0 = Vector.BitwiseOr(and0, new Vector(8388608.0f)); + var add0 = Vector.Add(v, or0); + return Vector.Subtract(add0, or0); + } } /// @@ -63,16 +76,18 @@ namespace SixLabors.ImageSharp /// The source span of bytes /// The destination span of floats [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { 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); + ExtendedIntrinsics.ByteToNormalizedFloatReduce(ref source, ref dest); #else - BasicIntrinsics256.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + BasicIntrinsics256.ByteToNormalizedFloatReduce(ref source, ref dest); #endif - FallbackIntrinsics128.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + + // Also deals with the remainder from previous conversions: + FallbackIntrinsics128.ByteToNormalizedFloatReduce(ref source, ref dest); // Deal with the remainder: if (source.Length > 0) @@ -90,16 +105,20 @@ namespace SixLabors.ImageSharp /// The source span of floats /// The destination span of bytes [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) + internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest) { 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); +#if SUPPORTS_RUNTIME_INTRINSICS + Avx2Intrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest); +#elif SUPPORTS_EXTENDED_INTRINSICS + ExtendedIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest); #else - BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); + BasicIntrinsics256.NormalizedFloatToByteSaturateReduce(ref source, ref dest); #endif - FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); + + // Also deals with the remainder from previous conversions: + FallbackIntrinsics128.NormalizedFloatToByteSaturateReduce(ref source, ref dest); // Deal with the remainder: if (source.Length > 0) @@ -154,9 +173,9 @@ namespace SixLabors.ImageSharp private static byte ConvertToByte(float f) => (byte)ComparableExtensions.Clamp((f * 255f) + 0.5f, 0, 255f); [Conditional("DEBUG")] - private static void VerifyIsAvx2Compatible(string operation) + private static void VerifyHasVector8(string operation) { - if (!IsAvx2CompatibleArchitecture) + if (!HasVector8) { throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!"); } @@ -182,4 +201,4 @@ namespace SixLabors.ImageSharp $"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 d330233c4c..33aa81f3d5 100644 --- a/src/ImageSharp/Common/Helpers/TestHelpers.cs +++ b/src/ImageSharp/Common/Helpers/TestHelpers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Common.Helpers @@ -13,14 +13,18 @@ namespace SixLabors.ImageSharp.Common.Helpers /// Only intended to be used in tests! /// internal const string ImageSharpBuiltAgainst = -#if NET472 - "netfx4.7.2"; +#if NETCOREAPP3_1 + "netcoreapp3.1"; #elif NETCOREAPP2_1 "netcoreapp2.1"; +#elif NETSTANDARD2_1 + "netstandard2.1"; +#elif NETSTANDARD2_0 + "netstandard2.0"; #elif NETSTANDARD1_3 "netstandard1.3"; #else - "netstandard2.0"; + "net472"; #endif } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/TolerantMath.cs b/src/ImageSharp/Common/Helpers/TolerantMath.cs index bef7eb1610..d1d3f21740 100644 --- a/src/ImageSharp/Common/Helpers/TolerantMath.cs +++ b/src/ImageSharp/Common/Helpers/TolerantMath.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -103,4 +103,4 @@ namespace SixLabors.ImageSharp 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 c8b25bf564..4a6e6abcb6 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.MetaData; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Common.Helpers { @@ -86,9 +86,10 @@ namespace SixLabors.ImageSharp.Common.Helpers [MethodImpl(InliningOptions.ShortMethod)] public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) { - return profile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolution) - ? (PixelResolutionUnit)(byte)(((ushort)resolution.Value) - 1) // EXIF is 1, 2, 3 - : default; + IExifValue resolution = profile.GetValue(ExifTag.ResolutionUnit); + + // EXIF is 1, 2, 3 so we minus "1" off the result. + return resolution is null ? default : (PixelResolutionUnit)(byte)(resolution.Value - 1); } } } diff --git a/src/ImageSharp/Common/Helpers/Vector4Utilities.cs b/src/ImageSharp/Common/Helpers/Vector4Utilities.cs new file mode 100644 index 0000000000..fccc50755d --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Vector4Utilities.cs @@ -0,0 +1,122 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Utility methods for the struct. + /// + internal static class Vector4Utilities + { + /// + /// Restricts a vector between a minimum and a maximum value. + /// 5x Faster then . + /// + /// The vector to restrict. + /// The minimum value. + /// The maximum value. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 FastClamp(Vector4 x, Vector4 min, Vector4 max) + => Vector4.Min(Vector4.Max(x, min), max); + + /// + /// 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); + } + } + } +} diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs deleted file mode 100644 index 5c122217d1..0000000000 --- a/src/ImageSharp/Common/Helpers/Vector4Utils.cs +++ /dev/null @@ -1,111 +0,0 @@ -// 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/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs deleted file mode 100644 index 40163bc789..0000000000 --- a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs +++ /dev/null @@ -1,71 +0,0 @@ -// 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 deleted file mode 100644 index a930b8390f..0000000000 --- a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs +++ /dev/null @@ -1,146 +0,0 @@ -// 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) - { - DebugGuard.MustBeGreaterThan(rectangle.Width, 0, nameof(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 - { - 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); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Tuples/Octet.cs b/src/ImageSharp/Common/Tuples/Octet.cs deleted file mode 100644 index 23faf08ae9..0000000000 --- a/src/ImageSharp/Common/Tuples/Octet.cs +++ /dev/null @@ -1,112 +0,0 @@ -// 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 new file mode 100644 index 0000000000..aaecafd09d --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Octet{T}.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// 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. + /// + [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 index b3a32deeef..6294a61775 100644 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tuples /// 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) + internal void RoundAndDownscalePreVector8(float downscaleFactor) { ref Vector a = ref Unsafe.As>(ref this.A); a = a.FastRound(); @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tuples /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleAvx2(float downscaleFactor) + internal void RoundAndDownscaleVector8(float downscaleFactor) { ref Vector self = ref Unsafe.As>(ref this); Vector v = self; @@ -79,4 +79,4 @@ namespace SixLabors.ImageSharp.Tuples return $"{nameof(Vector4Pair)}({this.A}, {this.B})"; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index c0064d1877..062bcb229c 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -1,154 +1,195 @@ -// 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 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 - }; - } - - /// - /// 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 +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +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.Formats.Tga; +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 Lazy(CreateDefaultInstance); + private const int DefaultStreamProcessingBufferSize = 8096; + private int streamProcessingBufferSize = DefaultStreamProcessingBufferSize; + 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 || value < -1) + { + throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); + } + + this.maxDegreeOfParallelism = value; + } + } + + /// + /// Gets or sets the size of the buffer to use when working with streams. + /// Intitialized with by default. + /// + public int StreamProcessingBufferSize + { + get => this.streamProcessingBufferSize; + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(this.StreamProcessingBufferSize)); + } + + this.streamProcessingBufferSize = value; + } + } + + /// + /// Gets a set of properties for the Congiguration. + /// + /// This can be used for storing global settings and defaults to be accessable 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 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. 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(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, + 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() + { + return new Configuration( + new PngConfigurationModule(), + new JpegConfigurationModule(), + new GifConfigurationModule(), + new BmpConfigurationModule(), + new TgaConfigurationModule()); + } + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs new file mode 100644 index 0000000000..2338572476 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 618999c87d..6fdf8d6342 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -1,13 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Enumerates the available bits per pixel for bitmap. + /// Enumerates the available bits per pixel the bitmap encoder supports. /// public enum BmpBitsPerPixel : short { + /// + /// 8 bits per pixel. Each pixel consists of 1 byte. + /// + Pixel8 = 8, + + /// + /// 16 bits per pixel. Each pixel consists of 2 bytes. + /// + Pixel16 = 16, + /// /// 24 bits per pixel. Each pixel consists of 3 bytes. /// diff --git a/src/ImageSharp/Formats/Bmp/BmpCompression.cs b/src/ImageSharp/Formats/Bmp/BmpCompression.cs index 5f14d22436..50d1ae46db 100644 --- a/src/ImageSharp/Formats/Bmp/BmpCompression.cs +++ b/src/ImageSharp/Formats/Bmp/BmpCompression.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Defines how the compression type of the image data + /// Defines the compression type of the image data /// in the bitmap file. /// internal enum BmpCompression : int @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// 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. + /// 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. @@ -30,11 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// 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. + /// 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. - /// Not supported at the moment. /// RLE4 = 2, @@ -54,6 +53,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The bitmap contains a PNG image. /// Not supported at the moment. /// - PNG = 5 + PNG = 5, + + /// + /// 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, + + /// + /// 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, } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs index 57117cc075..5505cd5e67 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Bmp diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs index 5cbed4af2b..d6c86e4db3 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 82af2a671e..129b3a1aa0 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,7 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -14,28 +18,61 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// JPG /// PNG - /// RLE4 + /// Some OS/2 specific subtypes like: Bitmap Array, Color Icon, Color Pointer, Icon, Pointer. /// /// 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 { + /// + /// Gets or sets a value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps. + /// + public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black; + /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Decode(stream); + var decoder = new BmpDecoderCore(configuration, this); + return decoder.Decode(configuration, stream); } + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new BmpDecoderCore(configuration, this); + return decoder.DecodeAsync(configuration, stream, cancellationToken); + } + + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + /// public IImageInfo Identify(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Identify(stream); + return new BmpDecoderCore(configuration, this).Identify(configuration, stream); + } + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(stream, nameof(stream)); + + return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 6bfdfa3a29..6f92236372 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1,17 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Buffers.Binary; -using System.IO; using System.Numerics; using System.Runtime.CompilerServices; +using System.Threading; using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Bmp { @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// A useful decoding source example can be found at /// - internal sealed class BmpDecoderCore + internal sealed class BmpDecoderCore : IImageDecoderInternals { /// /// The default mask for the red part of the color for 16 bit rgb bitmaps. @@ -39,55 +39,65 @@ namespace SixLabors.ImageSharp.Formats.Bmp private const int DefaultRgb16BMask = 0x1F; /// - /// RLE8 flag value that indicates following byte has special meaning. + /// RLE flag value that indicates following byte has special meaning. /// private const int RleCommand = 0x00; /// - /// RLE8 flag value marking end of a scan line. + /// RLE flag value marking end of a scan line. /// private const int RleEndOfLine = 0x00; /// - /// RLE8 flag value marking end of bitmap data. + /// RLE flag value marking end of bitmap data. /// private const int RleEndOfBitmap = 0x01; /// - /// RLE8 flag value marking the start of [x,y] offset instruction. + /// RLE flag value marking the start of [x,y] offset instruction. /// private const int RleDelta = 0x02; /// /// The stream to decode from. /// - private Stream stream; + private BufferedReadStream stream; /// /// The metadata. /// - private ImageMetaData metaData; + private ImageMetadata metadata; /// - /// The bmp specific metadata. + /// The bitmap specific metadata. /// - private BmpMetaData bmpMetaData; + private BmpMetadata bmpMetadata; /// /// The file header containing general information. - /// TODO: Why is this not used? We advance the stream but do not use the values parsed. /// 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; - private readonly Configuration configuration; - + /// + /// Used for allocating memory during processing operations. + /// private readonly MemoryAllocator memoryAllocator; + /// + /// The bitmap decoder options. + /// + private readonly IBmpDecoderOptions options; + /// /// Initializes a new instance of the class. /// @@ -95,29 +105,28 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The options. public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) { - this.configuration = configuration; + this.Configuration = configuration; this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; } + /// + public Configuration Configuration { get; } + /// - /// Decodes the image from the specified this._stream and sets - /// the data to image. + /// Gets the dimensions of the 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 + public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { try { int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); - var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metaData); + var image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -126,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp case BmpCompression.RGB: if (this.infoHeader.BitsPerPixel == 32) { - if (this.bmpMetaData.InfoHeaderType == BmpInfoHeaderType.WinVersion3) + if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3) { this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } @@ -156,6 +165,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp } break; + + case BmpCompression.RLE24: + this.ReadRle24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + + break; + case BmpCompression.RLE8: case BmpCompression.RLE4: this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); @@ -163,6 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp break; case BmpCompression.BitFields: + case BmpCompression.BI_ALPHABITFIELDS: this.ReadBitFields(pixels, inverted); break; @@ -181,14 +197,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - public IImageInfo Identify(Stream stream) + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.ReadImageHeaders(stream, out _, out _); - return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metaData); + return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); } /// @@ -207,7 +220,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The image width. /// The pixel component count. /// - /// The . + /// The padding. /// private static int CalculatePadding(int width, int componentCount) { @@ -222,14 +235,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Decodes a bitmap containing BITFIELDS Compression type. For each color channel, there will be bitmask + /// 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 output pixel buffer containing the decoded image. /// Whether the bitmap is inverted. private void ReadBitFields(Buffer2D pixels, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (this.infoHeader.BitsPerPixel == 16) { @@ -258,8 +271,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// 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 + /// Compressed RLE8 stream is uncompressed by + /// Compressed RLE4 stream is uncompressed by /// /// The pixel format. /// The compression type. Either RLE4 or RLE8. @@ -269,30 +282,146 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 + where TPixel : unmanaged, IPixel { TPixel color = default; - using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + 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)) { - if (compression == BmpCompression.RLE8) + Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; + Span bufferSpan = buffer.Memory.Span; + if (compression is BmpCompression.RLE8) { - this.UncompressRle8(width, buffer.GetSpan()); + this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } else { - this.UncompressRle4(width, buffer.GetSpan()); + this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span bufferRow = buffer.GetRowSpan(y); + int rowStartIdx = y * width; + Span bufferRow = bufferSpan.Slice(rowStartIdx, width); Span pixelRow = pixels.GetRowSpan(newY); - for (int x = 0; x < width; x++) + bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; + if (rowHasUndefinedPixels) { - color.FromBgr24(Unsafe.As(ref colors[bufferRow[x] * 4])); - pixelRow[x] = color; + // Slow path with undefined pixels. + for (int x = 0; x < width; x++) + { + byte colorIdx = bufferRow[x]; + if (undefinedPixelsSpan[rowStartIdx + x]) + { + switch (this.options.RleSkippedPixelHandling) + { + case RleSkippedPixelHandling.FirstColorOfPalette: + color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); + break; + case RleSkippedPixelHandling.Transparent: + color.FromVector4(Vector4.Zero); + break; + + // Default handling for skipped pixels is black (which is what System.Drawing is also doing). + default: + color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + break; + } + } + else + { + color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); + } + + pixelRow[x] = color; + } + } + else + { + // Fast path without any undefined pixels. + for (int x = 0; x < width; x++) + { + color.FromBgr24(Unsafe.As(ref colors[bufferRow[x] * 4])); + pixelRow[x] = color; + } + } + } + } + } + + /// + /// Looks up color values and builds the image from de-compressed RLE24. + /// + /// 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 ReadRle24(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + 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(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); + for (int y = 0; y < height; y++) + { + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.GetRowSpan(newY); + bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; + if (rowHasUndefinedPixels) + { + // Slow path with undefined pixels. + var yMulWidth = y * width; + int rowStartIdx = yMulWidth * 3; + for (int x = 0; x < width; x++) + { + int idx = rowStartIdx + (x * 3); + if (undefinedPixelsSpan[yMulWidth + x]) + { + switch (this.options.RleSkippedPixelHandling) + { + case RleSkippedPixelHandling.FirstColorOfPalette: + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + break; + case RleSkippedPixelHandling.Transparent: + color.FromVector4(Vector4.Zero); + break; + + // Default handling for skipped pixels is black (which is what System.Drawing is also doing). + default: + color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + break; + } + } + else + { + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + } + + 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); + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + pixelRow[x] = color; + } } } } @@ -308,20 +437,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// The width of the bitmap. /// Buffer for uncompressed data. - private void UncompressRle4(int w, Span buffer) + /// Keeps track over skipped and therefore undefined pixels. + /// Keeps track of rows, which have undefined pixels. + private void UncompressRle4(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) { -#if NETCOREAPP2_1 Span cmd = stackalloc byte[2]; -#else - byte[] cmd = new byte[2]; -#endif int count = 0; while (count < buffer.Length) { if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); + BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); } if (cmd[0] == RleCommand) @@ -329,21 +456,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp switch (cmd[1]) { case RleEndOfBitmap: + int skipEoB = buffer.Length - count; + RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); + return; case RleEndOfLine: - int extra = count % w; - if (extra > 0) - { - count += w - extra; - } + count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); break; case RleDelta: int dx = this.stream.ReadByte(); int dy = this.stream.ReadByte(); - count += (w * dy) + dx; + count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); break; @@ -353,7 +479,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int max = cmd[1]; int bytesToRead = (max + 1) / 2; - byte[] run = new byte[bytesToRead]; + var run = new byte[bytesToRead]; this.stream.Read(run, 0, run.Length); @@ -374,7 +500,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } - // Absolute mode data is aligned to two-byte word-boundary + // Absolute mode data is aligned to two-byte word-boundary. int padding = bytesToRead & 1; this.stream.Skip(padding); @@ -418,20 +544,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// The width of the bitmap. /// Buffer for uncompressed data. - private void UncompressRle8(int w, Span buffer) + /// Keeps track of skipped and therefore undefined pixels. + /// Keeps track of rows, which have undefined pixels. + private void UncompressRle8(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) { -#if NETCOREAPP2_1 Span cmd = stackalloc byte[2]; -#else - byte[] cmd = new byte[2]; -#endif int count = 0; while (count < buffer.Length) { if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); + BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); } if (cmd[0] == RleCommand) @@ -439,30 +563,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp switch (cmd[1]) { case RleEndOfBitmap: + int skipEoB = buffer.Length - count; + RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); + return; case RleEndOfLine: - int extra = count % w; - if (extra > 0) - { - count += w - extra; - } + count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); break; case RleDelta: int dx = this.stream.ReadByte(); int dy = this.stream.ReadByte(); - count += (w * dy) + dx; + 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 + // 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]; - byte[] run = new byte[length]; + var run = new byte[length]; this.stream.Read(run, 0, run.Length); @@ -470,7 +593,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp count += run.Length; - // Absolute mode data is aligned to two-byte word-boundary + // Absolute mode data is aligned to two-byte word-boundary. int padding = length & 1; this.stream.Skip(padding); @@ -481,16 +604,189 @@ namespace SixLabors.ImageSharp.Formats.Bmp else { 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 + byte colorIdx = cmd[1]; // store the value to avoid the repeated indexer access inside the loop. for (; count < max; count++) { - buffer[count] = cmd1; + buffer[count] = colorIdx; + } + } + } + } + + /// + /// 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 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(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) + { + Span cmd = stackalloc byte[2]; + int uncompressedPixels = 0; + + while (uncompressedPixels < buffer.Length) + { + if (this.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]) + { + case RleEndOfBitmap: + int skipEoB = (buffer.Length - (uncompressedPixels * 3)) / 3; + RleSkipEndOfBitmap(uncompressedPixels, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); + + return; + + case RleEndOfLine: + uncompressedPixels += RleSkipEndOfLine(uncompressedPixels, w, undefinedPixels, rowsWithUndefinedPixels); + + break; + + case RleDelta: + int dx = this.stream.ReadByte(); + int dy = this.stream.ReadByte(); + uncompressedPixels += RleSkipDelta(uncompressedPixels, 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]; + + var run = new byte[length * 3]; + + this.stream.Read(run, 0, run.Length); + + run.AsSpan().CopyTo(buffer.Slice(start: uncompressedPixels * 3)); + + uncompressedPixels += length; + + // Absolute mode data is aligned to two-byte word-boundary. + int padding = run.Length & 1; + + this.stream.Skip(padding); + + break; + } + } + else + { + int max = uncompressedPixels + cmd[0]; + byte blueIdx = cmd[1]; + byte greenIdx = (byte)this.stream.ReadByte(); + byte redIdx = (byte)this.stream.ReadByte(); + + int bufferIdx = uncompressedPixels * 3; + for (; uncompressedPixels < max; uncompressedPixels++) + { + buffer[bufferIdx++] = blueIdx; + buffer[bufferIdx++] = greenIdx; + buffer[bufferIdx++] = redIdx; } } } } + /// + /// 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. /// @@ -504,9 +800,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 + where TPixel : unmanaged, IPixel { - // Pixels per byte (bits per pixel) + // Pixels per byte (bits per pixel). int ppb = 8 / bitsPerPixel; int arrayWidth = (width + ppb - 1) / ppb; @@ -514,7 +810,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp // Bit mask int mask = 0xFF >> (8 - bitsPerPixel); - // Rows are aligned on 4 byte boundaries + // Rows are aligned on 4 byte boundaries. int padding = arrayWidth % 4; if (padding != 0) { @@ -562,7 +858,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 2); int stride = (width * 2) + padding; @@ -629,7 +925,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 3); @@ -641,7 +937,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes( - this.configuration, + this.Configuration, row.GetSpan(), pixelSpan, width); @@ -658,7 +954,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); @@ -670,7 +966,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( - this.configuration, + this.Configuration, row.GetSpan(), pixelSpan, width); @@ -688,7 +984,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); @@ -707,7 +1003,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.stream.Read(row); PixelOperations.Instance.FromBgra32Bytes( - this.configuration, + this.Configuration, row.GetSpan(), bgraRowSpan, width); @@ -743,7 +1039,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( - this.configuration, + this.Configuration, row.GetSpan(), pixelSpan, width); @@ -757,7 +1053,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(row); PixelOperations.Instance.FromBgra32Bytes( - this.configuration, + this.Configuration, row.GetSpan(), bgraRowSpan, width); @@ -789,7 +1085,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 + where TPixel : unmanaged, IPixel { TPixel color = default; int padding = CalculatePadding(width, 4); @@ -810,11 +1106,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask); float invMaxValueAlpha = 1.0f / maxValueAlpha; - bool unusualBitMask = false; - if (bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8) - { - unusualBitMask = true; - } + bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8; using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) { @@ -905,27 +1197,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp ///
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 + + // Read the header size. + this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); - if (headerSize < BmpInfoHeader.CoreSize) + if (headerSize < BmpInfoHeader.CoreSize || headerSize > BmpInfoHeader.MaxHeaderSize) { BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize is '{headerSize}'."); } - int skipAmount = 0; - if (headerSize > BmpInfoHeader.MaxHeaderSize) - { - skipAmount = headerSize - BmpInfoHeader.MaxHeaderSize; - headerSize = BmpInfoHeader.MaxHeaderSize; - } - - // read the rest of the header + // Read the rest of the header. this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2; @@ -947,17 +1230,27 @@ namespace SixLabors.ImageSharp.Formats.Bmp infoHeaderType = BmpInfoHeaderType.WinVersion3; this.infoHeader = BmpInfoHeader.ParseV3(buffer); - // if the info header is BMP version 3 and the compression type is BITFIELDS, + // 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) { - byte[] bitfieldsBuffer = new byte[12]; + var bitfieldsBuffer = new byte[12]; this.stream.Read(bitfieldsBuffer, 0, 12); - Span data = bitfieldsBuffer.AsSpan(); + 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 if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS) + { + var 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)); + } } else if (headerSize == BmpInfoHeader.AdobeV3Size) { @@ -989,7 +1282,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } // Resolution is stored in PPM. - var meta = new ImageMetaData + var meta = new ImageMetadata { ResolutionUnits = PixelResolutionUnit.PixelsPerMeter }; @@ -1001,25 +1294,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp else { // Convert default metadata values to PPM. - meta.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultHorizontalResolution)); - meta.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultVerticalResolution)); + meta.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultHorizontalResolution)); + meta.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution)); } - this.metaData = meta; + this.metadata = meta; short bitsPerPixel = this.infoHeader.BitsPerPixel; - this.bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance); - this.bmpMetaData.InfoHeaderType = infoHeaderType; + this.bmpMetadata = this.metadata.GetBmpMetadata(); + this.bmpMetadata.InfoHeaderType = infoHeaderType; - // We can only encode at these bit rates so far. - if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24) + // We can only encode at these bit rates so far (1 bit and 4 bit are still missing). + if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8) + || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16) + || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24) || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32)) { - this.bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; + this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; } - - // skip the remaining header because we can't read those parts - this.stream.Skip(skipAmount); } /// @@ -1027,18 +1319,33 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private void ReadFileHeader() { -#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.fileHeader = BmpFileHeader.Parse(buffer); - - if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap) + short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(buffer); + switch (fileTypeMarker) { - BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. File header bitmap type marker '{this.fileHeader.Type}'."); + 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. + this.stream.Read(buffer, 0, BmpFileHeader.Size); + this.fileHeader = BmpFileHeader.Parse(buffer); + if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap) + { + BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Type}'."); + } + + break; + + default: + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. File header bitmap type marker '{fileTypeMarker}'."); + break; } } @@ -1047,7 +1354,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp ///
/// 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) + private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette) { this.stream = stream; @@ -1066,44 +1373,73 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.infoHeader.Height = -this.infoHeader.Height; } - int colorMapSize = -1; int bytesPerColorMapEntry = 4; - + int colorMapSizeBytes = -1; 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; - colorMapSize = colorMapSizeBytes; + switch (this.fileMarkerType) + { + case BmpFileMarkerType.Bitmap: + colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; + int colorCountForBitDepth = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); + bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth; + + // Edge case for less-than-full-sized palette: bytesPerColorMapEntry should be at least 3. + bytesPerColorMapEntry = Math.Max(bytesPerColorMapEntry, 3); + + 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 = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * bytesPerColorMapEntry; + break; + } } } else { - colorMapSize = this.infoHeader.ClrUsed * bytesPerColorMapEntry; + colorMapSizeBytes = this.infoHeader.ClrUsed * bytesPerColorMapEntry; } palette = null; - if (colorMapSize > 0) + if (colorMapSizeBytes > 0) { - // 256 * 4 - if (colorMapSize > 1024) + // 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.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset) { - BmpThrowHelper.ThrowImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); + 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."); } - palette = new byte[colorMapSize]; + palette = new byte[colorMapSizeBytes]; - this.stream.Read(palette, 0, colorMapSize); + this.stream.Read(palette, 0, colorMapSizeBytes); } this.infoHeader.VerifyDimensions(); + int skipAmount = this.fileHeader.Offset - (int)this.stream.Position; + if ((skipAmount + (int)this.stream.Position) > this.stream.Length) + { + BmpThrowHelper.ThrowInvalidImageContentException("Invalid fileheader offset found. Offset is greater than the stream length."); + } + + if (skipAmount > 0) + { + this.stream.Skip(skipAmount); + } + return bytesPerColorMapEntry; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index b1a66accec..2f5c4b7cf7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -1,16 +1,18 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Bmp { /// /// Image encoder for writing an image to a stream as a Windows bitmap. /// - /// The encoder can currently only write 24-bit rgb images to streams. public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions { /// @@ -18,12 +20,34 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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; } + + /// + /// Gets or sets the quantizer for reducing the color count for 8-Bit images. + /// Defaults to OctreeQuantizer. + /// + public IQuantizer Quantizer { get; set; } + /// public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); encoder.Encode(image, stream); } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 27a38bc0d1..eb29c44050 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -1,22 +1,26 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; - +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Bmp { /// /// Image encoder for writing an image to a stream as a Windows bitmap. /// - internal sealed class BmpEncoderCore + internal sealed class BmpEncoderCore : IImageEncoderInternals { /// /// The amount to pad each row by. @@ -24,40 +28,68 @@ namespace SixLabors.ImageSharp.Formats.Bmp private int padding; /// - /// The mask for the alpha channel of the color for a 32 bit rgba bitmaps. + /// 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 a 32 bit rgba bitmaps. + /// 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 a 32 bit rgba bitmaps. + /// 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 a 32 bit rgba bitmaps. + /// 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; + + /// + /// Used for allocating memory during processing operations. + /// private readonly MemoryAllocator memoryAllocator; + /// + /// The global configuration. + /// private Configuration configuration; + /// + /// 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. + /// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders. + /// + private readonly bool writeV4Header; + + /// + /// The quantizer for reducing the color count for 8-Bit images. + /// + private readonly IQuantizer quantizer; + /// /// Initializes a new instance of the class. /// - /// The encoder options - /// The memory manager + /// The encoder options. + /// The memory manager. public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; + this.writeV4Header = options.SupportTransparency; + this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; } /// @@ -66,16 +98,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 + /// 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.GetConfiguration(); - ImageMetaData metaData = image.MetaData; - BmpMetaData bmpMetaData = metaData.GetFormatMetaData(BmpFormat.Instance); - this.bitsPerPixel = this.bitsPerPixel ?? bmpMetaData.BitsPerPixel; + ImageMetadata metadata = image.Metadata; + BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); + this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel; short bpp = (short)this.bitsPerPixel; int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); @@ -85,34 +118,34 @@ namespace SixLabors.ImageSharp.Formats.Bmp int hResolution = 0; int vResolution = 0; - if (metaData.ResolutionUnits != PixelResolutionUnit.AspectRatio) + if (metadata.ResolutionUnits != PixelResolutionUnit.AspectRatio) { - if (metaData.HorizontalResolution > 0 && metaData.VerticalResolution > 0) + if (metadata.HorizontalResolution > 0 && metadata.VerticalResolution > 0) { - switch (metaData.ResolutionUnits) + switch (metadata.ResolutionUnits) { case PixelResolutionUnit.PixelsPerInch: - hResolution = (int)Math.Round(UnitConverter.InchToMeter(metaData.HorizontalResolution)); - vResolution = (int)Math.Round(UnitConverter.InchToMeter(metaData.VerticalResolution)); + hResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.VerticalResolution)); break; case PixelResolutionUnit.PixelsPerCentimeter: - hResolution = (int)Math.Round(UnitConverter.CmToMeter(metaData.HorizontalResolution)); - vResolution = (int)Math.Round(UnitConverter.CmToMeter(metaData.VerticalResolution)); + 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); + hResolution = (int)Math.Round(metadata.HorizontalResolution); + vResolution = (int)Math.Round(metadata.VerticalResolution); break; } } } - int infoHeaderSize = BmpInfoHeader.SizeV4; + int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3; var infoHeader = new BmpInfoHeader( headerSize: infoHeaderSize, height: image.Height, @@ -123,35 +156,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp clrUsed: 0, clrImportant: 0, xPelsPerMeter: hResolution, - yPelsPerMeter: vResolution) - { - RedMask = Rgba32RedMask, - GreenMask = Rgba32GreenMask, - BlueMask = Rgba32BlueMask, - Compression = BmpCompression.BitFields - }; + yPelsPerMeter: vResolution); - if (this.bitsPerPixel == BmpBitsPerPixel.Pixel32) + if (this.writeV4Header && this.bitsPerPixel == BmpBitsPerPixel.Pixel32) { infoHeader.AlphaMask = Rgba32AlphaMask; + infoHeader.RedMask = Rgba32RedMask; + infoHeader.GreenMask = Rgba32GreenMask; + infoHeader.BlueMask = Rgba32BlueMask; + infoHeader.Compression = BmpCompression.BitFields; } + int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0; + var fileHeader = new BmpFileHeader( type: BmpConstants.TypeMarkers.Bitmap, fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize, reserved: 0, - offset: BmpFileHeader.Size + infoHeaderSize); + offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); -#if NETCOREAPP2_1 Span buffer = stackalloc byte[infoHeaderSize]; -#else - byte[] buffer = new byte[infoHeaderSize]; -#endif fileHeader.WriteTo(buffer); stream.Write(buffer, 0, BmpFileHeader.Size); - infoHeader.WriteV4Header(buffer); + if (this.writeV4Header) + { + infoHeader.WriteV4Header(buffer); + } + else + { + infoHeader.WriteV3Header(buffer); + } stream.Write(buffer, 0, infoHeaderSize); @@ -169,7 +205,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The containing pixel data. /// private void WriteImage(Stream stream, ImageFrame image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Buffer2D pixels = image.PixelBuffer; switch (this.bitsPerPixel) @@ -181,6 +217,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp case BmpBitsPerPixel.Pixel24: this.Write24Bit(stream, pixels); break; + + case BmpBitsPerPixel.Pixel16: + this.Write16Bit(stream, pixels); + break; + + case BmpBitsPerPixel.Pixel8: + this.Write8Bit(stream, image); + break; } } @@ -193,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write32Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) { @@ -217,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write24Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) { @@ -233,5 +277,137 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } } + + /// + /// 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 : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + + stream.Write(row.Array, 0, row.Length()); + } + } + } + + /// + /// Writes an 8 Bit image 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. + private void Write8Bit(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + bool isL8 = typeof(TPixel) == typeof(L8); + using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) + { + Span colorPalette = colorPaletteBuffer.GetSpan(); + if (isL8) + { + this.Write8BitGray(stream, image, colorPalette); + } + else + { + this.Write8BitColor(stream, image, colorPalette); + } + } + } + + /// + /// 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 to write to. + /// The containing pixel data. + /// A byte span of size 1024 for the color palette. + private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette) + where TPixel : unmanaged, IPixel + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + + ReadOnlySpan quantizedColors = quantized.Palette.Span; + var color = default(Rgba32); + + // TODO: Use bulk conversion here for better perf + int idx = 0; + foreach (TPixel quantizedColor in quantizedColors) + { + quantizedColor.ToRgba32(ref color); + colorPalette[idx] = color.B; + colorPalette[idx + 1] = color.G; + colorPalette[idx + 2] = color.R; + + // Padding byte, always 0. + colorPalette[idx + 3] = 0; + idx += 4; + } + + stream.Write(colorPalette); + + for (int y = image.Height - 1; y >= 0; y--) + { + ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); + stream.Write(pixelSpan); + + for (int i = 0; i < this.padding; i++) + { + stream.WriteByte(0); + } + } + } + + /// + /// Writes an 8 Bit gray image 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. + private void Write8BitGray(Stream stream, ImageFrame image, Span colorPalette) + 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(colorPalette); + + for (int y = image.Height - 1; y >= 0; y--) + { + ReadOnlySpan inputPixelRow = image.GetPixelRowSpan(y); + ReadOnlySpan outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow); + stream.Write(outputPixelRow); + + for (int i = 0; i < this.padding; i++) + { + stream.WriteByte(0); + } + } + } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs index e39a2af0e4..acbcdaef3a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp internal readonly struct BmpFileHeader { /// - /// Defines of the data structure in the bitmap file. + /// Defines the size of the data structure in the bitmap file. /// public const int Size = 14; @@ -62,11 +62,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp return MemoryMarshal.Cast(data)[0]; } - public unsafe void WriteTo(Span buffer) + public void WriteTo(Span buffer) { ref BmpFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); dest = this; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs b/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs new file mode 100644 index 0000000000..882ccd12a2 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 a5eaab8ebf..9e367c6da4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Registers the image encoders, decoders and mime type detectors for the bmp format. /// - public sealed class BmpFormat : IImageFormat + public sealed class BmpFormat : IImageFormat { private BmpFormat() { @@ -32,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp public IEnumerable FileExtensions => BmpConstants.FileExtensions; /// - public BmpMetaData CreateDefaultFormatMetaData() => new BmpMetaData(); + 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 6a740d47d1..b380486a3f 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -1,12 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers.Binary; namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Detects bmp file headers + /// Detects bmp file headers. /// public sealed class BmpImageFormatDetector : IImageFormatDetector { @@ -21,10 +22,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp private bool IsSupportedFileFormat(ReadOnlySpan header) { - // TODO: This should be in constants - return header.Length >= this.HeaderSize && - header[0] == 0x42 && // B - header[1] == 0x4D; // M + if (header.Length >= this.HeaderSize) + { + short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); + return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray; + } + + return false; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs index 6da5f73e3f..0d0c05c9f4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; @@ -51,10 +51,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public const int SizeV4 = 108; + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 5) data structure in the bitmap file. + /// + public const int SizeV5 = 124; + /// /// Defines the size of the biggest supported header data structure in the bitmap file. /// - public const int MaxHeaderSize = SizeV4; + public const int MaxHeaderSize = SizeV5; /// /// Defines the size of the field. @@ -272,7 +277,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes). /// /// The data to parse. - /// Parsed header + /// The parsed header. /// public static BmpInfoHeader ParseCore(ReadOnlySpan data) { @@ -289,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// are 4 bytes instead of 2, resulting in 16 bytes total. /// /// The data to parse. - /// Parsed header + /// The parsed header. /// public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) { @@ -383,8 +388,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp case 2: infoHeader.Compression = BmpCompression.RLE4; break; + case 4: + infoHeader.Compression = BmpCompression.RLE24; + break; default: - BmpThrowHelper.ThrowImageFormatException($"Compression type is not supported. ImageSharp only supports uncompressed, RLE4 and RLE8."); + // Compression type 3 (1DHuffman) is not supported. + BmpThrowHelper.ThrowInvalidImageContentException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24."); break; } @@ -406,7 +415,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public static BmpInfoHeader ParseV4(ReadOnlySpan data) { - if (data.Length != SizeV4) + if (data.Length < SizeV4) { throw new ArgumentException(nameof(data), $"Must be {SizeV4} bytes. Was {data.Length} bytes."); } @@ -438,7 +447,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Writes a complete Bitmap V4 header to a buffer. ///
/// The buffer to write to. - public unsafe void WriteV4Header(Span buffer) + public void WriteV4Header(Span buffer) { ref BmpInfoHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); @@ -457,4 +466,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs index a92a19d9ba..86bfdf9bf3 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Bmp diff --git a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs deleted file mode 100644 index 2d86856177..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 new file mode 100644 index 0000000000..50cf32fcb1 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// 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/BmpThrowHelper.cs b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs index 443471404e..3d577d278f 100644 --- a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs +++ b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,23 +9,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp internal static class BmpThrowHelper { /// - /// Cold path optimization for throwing -s + /// 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 ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); /// - /// Cold path optimization for throwing -s + /// 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); - } + => throw new NotSupportedException(errorMessage); } } diff --git a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs index 219d37ca62..d359e9f1d6 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Bmp @@ -8,6 +8,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp ///
internal interface IBmpDecoderOptions { - // added this for consistency so we can add stuff as required, no options currently available + /// + /// Gets the value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps. + /// + RleSkippedPixelHandling RleSkippedPixelHandling { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index f62504d08f..d4a22d66ea 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -1,17 +1,31 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing.Processors.Quantization; + namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Configuration options for use during bmp encoding + /// Configuration options for use during bmp encoding. /// - /// The encoder can currently only write 24-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; } + + /// + /// Gets the quantizer for reducing the color count for 8-Bit images. + /// + IQuantizer Quantizer { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs deleted file mode 100644 index aa1c353db2..0000000000 --- a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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/MetadataExtensions.cs b/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs new file mode 100644 index 0000000000..a6f212e2e7 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the bmp format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static BmpMetadata GetBmpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(BmpFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Bmp/README.md b/src/ImageSharp/Formats/Bmp/README.md index d072838438..f418307335 100644 --- a/src/ImageSharp/Formats/Bmp/README.md +++ b/src/ImageSharp/Formats/Bmp/README.md @@ -1,8 +1,17 @@ -Encoder/Decoder adapted from: +### Encoder/Decoder adapted from: -https://github.com/yufeih/Nine.Imaging/ -https://imagetools.codeplex.com/ +- [Nine.Imaging](https://github.com/yufeih/Nine.Imaging/) +- [imagetools.codeplex](https://imagetools.codeplex.com/) -TODO: +### Some useful links for documentation about the bitmap format: -- Add support for all bitmap formats. +- [Microsoft Windows Bitmap File](http://www.fileformat.info/format/bmp/egff.htm) +- [OS/2 Bitmap File Format Summary](http://www.fileformat.info/format/os2bmp/egff.htm) +- [The DIB File Format](https://www-user.tu-chemnitz.de/~heha/viewchm.php/hs/petzold.chm/petzoldi/ch15b.htm) +- [Dr.Dobbs: The BMP File Format, Part 1](http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517) +- [Windows Bitmap File Format Specifications](ftp://ftp.nada.kth.se/pub/hacks/sgi/src/libwmf/doc/Bmpfrmat.html) + +### A set of bitmap test images: + +- [bmpsuite](http://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html) +- [eclecticgeek](http://eclecticgeek.com/dompdf/core_tests/image_bmp.html) \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs b/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs new file mode 100644 index 0000000000..ce113dce72 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/Gif/GifColorTableMode.cs b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs index 95b3335626..b8569a321f 100644 --- a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs +++ b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Gif diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs index 861d3e0368..b08a3c38e6 100644 --- a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Gif diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index 288c3dfa19..24fd8a9365 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -1,13 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text; namespace SixLabors.ImageSharp.Formats.Gif { /// - /// Constants that define specific points within a gif. + /// Constants that define specific points within a Gif. /// internal static class GifConstants { @@ -21,11 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Gif ///
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 !. /// @@ -51,11 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Gif ///
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. /// @@ -67,14 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Gif public const byte CommentLabel = 0xFE; /// - /// The name of the property inside the image properties for the comments. + /// The maximum length of a comment data sub-block is 255. /// - public const string Comments = "Comments"; - - /// - /// The maximum comment length. - /// - public const int MaxCommentLength = 1024 * 8; + public const int MaxCommentSubBlockLength = 255; /// /// The image descriptor label ,. @@ -102,18 +88,38 @@ namespace SixLabors.ImageSharp.Formats.Gif public const byte EndIntroducer = 0x3B; /// - /// Gets the default encoding to use when reading comments. + /// The character encoding to use when reading and writing comments - (ASCII 7bit). /// - public static readonly Encoding DefaultEncoding = Encoding.ASCII; + public static readonly Encoding Encoding = Encoding.ASCII; /// - /// The list of mimetypes that equate to a gif. + /// The collection 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. + /// The collection of file extensions that equate to a Gif. /// public static readonly IEnumerable FileExtensions = new[] { "gif" }; + + /// + /// Gets the ASCII encoded bytes used to identify the GIF file (combining and ). + /// + internal static ReadOnlySpan MagicNumber => new[] + { + (byte)'G', (byte)'I', (byte)'F', + (byte)'8', (byte)'9', (byte)'a' + }; + + /// + /// Gets the ASCII encoded application identification bytes (representing ). + /// + internal static ReadOnlySpan NetscapeApplicationIdentificationBytes => new[] + { + (byte)'N', (byte)'E', (byte)'T', + (byte)'S', (byte)'C', (byte)'A', + (byte)'P', (byte)'E', + (byte)'2', (byte)'.', (byte)'0' + }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 42c76d6400..196d77ad77 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -1,9 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; -using System.Text; -using SixLabors.ImageSharp.MetaData; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Gif @@ -18,11 +21,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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 /// @@ -30,19 +28,47 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + { + var decoder = new GifDecoderCore(configuration, this); + return decoder.Decode(configuration, stream); + } + + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(stream); + return decoder.DecodeAsync(configuration, stream, cancellationToken); } + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + /// public IImageInfo Identify(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); - return decoder.Identify(stream); + + using var bufferedStream = new BufferedReadStream(configuration, stream); + return decoder.Identify(bufferedStream, default); + } + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new GifDecoderCore(configuration, this); + return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index bfa91416aa..8f5cc3b5c8 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,34 +6,29 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using SixLabors.ImageSharp.Advanced; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Gif { /// /// Performs the gif decoding operation. /// - internal sealed class GifDecoderCore + internal sealed class GifDecoderCore : IImageDecoderInternals { /// /// 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; + private BufferedReadStream stream; /// /// The global color table. @@ -63,12 +58,12 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The abstract metadata. /// - private ImageMetaData metaData; + private ImageMetadata metadata; /// /// The gif specific metadata. /// - private GifMetaData gifMetaData; + private GifMetadata gifMetadata; /// /// Initializes a new instance of the class. @@ -77,37 +72,34 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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; + this.Configuration = configuration ?? Configuration.Default; } + /// + public Configuration Configuration { get; } + /// /// 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 + /// Gets the decoding mode for multi-frame images. /// - public Encoding TextEncoding { get; } + public FrameDecodingMode DecodingMode { get; } /// - /// Gets the decoding mode for multi-frame images + /// Gets the dimensions of the image. /// - public FrameDecodingMode DecodingMode { get; } + public Size Dimensions => new Size(this.imageDescriptor.Width, this.imageDescriptor.Height); - private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator; + 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 + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { Image image = null; ImageFrame previousFrame = null; @@ -166,11 +158,8 @@ namespace SixLabors.ImageSharp.Formats.Gif return image; } - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - public IImageInfo Identify(Stream stream) + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { try { @@ -223,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Gif new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height, - this.metaData); + this.metadata); } /// @@ -244,6 +233,10 @@ namespace SixLabors.ImageSharp.Formats.Gif this.stream.Read(this.buffer, 0, 9); this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); + if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) + { + GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0"); + } } /// @@ -276,15 +269,14 @@ namespace SixLabors.ImageSharp.Formats.Gif if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) { this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); - this.gifMetaData.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; + 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); + // Skip the subblock and terminator. + this.SkipBlock(subBlockSize); return; } @@ -317,11 +309,12 @@ namespace SixLabors.ImageSharp.Formats.Gif { int length; + var stringBuilder = new StringBuilder(); while ((length = this.stream.ReadByte()) != 0) { - if (length > GifConstants.MaxCommentLength) + if (length > GifConstants.MaxCommentSubBlockLength) { - throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'"); + GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block"); } if (this.IgnoreMetadata) @@ -333,10 +326,15 @@ namespace SixLabors.ImageSharp.Formats.Gif 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)); + string commentPart = GifConstants.Encoding.GetString(commentsBuffer.Array, 0, length); + stringBuilder.Append(commentPart); } } + + if (stringBuilder.Length > 0) + { + this.gifMetadata.Comments.Add(stringBuilder.ToString()); + } } /// @@ -346,27 +344,27 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The image to decode the information to. /// The previous frame. private void ReadFrame(ref Image image, ref ImageFrame previousFrame) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.ReadImageDescriptor(); IManagedByteBuffer localColorTable = null; - IManagedByteBuffer indices = null; + Buffer2D 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); + localColorTable = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); this.stream.Read(localColorTable.Array, 0, length); } - indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.imageDescriptor.Width * this.imageDescriptor.Height, AllocationOptions.Clean); + indices = this.Configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); - this.ReadFrameIndices(this.imageDescriptor, indices.GetSpan()); + this.ReadFrameIndices(indices); ReadOnlySpan colorTable = MemoryMarshal.Cast((localColorTable ?? this.globalColorTable).GetSpan()); - this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, this.imageDescriptor); + this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor); // Skip any remaining blocks this.SkipBlock(); @@ -381,16 +379,13 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Reads the frame indices marking the color to use for each pixel. /// - /// The . - /// The pixel array to write to. + /// The 2D pixel buffer to write to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span indices) + private void ReadFrameIndices(Buffer2D indices) { int dataSize = this.stream.ReadByte(); - using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream)) - { - lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); - } + using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream); + lzwDecoder.DecodePixels(dataSize, indices); } /// @@ -402,10 +397,9 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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 + private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Buffer2D indices, ReadOnlySpan colorTable, in GifImageDescriptor descriptor) + where TPixel : unmanaged, IPixel { - ref byte indicesRef = ref MemoryMarshal.GetReference(indices); int imageWidth = this.logicalScreenDescriptor.Width; int imageHeight = this.logicalScreenDescriptor.Height; @@ -416,9 +410,9 @@ namespace SixLabors.ImageSharp.Formats.Gif if (previousFrame is null) { // This initializes the image to become fully transparent because the alpha channel is zero. - image = new Image(this.configuration, imageWidth, imageHeight, this.metaData); + image = new Image(this.Configuration, imageWidth, imageHeight, this.metadata); - this.SetFrameMetaData(image.Frames.RootFrame.MetaData); + this.SetFrameMetadata(image.Frames.RootFrame.Metadata); imageFrame = image.Frames.RootFrame; } @@ -431,20 +425,27 @@ namespace SixLabors.ImageSharp.Formats.Gif currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection - this.SetFrameMetaData(currentFrame.MetaData); + this.SetFrameMetadata(currentFrame.Metadata); imageFrame = currentFrame; this.RestoreToBackground(imageFrame); } - 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 = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + int descriptorTop = descriptor.Top; + int descriptorBottom = descriptorTop + descriptor.Height; + int descriptorLeft = descriptor.Left; + int descriptorRight = descriptorLeft + descriptor.Width; + bool transFlag = this.graphicsControlExtension.TransparencyFlag; + byte transIndex = this.graphicsControlExtension.TransparencyIndex; + + for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) { + ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.GetRowSpan(y - descriptorTop)); + // Check if this image is interlaced. int writeY; // the target y offset to write to if (descriptor.InterlaceFlag) @@ -480,35 +481,29 @@ namespace SixLabors.ImageSharp.Formats.Gif } ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY)); - bool transFlag = this.graphicsControlExtension.TransparencyFlag; if (!transFlag) { // #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++) + for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) { - int index = Unsafe.Add(ref indicesRef, i); + int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); Rgb24 rgb = colorTable[index]; pixel.FromRgb24(rgb); - - i++; } } else { - byte transIndex = this.graphicsControlExtension.TransparencyIndex; - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width && x < imageWidth; x++) + for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) { - int index = Unsafe.Add(ref indicesRef, i); + int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft); if (transIndex != index) { ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); Rgb24 rgb = colorTable[index]; pixel.FromRgb24(rgb); } - - i++; } } } @@ -533,15 +528,15 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The frame. private void RestoreToBackground(ImageFrame frame) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (this.restoreArea is null) { return; } - BufferArea pixelArea = frame.PixelBuffer.GetArea(this.restoreArea.Value); - pixelArea.Clear(); + Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(this.restoreArea.Value); + pixelRegion.Clear(); this.restoreArea = null; } @@ -549,11 +544,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Sets the frames metadata. /// - /// The meta data. + /// The metadata. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetFrameMetaData(ImageFrameMetaData meta) + private void SetFrameMetadata(ImageFrameMetadata meta) { - GifFrameMetaData gifMeta = meta.GetFormatMetaData(GifFormat.Instance); + GifFrameMetadata gifMeta = meta.GetGifMetadata(); if (this.graphicsControlExtension.DelayTime > 0) { gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; @@ -578,7 +573,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Reads the logical screen descriptor and global color table blocks /// /// The stream containing image data. - private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream) + private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream) { this.stream = stream; @@ -586,7 +581,7 @@ namespace SixLabors.ImageSharp.Formats.Gif this.stream.Skip(6); this.ReadLogicalScreenDescriptor(); - var meta = new ImageMetaData(); + 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 @@ -614,16 +609,16 @@ namespace SixLabors.ImageSharp.Formats.Gif } } - this.metaData = meta; - this.gifMetaData = meta.GetFormatMetaData(GifFormat.Instance); - this.gifMetaData.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag + this.metadata = meta; + this.gifMetadata = meta.GetGifMetadata(); + this.gifMetadata.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag ? GifColorTableMode.Global : GifColorTableMode.Local; if (this.logicalScreenDescriptor.GlobalColorTableFlag) { int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; - this.gifMetaData.GlobalColorTableLength = globalColorTableLength; + this.gifMetadata.GlobalColorTableLength = globalColorTableLength; this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean); @@ -632,4 +627,4 @@ namespace SixLabors.ImageSharp.Formats.Gif } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs index 982340db66..b57491cf97 100644 --- a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs +++ b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Gif diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 4210b08765..116ee3daeb 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -1,10 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; -using System.Text; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Gif @@ -14,28 +16,37 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions { - /// - /// 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(); + public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree; /// /// Gets or sets the color table mode: Global or local. /// public GifColorTableMode? ColorTableMode { get; set; } + /// + /// Gets or sets the used for quantization + /// when building a global color table in case of . + /// + public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } = new DefaultPixelSamplingStrategy(); + /// public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var encoder = new GifEncoderCore(image.GetConfiguration().MemoryAllocator, this); + var encoder = new GifEncoderCore(image.GetConfiguration(), this); encoder.Encode(image, stream); } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new GifEncoderCore(image.GetConfiguration(), this); + return encoder.EncodeAsync(image, stream, cancellationToken); + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index a922f30117..070864e603 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,21 +6,20 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; - +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; 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 + internal sealed class GifEncoderCore : IImageEncoderInternals { /// /// Used for allocating memory during processing operations. @@ -30,18 +29,13 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Configuration bound to the encoding operation. /// - private Configuration configuration; + private readonly 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. /// @@ -58,21 +52,22 @@ namespace SixLabors.ImageSharp.Formats.Gif private int bitDepth; /// - /// Gif specific meta data. + /// The pixel sampling strategy for global quantization. /// - private GifMetaData gifMetaData; + private IPixelSamplingStrategy pixelSamplingStrategy; /// /// Initializes a new instance of the class. /// - /// The to use for buffer allocations. + /// The configuration which allows altering default behaviour or extending the library. /// The options for the encoder. - public GifEncoderCore(MemoryAllocator memoryAllocator, IGifEncoderOptions options) + public GifEncoderCore(Configuration configuration, IGifEncoderOptions options) { - this.memoryAllocator = memoryAllocator; - this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; this.quantizer = options.Quantizer; this.colorTableMode = options.ColorTableMode; + this.pixelSamplingStrategy = options.GlobalPixelSamplingStrategy; } /// @@ -81,32 +76,43 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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 + /// 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.GetConfiguration(); - - ImageMetaData metaData = image.MetaData; - this.gifMetaData = metaData.GetFormatMetaData(GifFormat.Instance); - this.colorTableMode = this.colorTableMode ?? this.gifMetaData.ColorTableMode; - bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global); + ImageMetadata metadata = image.Metadata; + GifMetadata gifMetadata = metadata.GetGifMetadata(); + this.colorTableMode ??= gifMetadata.ColorTableMode; + bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; // Quantize the image returning a palette. - QuantizedFrame quantized = - this.quantizer.CreateFrameQuantizer(image.GetConfiguration()).QuantizeFrame(image.Frames.RootFrame); + IndexedImageFrame quantized; + + using (IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration)) + { + if (useGlobalTable) + { + frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); + } + else + { + quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds()); + } + } // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); // Write the header. this.WriteHeader(stream); // Write the LSD. int index = this.GetTransparentIndex(quantized); - this.WriteLogicalScreenDescriptor(metaData, image.Width, image.Height, index, useGlobalTable, stream); + this.WriteLogicalScreenDescriptor(metadata, image.Width, image.Height, index, useGlobalTable, stream); if (useGlobalTable) { @@ -114,12 +120,12 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Write the comments. - this.WriteComments(metaData, stream); + this.WriteComments(gifMetadata, stream); // Write application extension to allow additional frames. if (image.Frames.Count > 1) { - this.WriteApplicationExtension(stream, this.gifMetaData.RepeatCount); + this.WriteApplicationExtension(stream, gifMetadata.RepeatCount); } if (useGlobalTable) @@ -132,24 +138,26 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Clean up. - quantized?.Dispose(); - quantized = null; + quantized.Dispose(); // TODO: Write extension etc stream.WriteByte(GifConstants.EndIntroducer); } - private void EncodeGlobal(Image image, QuantizedFrame quantized, int transparencyIndex, Stream stream) - where TPixel : struct, IPixel + private void EncodeGlobal(Image image, IndexedImageFrame quantized, int transparencyIndex, Stream stream) + where TPixel : unmanaged, IPixel { - var palleteQuantizer = new PaletteQuantizer(quantized.Palette, this.quantizer.Diffuser); - + // The palette quantizer can reuse the same pixel map across multiple frames + // since the palette is unchanging. This allows a reduction of memory usage across + // multi frame gifs using a global palette. + EuclideanPixelMap pixelMap = default; + bool pixelMapSet = false; 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); + ImageFrameMetadata metadata = frame.Metadata; + GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); + this.WriteGraphicalControlExtension(frameMetadata, transparencyIndex, stream); this.WriteImageDescriptor(frame, false, stream); if (i == 0) @@ -158,82 +166,90 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (QuantizedFrame paletteQuantized = palleteQuantizer.CreateFrameQuantizer(image.GetConfiguration()).QuantizeFrame(frame)) + if (!pixelMapSet) { - this.WriteImageData(paletteQuantized, stream); + pixelMapSet = true; + pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette); } + + using var paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, pixelMap); + using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); + this.WriteImageData(paletteQuantized, stream); } } } - private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream) - where TPixel : struct, IPixel + private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) + where TPixel : unmanaged, IPixel { ImageFrame previousFrame = null; - GifFrameMetaData previousMeta = null; - foreach (ImageFrame frame in image.Frames) + GifFrameMetadata previousMeta = null; + for (int i = 0; i < image.Frames.Count; i++) { - ImageFrameMetaData metaData = frame.MetaData; - GifFrameMetaData frameMetaData = metaData.GetFormatMetaData(GifFormat.Instance); + ImageFrame frame = image.Frames[i]; + ImageFrameMetadata metadata = frame.Metadata; + GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); if (quantized is null) { // 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) + if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength + && frameMetadata.ColorTableLength > 0) { - quantized = this.quantizer.CreateFrameQuantizer( - image.GetConfiguration(), - frameMetaData.ColorTableLength).QuantizeFrame(frame); + var options = new QuantizerOptions + { + Dither = this.quantizer.Options.Dither, + DitherScale = this.quantizer.Options.DitherScale, + MaxColors = frameMetadata.ColorTableLength + }; + + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, options); + quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); } else { - quantized = this.quantizer.CreateFrameQuantizer(image.GetConfiguration()) - .QuantizeFrame(frame); + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration); + quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); } } - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); - this.WriteGraphicalControlExtension(frameMetaData, this.GetTransparentIndex(quantized), stream); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); + this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream); this.WriteImageDescriptor(frame, true, stream); this.WriteColorTable(quantized, stream); this.WriteImageData(quantized, stream); - quantized?.Dispose(); + quantized.Dispose(); quantized = null; // So next frame can regenerate it previousFrame = frame; - previousMeta = frameMetaData; + previousMeta = frameMetadata; } } /// /// Returns the index of the most transparent color in the palette. /// - /// - /// The quantized. - /// + /// The quantized frame. /// The pixel format. /// /// The . /// - private int GetTransparentIndex(QuantizedFrame quantized) - where TPixel : struct, IPixel + private int GetTransparentIndex(IndexedImageFrame quantized) + where TPixel : unmanaged, IPixel { - // Transparent pixels are much more likely to be found at the end of a palette + // Transparent pixels are much more likely to be found at the end of a palette. int index = -1; - int length = quantized.Palette.Length; + ReadOnlySpan paletteSpan = quantized.Palette.Span; - using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(length)) - { - Span rgbaSpan = rgbaBuffer.GetSpan(); - ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette, rgbaSpan); + using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteSpan.Length); + Span rgbaSpan = rgbaOwner.GetSpan(); + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan); + ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan); - for (int i = quantized.Palette.Length - 1; i >= 0; i--) + for (int i = rgbaSpan.Length - 1; i >= 0; i--) + { + if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default)) { - if (Unsafe.Add(ref paletteRef, i).Equals(default)) - { - index = i; - } + index = i; } } @@ -245,19 +261,19 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The stream to write to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length); + private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber); /// /// Writes the logical screen descriptor to the stream. /// - /// The image metadata. + /// 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, + ImageMetadata metadata, int width, int height, int transparencyIndex, @@ -277,10 +293,10 @@ namespace SixLabors.ImageSharp.Formats.Gif // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 byte ratio = 0; - if (metaData.ResolutionUnits == PixelResolutionUnit.AspectRatio) + if (metadata.ResolutionUnits == PixelResolutionUnit.AspectRatio) { - double hr = metaData.HorizontalResolution; - double vr = metaData.VerticalResolution; + double hr = metadata.HorizontalResolution; + double vr = metadata.VerticalResolution; if (hr != vr) { if (hr > vr) @@ -326,42 +342,71 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The metadata to be extract the comment data. /// The stream to write to. - private void WriteComments(ImageMetaData metadata, Stream stream) + private void WriteComments(GifMetadata metadata, Stream stream) { - if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) - || string.IsNullOrEmpty(property.Value)) + if (metadata.Comments.Count == 0) { return; } - byte[] comments = this.textEncoding.GetBytes(property.Value); + for (var i = 0; i < metadata.Comments.Count; i++) + { + string comment = metadata.Comments[i]; + this.buffer[0] = GifConstants.ExtensionIntroducer; + this.buffer[1] = GifConstants.CommentLabel; + stream.Write(this.buffer, 0, 2); + + // 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) + { + WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength); + } - int count = Math.Min(comments.Length, 255); + // Write the length bytes, if any, to another sub block. + if (idx < comment.Length) + { + int remaining = comment.Length - idx; + WriteCommentSubBlock(stream, commentSpan, idx, remaining); + } - this.buffer[0] = GifConstants.ExtensionIntroducer; - this.buffer[1] = GifConstants.CommentLabel; - this.buffer[2] = (byte)count; + stream.WriteByte(GifConstants.Terminator); + } + } - 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 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) + private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int transparencyIndex, Stream stream) { byte packedValue = GifGraphicControlExtension.GetPackedValue( - disposalMethod: metaData.DisposalMethod, + disposalMethod: metadata.DisposalMethod, transparencyFlag: transparencyIndex > -1); var extension = new GifGraphicControlExtension( packed: packedValue, - delayTime: (ushort)metaData.FrameDelay, + delayTime: (ushort)metadata.FrameDelay, transparencyIndex: unchecked((byte)transparencyIndex)); this.WriteExtension(extension, stream); @@ -372,7 +417,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The extension to write to the stream. /// The stream to write to. - public void WriteExtension(IGifExtension extension, Stream stream) + private void WriteExtension(TGifExtension extension, Stream stream) + where TGifExtension : struct, IGifExtension { this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[1] = extension.Label; @@ -392,7 +438,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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 + where TPixel : unmanaged, IPixel { byte packedValue = GifImageDescriptor.GetPackedValue( localColorTableFlag: hasColorTable, @@ -418,37 +464,33 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The to encode. /// The stream to write to. - private void WriteColorTable(QuantizedFrame image, Stream stream) - where TPixel : struct, IPixel + private void WriteColorTable(IndexedImageFrame image, Stream stream) + where TPixel : unmanaged, IPixel { // The maximum number of colors for the bit depth - int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; - int pixelCount = image.Palette.Length; + int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf(); - using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - { - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - image.Palette.AsSpan(), - colorTable.GetSpan(), - pixelCount); - stream.Write(colorTable.Array, 0, colorTableLength); - } + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean); + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + image.Palette.Span, + colorTable.GetSpan(), + image.Palette.Length); + + stream.Write(colorTable.Array, 0, colorTableLength); } /// /// Writes the image pixel data to the stream. /// /// The pixel format. - /// The containing indexed pixels. + /// The containing indexed pixels. /// The stream to write to. - private void WriteImageData(QuantizedFrame image, Stream stream) - where TPixel : struct, IPixel + private void WriteImageData(IndexedImageFrame image, Stream stream) + where TPixel : unmanaged, IPixel { - using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth)) - { - encoder.Encode(image.GetPixelSpan(), stream); - } + using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth); + encoder.Encode(((IPixelSource)image).PixelBuffer, stream); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index 07d4a54547..4ff53a4099 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Registers the image encoders, decoders and mime type detectors for the gif format. /// - public sealed class GifFormat : IImageFormat + public sealed class GifFormat : IImageFormat { private GifFormat() { @@ -32,9 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Gif public IEnumerable FileExtensions => GifConstants.FileExtensions; /// - public GifMetaData CreateDefaultFormatMetaData() => new GifMetaData(); + public GifMetadata CreateDefaultFormatMetadata() => new GifMetadata(); /// - public GifFrameMetaData CreateDefaultFormatFrameMetaData() => new GifFrameMetaData(); + 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 deleted file mode 100644 index 0042c6a108..0000000000 --- a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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 new file mode 100644 index 0000000000..c254a7650d --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// 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); + } +} diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs index b8f9a03f1a..3b3dd0bf1c 100644 --- a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Gif/GifMetaData.cs b/src/ImageSharp/Formats/Gif/GifMetaData.cs deleted file mode 100644 index bb7fb50518..0000000000 --- a/src/ImageSharp/Formats/Gif/GifMetaData.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 new file mode 100644 index 0000000000..686288a980 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +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; + + 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 GifColorTableMode ColorTableMode { get; set; } + + /// + /// Gets or sets the length of the global color table if present. + /// + public int GlobalColorTableLength { get; set; } + + /// + /// Gets or sets the 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; } = new List(); + + /// + public IDeepCloneable DeepClone() => new GifMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Gif/GifThrowHelper.cs b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs new file mode 100644 index 0000000000..b85bb139ad --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Gif +{ + internal static class GifThrowHelper + { + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); + + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + /// The exception that is the cause of the current exception, or a null reference + /// if no inner exception is specified. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException); + } +} diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs index 42c202a3d9..56bb6d6519 100644 --- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Text; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Gif { @@ -17,12 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Gif 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 + /// 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 index 4b3c28a92c..909d1c3a72 100644 --- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs @@ -1,7 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Text; +using System.Collections.Generic; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Gif @@ -11,11 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// internal interface IGifEncoderOptions { - /// - /// Gets the text encoding used to write comments. - /// - Encoding TextEncoding { get; } - /// /// Gets the quantizer used to generate the color palette. /// @@ -25,5 +22,10 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Gets the color table mode: Global or local. /// GifColorTableMode? ColorTableMode { get; } + + /// + /// Gets the used for quantization when building a global color table. + /// + IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Gif/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs deleted file mode 100644 index 8ddd4247e1..0000000000 --- a/src/ImageSharp/Formats/Gif/ImageExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 07594e81a1..9eaa55566b 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -1,14 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.IO; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Gif { @@ -30,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The stream to decode. /// - private readonly Stream stream; + private readonly BufferedReadStream stream; /// /// The prefix buffer. @@ -53,8 +51,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The to use for buffer allocations. /// The stream to read from. - /// is null. - public LzwDecoder(MemoryAllocator memoryAllocator, Stream stream) + /// is null. + public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream) { this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); @@ -66,15 +64,15 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// 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) + public void DecodePixels(int dataSize, Buffer2D pixels) { Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); // The resulting index table length. + int width = pixels.Width; + int height = pixels.Height; int length = width * height; // Calculate the clear code. The value of the clear code is 2 ^ dataSize @@ -106,21 +104,28 @@ namespace SixLabors.ImageSharp.Formats.Gif 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); for (code = 0; code < clearCode; code++) { Unsafe.Add(ref suffixRef, code) = (byte)code; } -#if NETCOREAPP2_1 - Span buffer = stackalloc byte[255]; -#else - byte[] buffer = new byte[255]; -#endif + Span buffer = stackalloc byte[byte.MaxValue]; + int y = 0; + int x = 0; + int rowMax = width; + ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(y)); while (xyz < length) { + // Reset row reference. + if (xyz == rowMax) + { + x = 0; + pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(++y)); + rowMax = (y * width) + width; + } + if (top == 0) { if (bits < codeSize) @@ -214,7 +219,8 @@ namespace SixLabors.ImageSharp.Formats.Gif top--; // Clear missing pixels - Unsafe.Add(ref pixelsRef, xyz++) = (byte)Unsafe.Add(ref pixelStackRef, top); + xyz++; + Unsafe.Add(ref pixelsRowRef, x++) = (byte)Unsafe.Add(ref pixelStackRef, top); } } @@ -227,11 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] -#if NETCOREAPP2_1 private int ReadBlock(Span buffer) -#else - private int ReadBlock(byte[] buffer) -#endif { int bufferSize = this.stream.ReadByte(); @@ -253,4 +255,4 @@ namespace SixLabors.ImageSharp.Formats.Gif this.pixelStack.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index 2d32fd23aa..195a84a1d4 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Gif { @@ -42,13 +41,33 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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 = { - 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, - 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF + 0b0, + 0b1, + 0b11, + 0b111, + 0b1111, + 0b11111, + 0b111111, + 0b1111111, + 0b11111111, + 0b111111111, + 0b1111111111, + 0b11111111111, + 0b111111111111, + 0b1111111111111, + 0b11111111111111, + 0b111111111111111, + 0b1111111111111111 }; /// @@ -81,16 +100,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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 /// @@ -178,15 +187,13 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Encodes and compresses the indexed pixels to the stream. /// - /// The span of indexed pixels. + /// The 2D buffer of indexed pixels. /// The stream to write to. - public void Encode(Span indexedPixels, Stream stream) + public void Encode(Buffer2D indexedPixels, Stream stream) { // Write "initial code size" byte stream.WriteByte((byte)this.initialCodeSize); - this.position = 0; - // Compress and write the pixel data this.Compress(indexedPixels, this.initialCodeSize + 1, stream); @@ -200,10 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The number of bits /// See [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetMaxcode(int bitCount) - { - return (1 << bitCount) - 1; - } + private static int GetMaxcode(int bitCount) => (1 << bitCount) - 1; /// /// Add a character to the end of the current packet, and if it is 254 characters, @@ -240,118 +244,99 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Reset the code table. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ResetCodeTable() - { - this.hashTable.GetSpan().Fill(-1); - } + private void ResetCodeTable() => this.hashTable.GetSpan().Fill(-1); /// /// Compress the packets to the stream. /// - /// The span of indexed pixels. - /// The initial bits. + /// The 2D buffer of indexed pixels. + /// The initial bits. /// The stream to write to. - private void Compress(Span indexedPixels, int intialBits, Stream stream) + private void Compress(Buffer2D indexedPixels, int initialBits, Stream stream) { - int fcode; - int c; - int ent; - int hsizeReg; - int hshift; - // Set up the globals: globalInitialBits - initial number of bits - this.globalInitialBits = intialBits; + this.globalInitialBits = initialBits; // Set up the necessary values this.clearFlag = false; this.bitCount = this.globalInitialBits; this.maxCode = GetMaxcode(this.bitCount); - - this.clearCode = 1 << (intialBits - 1); + this.clearCode = 1 << (initialBits - 1); this.eofCode = this.clearCode + 1; this.freeEntry = this.clearCode + 2; + this.accumulatorCount = 0; // Clear packet - this.accumulatorCount = 0; // clear packet - - ent = this.NextPixel(indexedPixels); - - // TODO: PERF: It looks likt hshift could be calculated once statically. - hshift = 0; - for (fcode = this.hsize; fcode < 65536; fcode *= 2) - { - ++hshift; - } - - hshift = 8 - hshift; // set hash code range bound - - hsizeReg = this.hsize; - - this.ResetCodeTable(); // clear hash table - + this.ResetCodeTable(); // Clear hash table this.Output(this.clearCode, stream); ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); - while (this.position < indexedPixels.Length) - { - c = this.NextPixel(indexedPixels); + int entry = indexedPixels[0, 0]; - fcode = (c << MaxBits) + ent; - int i = (c << hshift) ^ ent /* = 0 */; + for (int y = 0; y < indexedPixels.Height; y++) + { + ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.GetRowSpan(y)); + int offsetX = y == 0 ? 1 : 0; - if (Unsafe.Add(ref hashTableRef, i) == fcode) + for (int x = offsetX; x < indexedPixels.Width; x++) { - ent = Unsafe.Add(ref codeTableRef, i); - continue; - } + int code = Unsafe.Add(ref rowSpanRef, x); + int freeCode = (code << MaxBits) + entry; + int hashIndex = (code << HashShift) ^ entry; - // Non-empty slot - if (Unsafe.Add(ref hashTableRef, i) >= 0) - { - int disp = 1; - if (i != 0) + if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) { - disp = hsizeReg - i; + entry = Unsafe.Add(ref codeTableRef, hashIndex); + continue; } - do + // Non-empty slot + if (Unsafe.Add(ref hashTableRef, hashIndex) >= 0) { - if ((i -= disp) < 0) + int disp = 1; + if (hashIndex != 0) + { + disp = HashSize - hashIndex; + } + + do { - i += hsizeReg; + if ((hashIndex -= disp) < 0) + { + hashIndex += HashSize; + } + + if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) + { + entry = Unsafe.Add(ref codeTableRef, hashIndex); + break; + } } + while (Unsafe.Add(ref hashTableRef, hashIndex) >= 0); - if (Unsafe.Add(ref hashTableRef, i) == fcode) + if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) { - ent = Unsafe.Add(ref codeTableRef, i); - break; + continue; } } - while (Unsafe.Add(ref hashTableRef, i) >= 0); - if (Unsafe.Add(ref hashTableRef, i) == fcode) + this.Output(entry, stream); + entry = code; + if (this.freeEntry < MaxMaxCode) { - continue; + Unsafe.Add(ref codeTableRef, hashIndex) = this.freeEntry++; // code -> hashtable + Unsafe.Add(ref hashTableRef, hashIndex) = freeCode; + } + else + { + this.ClearBlock(stream); } - } - - this.Output(ent, stream); - ent = c; - if (this.freeEntry < MaxMaxCode) - { - Unsafe.Add(ref codeTableRef, i) = this.freeEntry++; // code -> hashtable - Unsafe.Add(ref hashTableRef, i) = fcode; - } - else - { - this.ClearBlock(stream); } } - // Put out the final code. - this.Output(ent, stream); - + // Output the final code. + this.Output(entry, stream); this.Output(this.eofCode, stream); } @@ -367,19 +352,6 @@ namespace SixLabors.ImageSharp.Formats.Gif this.accumulatorCount = 0; } - /// - /// Reads the next pixel from the image. - /// - /// The span of indexed pixels. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int NextPixel(Span indexedPixels) - { - return indexedPixels[this.position++] & 0xFF; - } - /// /// Output the current code to the stream. /// @@ -450,4 +422,4 @@ namespace SixLabors.ImageSharp.Formats.Gif this.codeTable?.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs new file mode 100644 index 0000000000..2e8dda5c05 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the gif format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static GifMetadata GetGifMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(GifFormat.Instance); + + /// + /// Gets the gif format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static GifFrameMetadata GetGifMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(GifFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Gif/README.md b/src/ImageSharp/Formats/Gif/README.md index d47a4c6836..eeda20c06a 100644 --- a/src/ImageSharp/Formats/Gif/README.md +++ b/src/ImageSharp/Formats/Gif/README.md @@ -1,4 +1,6 @@ -Encoder/Decoder adapted and extended from: +Encoder/Decoder adapted and extended from: -https://github.com/yufeih/Nine.Imaging/ -https://imagetools.codeplex.com/ +- [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 diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs index cb548d687d..77b32f77d7 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs index c3504dfe7b..68b0484826 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs index 1cfec4763a..88c13d2035 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs index 49a52edf6a..26faa8925e 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -29,11 +29,11 @@ namespace SixLabors.ImageSharp.Formats.Gif buffer[0] = GifConstants.ApplicationBlockSize; // Write NETSCAPE2.0 - GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11)); + GifConstants.NetscapeApplicationIdentificationBytes.CopyTo(buffer.Slice(1, 11)); // Application Data ---- buffer[12] = 3; // Application block length (always 3) - buffer[13] = 1; // Data sub-block indentity (always 1) + buffer[13] = 1; // Data sub-block identity (always 1) // 0 means loop indefinitely. Count is set as play n + 1 times. BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount); diff --git a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs index c8bd286746..bec1881230 100644 --- a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index ffc40314d8..b55f1119b3 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -1,7 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats @@ -12,13 +14,45 @@ namespace SixLabors.ImageSharp.Formats public interface IImageDecoder { /// - /// Decodes the image from the specified stream to the . + /// Decodes the image from the specified stream to an of a specific pixel type. /// /// The pixel format. /// The configuration for the image. /// The containing image data. - /// The decoded image + /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; + + /// + /// Decodes the image from the specified stream to an . + /// + /// The configuration for the image. + /// The containing image data. + /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) + Image Decode(Configuration configuration, Stream stream); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The configuration for the image. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) + Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + + /// + /// Decodes the image from the specified stream to an . + /// + /// The configuration for the image. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) + Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageDecoderInternals.cs b/src/ImageSharp/Formats/IImageDecoderInternals.cs new file mode 100644 index 0000000000..e190f7adda --- /dev/null +++ b/src/ImageSharp/Formats/IImageDecoderInternals.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Abstraction for shared internals for ***DecoderCore implementations to be used with . + /// + internal interface IImageDecoderInternals + { + /// + /// Gets the associated configuration. + /// + Configuration Configuration { get; } + + /// + /// Gets the dimensions of the image being decoded. + /// + Size Dimensions { get; } + + /// + /// 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. + /// + Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + + /// + /// 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. + /// + IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 76d831d5aa..e5a1b1c839 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -1,7 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats @@ -18,6 +20,17 @@ namespace SixLabors.ImageSharp.Formats /// The to encode from. /// The to encode the image data to. void Encode(Image image, Stream stream) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; + + /// + /// 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 monitor for cancellation requests. + /// A representing the asynchronous operation. + Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Formats/IImageEncoderInternals.cs b/src/ImageSharp/Formats/IImageEncoderInternals.cs new file mode 100644 index 0000000000..d44ac45f27 --- /dev/null +++ b/src/ImageSharp/Formats/IImageEncoderInternals.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Abstraction for shared internals for ***DecoderCore implementations to be used with . + /// + internal interface IImageEncoderInternals + { + /// + /// Encodes the image. + /// + /// The image. + /// The stream. + /// The token to monitor for cancellation requests. + /// The pixel type. + void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + } +} diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs index 3cd289df76..06b96caadc 100644 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -34,30 +34,30 @@ namespace SixLabors.ImageSharp.Formats /// /// Defines the contract for an image format containing metadata. /// - /// The type of format metadata. - public interface IImageFormat : IImageFormat - where TFormatMetaData : class + /// The type of format metadata. + public interface IImageFormat : IImageFormat + where TFormatMetadata : class { /// /// Creates a default instance of the format metadata. /// - /// The . - TFormatMetaData CreateDefaultFormatMetaData(); + /// 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 + /// 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(); + /// The . + TFormatFrameMetadata CreateDefaultFormatFrameMetadata(); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/IImageFormatDetector.cs b/src/ImageSharp/Formats/IImageFormatDetector.cs index da3730d207..96c3a49afe 100644 --- a/src/ImageSharp/Formats/IImageFormatDetector.cs +++ b/src/ImageSharp/Formats/IImageFormatDetector.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats /// Detect mimetype ///
/// The containing the file header. - /// returns the mime type of detected othersie returns null + /// returns the mime type of detected otherwise returns null IImageFormat DetectFormat(ReadOnlySpan header); } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index b7769e8955..6f5fc23338 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -1,7 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace SixLabors.ImageSharp.Formats { @@ -17,5 +19,14 @@ namespace SixLabors.ImageSharp.Formats /// The containing image data. /// The object IImageInfo Identify(Configuration configuration, Stream stream); + + /// + /// Reads the raw image information from the specified stream. + /// + /// The configuration for the image. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The object + Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs new file mode 100644 index 0000000000..5d77fb0c8c --- /dev/null +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -0,0 +1,175 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + internal static class ImageDecoderUtilities + { + /// + /// Reads the raw image information from the specified stream. + /// + /// The decoder. + /// /// The configuration for the image. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// is null. + /// A representing the asynchronous operation. + public static Task IdentifyAsync( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + CancellationToken cancellationToken) + => decoder.IdentifyAsync(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); + + /// + /// Reads the raw image information from the specified stream. + /// + /// The decoder. + /// The configuration for the image. + /// The containing image data. + /// Factory method to handle as . + /// The token to monitor for cancellation requests. + /// is null. + /// A representing the asynchronous operation. + public static Task IdentifyAsync( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + Func tooLargeImageExceptionFactory, + CancellationToken cancellationToken) + { + try + { + using var bufferedReadStream = new BufferedReadStream(configuration, stream); + IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken); + return Task.FromResult(imageInfo); + } + catch (InvalidMemoryOperationException ex) + { + InvalidImageContentException invalidImageContentException = tooLargeImageExceptionFactory(ex, decoder.Dimensions); + return Task.FromException(invalidImageContentException); + } + catch (OperationCanceledException) + { + return Task.FromCanceled(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + /// Decodes the image from the specified stream. + /// + /// The pixel format. + /// The decoder. + /// The configuration for the image. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public static Task> DecodeAsync( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel => + decoder.DecodeAsync( + configuration, + stream, + DefaultLargeImageExceptionFactory, + cancellationToken); + + /// + /// Decodes the image from the specified stream. + /// + /// The pixel format. + /// The decoder. + /// The configuration for the image. + /// The containing image data. + /// Factory method to handle as . + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public static Task> DecodeAsync( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + Func largeImageExceptionFactory, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + try + { + using var bufferedReadStream = new BufferedReadStream(configuration, stream); + Image image = decoder.Decode(bufferedReadStream, cancellationToken); + return Task.FromResult(image); + } + catch (InvalidMemoryOperationException ex) + { + InvalidImageContentException invalidImageContentException = largeImageExceptionFactory(ex, decoder.Dimensions); + return Task.FromException>(invalidImageContentException); + } + catch (OperationCanceledException) + { + return Task.FromCanceled>(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException>(ex); + } + } + + public static IImageInfo Identify( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream) + { + using var bufferedReadStream = new BufferedReadStream(configuration, stream); + + try + { + return decoder.Identify(bufferedReadStream, default); + } + catch (InvalidMemoryOperationException ex) + { + throw new InvalidImageContentException(decoder.Dimensions, ex); + } + } + + public static Image Decode(this IImageDecoderInternals decoder, Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory); + + public static Image Decode( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + Func largeImageExceptionFactory) + where TPixel : unmanaged, IPixel + { + using var bufferedReadStream = new BufferedReadStream(configuration, stream); + + try + { + return decoder.Decode(bufferedReadStream, default); + } + catch (InvalidMemoryOperationException ex) + { + throw largeImageExceptionFactory(ex, decoder.Dimensions); + } + } + + private static InvalidImageContentException DefaultLargeImageExceptionFactory( + InvalidMemoryOperationException memoryOperationException, + Size dimensions) => + new InvalidImageContentException(dimensions, memoryOperationException); + } +} diff --git a/src/ImageSharp/Formats/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs new file mode 100644 index 0000000000..896fffa6fc --- /dev/null +++ b/src/ImageSharp/Formats/ImageEncoderUtilities.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + internal static class ImageEncoderUtilities + { + public static async Task EncodeAsync( + this IImageEncoderInternals encoder, + Image image, + Stream stream, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Configuration configuration = image.GetConfiguration(); + if (stream.CanSeek) + { + await DoEncodeAsync(stream).ConfigureAwait(false); + } + else + { + using var ms = new MemoryStream(); + await DoEncodeAsync(ms); + ms.Position = 0; + await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken) + .ConfigureAwait(false); + } + + Task DoEncodeAsync(Stream innerStream) + { + try + { + encoder.Encode(image, innerStream, cancellationToken); + return Task.CompletedTask; + } + catch (OperationCanceledException) + { + return Task.FromCanceled(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + } + + public static void Encode( + this IImageEncoderInternals encoder, + Image image, + Stream stream) + where TPixel : unmanaged, IPixel + => encoder.Encode(image, stream, default); + } +} diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs new file mode 100644 index 0000000000..075c708b6a --- /dev/null +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -0,0 +1,539 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; + +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tga; + +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, null); + + /// + /// 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, null); + + /// + /// 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, null, 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.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(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, null); + + /// + /// 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, null, 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. + /// A representing the asynchronous operation. + public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.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, null); + + /// + /// 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, null); + + /// + /// 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, null, 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.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(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, null); + + /// + /// 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, null, 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. + /// A representing the asynchronous operation. + public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.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, null); + + /// + /// 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, null); + + /// + /// 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, null, 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.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(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, null); + + /// + /// 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, null, 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. + /// A representing the asynchronous operation. + public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.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, null); + + /// + /// 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, null); + + /// + /// 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, null, 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.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(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, null); + + /// + /// 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, null, 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. + /// A representing the asynchronous operation. + public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.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, null); + + /// + /// 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, null); + + /// + /// 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, null, 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.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(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, null); + + /// + /// 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, null, 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. + /// A representing the asynchronous operation. + public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(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.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), + cancellationToken); + + } +} diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt new file mode 100644 index 0000000000..63b404cc44 --- /dev/null +++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt @@ -0,0 +1,149 @@ +<#@ template language="C#" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; + +<# + var formats = new []{ + "Bmp", + "Gif", + "Jpeg", + "Png", + "Tga", + }; + + 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, null); + + /// + /// 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, null); + + /// + /// 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, null, 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.GetConfiguration().ImageFormatsManager.FindEncoder(<#= 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.GetConfiguration().ImageFormatsManager.FindEncoder(<#= 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, null); + + /// + /// 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, null, 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. + /// A representing the asynchronous operation. + public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= 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.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), + cancellationToken); + +<# + } +#> + } +} diff --git a/src/ImageSharp/Formats/ImageFormatManager.cs b/src/ImageSharp/Formats/ImageFormatManager.cs index e62805d478..2e18745925 100644 --- a/src/ImageSharp/Formats/ImageFormatManager.cs +++ b/src/ImageSharp/Formats/ImageFormatManager.cs @@ -1,201 +1,201 @@ -// 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); - } - } -} +// Copyright (c) Six Labors. +// 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 3423fb3159..a5967a02e4 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.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 60fec25d29..bc6036903b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public Block8x8(Span coefficients) { ref byte selfRef = ref Unsafe.As(ref this); - ref byte sourceRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(coefficients)); + ref byte sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(coefficients)); Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); } @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ///
public short[] ToArray() { - short[] result = new short[Size]; + var result = new short[Size]; this.CopyTo(result); return result; } @@ -297,4 +297,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 deleted file mode 100644 index 6bf9c8483a..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs +++ /dev/null @@ -1,142 +0,0 @@ -// 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 index 1f47de594c..f6f5903684 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -95,36 +95,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ///
public void NormalizeColorsInplace(float maximum) { - Vector4 CMin4 = new Vector4(0F); - Vector4 CMax4 = new Vector4(maximum); - Vector4 COff4 = new Vector4((float)Math.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); + var CMin4 = new Vector4(0F); + var CMax4 = new Vector4(maximum); + var COff4 = new Vector4(MathF.Ceiling(maximum / 2)); + + this.V0L = Vector4Utilities.FastClamp(this.V0L + COff4, CMin4, CMax4); + this.V0R = Vector4Utilities.FastClamp(this.V0R + COff4, CMin4, CMax4); + this.V1L = Vector4Utilities.FastClamp(this.V1L + COff4, CMin4, CMax4); + this.V1R = Vector4Utilities.FastClamp(this.V1R + COff4, CMin4, CMax4); + this.V2L = Vector4Utilities.FastClamp(this.V2L + COff4, CMin4, CMax4); + this.V2R = Vector4Utilities.FastClamp(this.V2R + COff4, CMin4, CMax4); + this.V3L = Vector4Utilities.FastClamp(this.V3L + COff4, CMin4, CMax4); + this.V3R = Vector4Utilities.FastClamp(this.V3R + COff4, CMin4, CMax4); + this.V4L = Vector4Utilities.FastClamp(this.V4L + COff4, CMin4, CMax4); + this.V4R = Vector4Utilities.FastClamp(this.V4R + COff4, CMin4, CMax4); + this.V5L = Vector4Utilities.FastClamp(this.V5L + COff4, CMin4, CMax4); + this.V5R = Vector4Utilities.FastClamp(this.V5R + COff4, CMin4, CMax4); + this.V6L = Vector4Utilities.FastClamp(this.V6L + COff4, CMin4, CMax4); + this.V6R = Vector4Utilities.FastClamp(this.V6R + COff4, CMin4, CMax4); + this.V7L = Vector4Utilities.FastClamp(this.V7L + COff4, CMin4, CMax4); + this.V7R = Vector4Utilities.FastClamp(this.V7R + COff4, CMin4, CMax4); } /// /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2(float maximum) + public void NormalizeColorsAndRoundInplaceVector8(float maximum) { - Vector off = new Vector((float)Math.Ceiling(maximum/2)); - Vector max = new Vector(maximum); + var off = new Vector(MathF.Ceiling(maximum / 2)); + var max = new Vector(maximum); ref Vector row0 = ref Unsafe.As>(ref this.V0L); row0 = NormalizeAndRound(row0, off, max); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt index ec4e06e429..6ee0540213 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -1,5 +1,5 @@ -<# -// Copyright (c) Six Labors and contributors. +<# +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. #> <#@ template debug="false" hostspecific="false" language="C#" #> @@ -8,7 +8,7 @@ <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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"; + var expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord};\r\n"; Write(expression); } } @@ -60,9 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ///
public void NormalizeColorsInplace(float maximum) { - Vector4 CMin4 = new Vector4(0F); - Vector4 CMax4 = new Vector4(maximum); - Vector4 COff4 = new Vector4((float)Math.Ceiling(maximum/2)); + var CMin4 = new Vector4(0F); + var CMax4 = new Vector4(maximum); + var COff4 = new Vector4(MathF.Ceiling(maximum / 2)); <# @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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"); + Write($"this.V{i}{side} = Vector4Utilities.FastClamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); } } PopIndent(); @@ -84,10 +84,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// AVX2-only variant for executing and in one step. ///
[MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2(float maximum) + public void NormalizeColorsAndRoundInplaceVector8(float maximum) { - Vector off = new Vector((float)Math.Ceiling(maximum/2)); - Vector max = new Vector(maximum); + var off = new Vector(MathF.Ceiling(maximum / 2)); + var max = new Vector(maximum); <# for (int i = 0; i < 8; i++) @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { char destCoord = coordz[i % 4]; char destSide = (i / 4) % 2 == 0 ? 'L' : 'R'; - + if(j > 0 && i == 0){ WriteLine(""); } @@ -125,9 +125,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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"; + var 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.ScaledCopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs new file mode 100644 index 0000000000..23cf4ce4a9 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors. +// 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 ScaledCopyTo(in Buffer2DRegion region, int horizontalScale, int verticalScale) + { + ref float areaOrigin = ref region.GetReferenceToOrigin(); + this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + this.Copy1x1Scale(ref areaOrigin, areaStride); + return; + } + + if (horizontalScale == 2 && verticalScale == 2) + { + this.Copy2x2Scale(ref areaOrigin, areaStride); + return; + } + + // TODO: Optimize: implement all cases with scale-specific, loopless code! + this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); + } + + public void Copy1x1Scale(ref float areaOrigin, int areaStride) + { + ref byte selfBase = ref Unsafe.As(ref this); + ref byte destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride * 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(ref float areaOrigin, int areaStride) + { + ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride / 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(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + 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) * areaStride) + xx; + + for (int 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/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index c9c886f057..b7835d6706 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Represents a Jpeg block with coefficients. /// - internal partial struct Block8x8F + internal partial struct Block8x8F : IEquatable { /// /// A number of scalar coefficients in a @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Fill the block with defaults (zeroes) + /// Fill the block with defaults (zeroes). /// [MethodImpl(InliningOptions.ShortMethod)] public void Clear() @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Load raw 32bit floating point data from source + /// Load raw 32bit floating point data from source. /// /// Source [MethodImpl(InliningOptions.ShortMethod)] @@ -170,7 +170,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Load raw 32bit floating point data from source + /// Load raw 32bit floating point data from source. /// /// Block pointer /// Source @@ -197,11 +197,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Copy raw 32bit floating point data to dest + /// Copy raw 32bit floating point data to dest, /// /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public void CopyTo(Span dest) + public void ScaledCopyTo(Span dest) { ref byte d = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); ref byte s = ref Unsafe.As(ref this); @@ -210,12 +210,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Convert salars to byte-s and copy to dest + /// Convert scalars to byte-s and copy to dest, /// /// Pointer to block /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) + public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) { float* fPtr = (float*)blockPtr; for (int i = 0; i < Size; i++) @@ -226,14 +226,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Copy raw 32bit floating point data to dest + /// Copy raw 32bit floating point data to dest. /// - /// Block pointer - /// Destination + /// The block pointer. + /// The destination. [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) + public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) { - blockPtr->CopyTo(dest); + blockPtr->ScaledCopyTo(dest); } /// @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void CopyTo(float[] dest) + public unsafe void ScaledCopyTo(float[] dest) { fixed (void* ptr = &this.V0L) { @@ -253,11 +253,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Copy raw 32bit floating point data to dest /// /// Destination - public unsafe void CopyTo(Span dest) + public unsafe void ScaledCopyTo(Span dest) { fixed (Vector4* ptr = &this.V0L) { - float* fp = (float*)ptr; + var fp = (float*)ptr; for (int i = 0; i < Size; i++) { dest[i] = (int)fp[i]; @@ -267,15 +267,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public float[] ToArray() { - float[] result = new float[Size]; - this.CopyTo(result); + var result = new float[Size]; + this.ScaledCopyTo(result); return result; } /// /// Multiply all elements of the block. /// - /// The value to multiply by + /// The value to multiply by. [MethodImpl(InliningOptions.ShortMethod)] public void MultiplyInplace(float value) { @@ -298,7 +298,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Multiply all elements of the block by the corresponding elements of 'other' + /// Multiply all elements of the block by the corresponding elements of 'other'. /// [MethodImpl(InliningOptions.ShortMethod)] public void MultiplyInplace(ref Block8x8F other) @@ -349,8 +349,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Quantize the block. /// - /// Block pointer - /// Qt pointer + /// The block pointer. + /// The qt pointer. /// Unzig pointer // [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, byte* unzigPtr) @@ -376,22 +376,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Source block /// Destination block /// The quantization table - /// Pointer to elements of + /// The 8x8 Unzig block. public static unsafe void Quantize( - Block8x8F* block, - Block8x8F* dest, - Block8x8F* qt, - byte* unzigPtr) + ref Block8x8F block, + ref Block8x8F dest, + ref Block8x8F qt, + ref ZigZag unZig) { - float* s = (float*)block; - float* d = (float*)dest; - for (int zig = 0; zig < Size; zig++) { - d[zig] = s[unzigPtr[zig]]; + dest[zig] = block[unZig[zig]]; } - DivideRoundAll(ref *dest, ref *qt); + DivideRoundAll(ref dest, ref qt); } /// @@ -399,14 +396,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// The destination block. /// The source block. - public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source) + public static unsafe void Scale16X16To8X8(ref Block8x8F destination, ReadOnlySpan source) { - float* d = (float*)destination; for (int i = 0; i < 4; i++) { int dstOff = ((i & 2) << 4) | ((i & 1) << 2); - - float* iSource = (float*)(source + i); + Block8x8F iSource = source[i]; for (int y = 0; y < 4; y++) { @@ -414,7 +409,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { 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; + destination[(8 * y) + x + dstOff] = (sum + 2) * .25F; } } } @@ -471,9 +466,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ///
public void NormalizeColorsAndRoundInplace(float maximum) { - if (SimdUtils.IsAvx2CompatibleArchitecture) + if (SimdUtils.HasVector8) { - this.NormalizeColorsAndRoundInplaceAvx2(maximum); + this.NormalizeColorsAndRoundInplaceVector8(maximum); } else { @@ -497,7 +492,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public void LoadFrom(ref Block8x8 source) { #if SUPPORTS_EXTENDED_INTRINSICS - if (SimdUtils.IsAvx2CompatibleArchitecture) + if (SimdUtils.HasVector8) { this.LoadFromInt16ExtendedAvx2(ref source); return; @@ -513,7 +508,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public void LoadFromInt16ExtendedAvx2(ref Block8x8 source) { DebugGuard.IsTrue( - SimdUtils.IsAvx2CompatibleArchitecture, + SimdUtils.HasVector8, "LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!"); ref Vector sRef = ref Unsafe.As>(ref source); @@ -538,6 +533,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components Unsafe.Add(ref dRef, 7) = bottom; } + /// + public bool Equals(Block8x8F other) + { + 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; + } + /// public override string ToString() { @@ -568,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { // sign(dividend) = max(min(dividend, 1), -1) - var sign = Vector4.Clamp(dividend, NegativeOne, Vector4.One); + Vector4 sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One); // AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend) return (dividend / divisor) + (sign * Offset); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs index 43de39ac78..00ab48e25d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Gets the horizontal downsampling hint used for DCT encoding /// 0x0 : (none - Chop) - /// Bit 15 : Encoded with Blend=1 downsampling + /// Bit 15 : Encoded with Blend=1 downsampling. /// public short APP14Flags0 { get; } @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// 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 byte array containing metadata to parse. /// The marker to return. public static bool TryParse(byte[] bytes, out AdobeMarker marker) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs index d4dc31fe0c..7b257b37da 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromCmyk : JpegColorConverter + internal sealed class FromCmyk : JpegColorConverter { public FromCmyk(int precision) : base(JpegColorSpace.Cmyk, precision) @@ -25,11 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4( - 1 / this.MaximumValue, - 1 / this.MaximumValue, - 1 / this.MaximumValue, - 1F); + var maximum = 1 / this.MaximumValue; + var scale = new Vector4(maximum, maximum, maximum, 1F); for (int i = 0; i < result.Length; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs index 4a5dfa6322..cf0bc2c920 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromGrayscale : JpegColorConverter + internal sealed class FromGrayscale : JpegColorConverter { public FromGrayscale(int precision) : base(JpegColorSpace.Grayscale, precision) @@ -19,11 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters public override void ConvertToRgba(in ComponentValues values, Span result) { - var scale = new Vector4( - 1 / this.MaximumValue, - 1 / this.MaximumValue, - 1 / this.MaximumValue, - 1F); + var maximum = 1 / this.MaximumValue; + var scale = new Vector4(maximum, maximum, maximum, 1F); ref float sBase = ref MemoryMarshal.GetReference(values.Component0); ref Vector4 dBase = ref MemoryMarshal.GetReference(result); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs index 516dfb39fe..25889a6dfc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromRgb : JpegColorConverter + internal sealed class FromRgb : JpegColorConverter { public FromRgb(int precision) : base(JpegColorSpace.RGB, precision) @@ -24,11 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1); - var scale = new Vector4( - 1 / this.MaximumValue, - 1 / this.MaximumValue, - 1 / this.MaximumValue, - 1F); + var maximum = 1 / this.MaximumValue; + var scale = new Vector4(maximum, maximum, maximum, 1F); for (int i = 0; i < result.Length; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs index 124aac1224..31fc054619 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromYCbCrBasic : JpegColorConverter + internal sealed class FromYCbCrBasic : JpegColorConverter { public FromYCbCrBasic(int precision) : base(JpegColorSpace.YCbCr, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs index 10ef02a931..541a03615e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromYCbCrSimd : JpegColorConverter + internal sealed class FromYCbCrSimd : JpegColorConverter { public FromYCbCrSimd(int precision) : base(JpegColorSpace.YCbCr, precision) @@ -90,21 +90,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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); + r.RoundAndDownscalePreVector8(maxValue); + g.RoundAndDownscalePreVector8(maxValue); + b.RoundAndDownscalePreVector8(maxValue); } - else if (SimdUtils.IsAvx2CompatibleArchitecture) + else if (SimdUtils.HasVector8) { - r.RoundAndDownscaleAvx2(maxValue); - g.RoundAndDownscaleAvx2(maxValue); - b.RoundAndDownscaleAvx2(maxValue); + r.RoundAndDownscaleVector8(maxValue); + g.RoundAndDownscaleVector8(maxValue); + b.RoundAndDownscaleVector8(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!"); + JpegThrowHelper.ThrowNotImplementedException("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: @@ -114,4 +114,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ 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 index 9953f78c18..c4d1408a2e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,14 +13,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromYCbCrSimdAvx2 : JpegColorConverter + internal sealed class FromYCbCrSimdVector8 : JpegColorConverter { - public FromYCbCrSimdAvx2(int precision) + public FromYCbCrSimdVector8(int precision) : base(JpegColorSpace.YCbCr, precision) { } - public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture; + public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.HasVector8; public override void ConvertToRgba(in ComponentValues values, Span result) { @@ -107,4 +107,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ 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 index 94be11e237..1137cdc0ec 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromYccK : JpegColorConverter + internal sealed class FromYccK : JpegColorConverter { public FromYccK(int precision) : base(JpegColorSpace.Ycck, precision) @@ -25,11 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4( - 1 / this.MaximumValue, - 1 / this.MaximumValue, - 1 / this.MaximumValue, - 1F); + var maximum = 1 / this.MaximumValue; + var scale = new Vector4(maximum, maximum, maximum, 1F); for (int i = 0; i < result.Length; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index c2e390c590..f68bca0412 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -42,8 +42,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { this.ColorSpace = colorSpace; this.Precision = precision; - this.MaximumValue = (float)Math.Pow(2, precision) - 1; - this.HalfValue = (float)Math.Ceiling(this.MaximumValue / 2); + this.MaximumValue = MathF.Pow(2, precision) - 1; + this.HalfValue = MathF.Ceiling(this.MaximumValue / 2); } /// @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// Returns the corresponding to the given /// - public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, float precision) + public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, int precision) { JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace && c.Precision == precision); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// 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); + FromYCbCrSimdVector8.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdVector8(precision) : new FromYCbCrSimd(precision); /// /// A stack-only struct to reference the input buffers using -s. @@ -232,4 +232,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs deleted file mode 100644 index 06b46746a6..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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 SixLabors.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - /// - /// The collection of lookup tables used for fast AC entropy scan decoding. - /// - internal sealed class FastACTables : IDisposable - { - private Buffer2D tables; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator used to allocate memory for image processing operations. - public FastACTables(MemoryAllocator memoryAllocator) => this.tables = memoryAllocator.Allocate2D(512, 4, AllocationOptions.Clean); - - /// - /// Gets the representing the table at the index in the collection. - /// - /// The table index. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetTableSpan(int index) => this.tables.GetRowSpan(index); - - /// - /// Gets a reference to the first element of the AC table indexed by - /// - /// The frame component. - [MethodImpl(InliningOptions.ShortMethod)] - public ref short GetAcTableReference(JpegComponent component) => ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0]; - - /// - /// Builds a lookup table for fast AC entropy scan decoding. - /// - /// The table index. - /// The collection of AC Huffman tables. - public unsafe void BuildACTableLut(int index, HuffmanTables acHuffmanTables) - { - const int FastBits = ScanDecoder.FastBits; - Span fastAC = this.tables.GetRowSpan(index); - ref HuffmanTable huffmanTable = ref acHuffmanTables[index]; - - int i; - for (i = 0; i < (1 << FastBits); i++) - { - byte fast = huffmanTable.Lookahead[i]; - fastAC[i] = 0; - if (fast < byte.MaxValue) - { - int rs = huffmanTable.Values[fast]; - int run = (rs >> 4) & 15; - int magbits = rs & 15; - int len = huffmanTable.Sizes[fast]; - - if (magbits != 0 && len + magbits <= FastBits) - { - // Magnitude code followed by receive_extend code - int k = ((i << len) & ((1 << FastBits) - 1)) >> (FastBits - magbits); - int m = 1 << (magbits - 1); - if (k < m) - { - k += (int)((~0U << magbits) + 1); - } - - // if the result is small enough, we can fit it in fastAC table - if (k >= -128 && k <= 127) - { - fastAC[i] = (short)((k << 8) + (run << 4) + (len + magbits)); - } - } - } - } - } - - /// - public void Dispose() - { - this.tables?.Dispose(); - this.tables = null; - } - } -} \ No newline at end of file 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..12ea39e37b --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs @@ -0,0 +1,226 @@ +// Copyright (c) Six Labors. +// 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 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; + + public HuffmanScanBuffer(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; + } + + /// + /// 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 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 bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker(); + + [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.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 int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - 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 < 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(); + } + + // We accept multiple FF bytes followed by a 0 as meaning a single FF data byte. + // This data pattern is not valid according to the standard. + if (c != 0) + { + this.Marker = (byte)c; + this.badData = true; + 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.badData = true; + this.MarkerPosition = this.stream.Position - 2; + return true; + } + } + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int ReadStream() + { + int value = this.badData ? 0 : this.stream.ReadByte(); + if (value == -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.badData = true; + this.NoData = true; + value = 0; + } + + return value; + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs new file mode 100644 index 0000000000..6424ee23ac --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -0,0 +1,718 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using SixLabors.ImageSharp.IO; + +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 + { + private readonly JpegFrame frame; + private readonly HuffmanTable[] dcHuffmanTables; + private readonly HuffmanTable[] acHuffmanTables; + private readonly BufferedReadStream 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; + + private CancellationToken cancellationToken; + + /// + /// 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. + /// The token to monitor cancellation. + public HuffmanScanDecoder( + BufferedReadStream stream, + JpegFrame frame, + HuffmanTable[] dcHuffmanTables, + HuffmanTable[] acHuffmanTables, + int componentsLength, + int restartInterval, + int spectralStart, + int spectralEnd, + int successiveHigh, + int successiveLow, + CancellationToken cancellationToken) + { + 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; + this.cancellationToken = cancellationToken; + } + + /// + /// Decodes the entropy coded data. + /// + public void ParseEntropyCodedData() + { + this.cancellationToken.ThrowIfCancellationRequested(); + + if (!this.frame.Progressive) + { + this.ParseBaselineData(); + } + else + { + this.ParseProgressiveData(); + } + + if (this.scanBuffer.HasBadMarker()) + { + this.stream.Position = this.scanBuffer.MarkerPosition; + } + } + + private void ParseBaselineData() + { + if (this.componentsLength == 1) + { + this.ParseBaselineDataNonInterleaved(); + } + else + { + this.ParseBaselineDataInterleaved(); + } + } + + private void ParseBaselineDataInterleaved() + { + // Interleaved + int mcu = 0; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; + ref HuffmanScanBuffer 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]; + + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + dcHuffmanTable.Configure(); + acHuffmanTable.Configure(); + } + + for (int j = 0; j < mcusPerColumn; j++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + + for (int i = 0; i < mcusPerLine; i++) + { + // 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]; + + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + + 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++) + { + 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++) + { + if (buffer.NoData) + { + return; + } + + int blockCol = (mcuCol * h) + x; + + 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(); + } + } + } + + private void ParseBaselineDataNonInterleaved() + { + JpegComponent component = this.components[this.frame.ComponentOrder[0]]; + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; + + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + dcHuffmanTable.Configure(); + acHuffmanTable.Configure(); + + for (int j = 0; j < h; j++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int i = 0; i < w; i++) + { + if (buffer.NoData) + { + return; + } + + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockRef, i), + ref dcHuffmanTable, + ref acHuffmanTable); + + this.HandleRestart(); + } + } + } + + 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.componentsLength != 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 ParseProgressiveData() + { + this.CheckProgressiveData(); + + if (this.componentsLength == 1) + { + this.ParseProgressiveDataNonInterleaved(); + } + else + { + this.ParseProgressiveDataInterleaved(); + } + } + + private void ParseProgressiveDataInterleaved() + { + // Interleaved + int mcu = 0; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; + ref HuffmanScanBuffer 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++) + { + // 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]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + + 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++) + { + 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++) + { + if (buffer.NoData) + { + return; + } + + int blockCol = (mcuCol * h) + x; + + 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(); + } + } + } + + private void ParseProgressiveDataNonInterleaved() + { + JpegComponent component = this.components[this.frame.ComponentOrder[0]]; + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; + + if (this.spectralStart == 0) + { + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + dcHuffmanTable.Configure(); + + for (int j = 0; j < h; j++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int i = 0; i < w; i++) + { + if (buffer.NoData) + { + return; + } + + this.DecodeBlockProgressiveDC( + component, + ref Unsafe.Add(ref blockRef, i), + ref dcHuffmanTable); + + this.HandleRestart(); + } + } + } + else + { + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + acHuffmanTable.Configure(); + + for (int j = 0; j < h; j++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int i = 0; i < w; i++) + { + if (buffer.NoData) + { + return; + } + + this.DecodeBlockProgressiveAC( + ref Unsafe.Add(ref blockRef, i), + ref acHuffmanTable); + + 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 HuffmanScanBuffer buffer = ref this.scanBuffer; + ref ZigZag zigzag = ref this.dctZigZag; + + // DC + int t = buffer.DecodeHuffman(ref dcTable); + if (t != 0) + { + 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; + + if (s != 0) + { + i += r; + s = buffer.Receive(s); + Unsafe.Add(ref blockDataRef, zigzag[i++]) = (short)s; + } + else + { + if (r == 0) + { + break; + } + + i += 16; + } + } + } + + private void DecodeBlockProgressiveDC(JpegComponent component, ref Block8x8 block, ref HuffmanTable dcTable) + { + ref short blockDataRef = ref Unsafe.As(ref block); + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + + if (this.successiveHigh == 0) + { + // 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); + } + } + + private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable) + { + ref short blockDataRef = ref Unsafe.As(ref block); + if (this.successiveHigh == 0) + { + // MCU decoding for AC initial scan (either spectral selection, + // or first pass of successive approximation). + if (this.eobrun != 0) + { + --this.eobrun; + return; + } + + 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; + + i += r; + + if (s != 0) + { + s = buffer.Receive(s); + Unsafe.Add(ref blockDataRef, zigzag[i]) = (short)(s << low); + } + else + { + if (r != 15) + { + this.eobrun = 1 << r; + if (r != 0) + { + buffer.CheckBits(); + this.eobrun += buffer.GetBits(r); + } + + --this.eobrun; + break; + } + } + } + } + else + { + // Refinement scan for these AC coefficients + this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable); + } + } + + private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable) + { + // Refinement scan for these AC coefficients + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + ref ZigZag zigzag = ref this.dctZigZag; + int start = this.spectralStart; + int end = this.spectralEnd; + + int p1 = 1 << this.successiveLow; + int m1 = (-1) << this.successiveLow; + + int k = start; + + if (this.eobrun == 0) + { + for (; k <= end; k++) + { + int s = buffer.DecodeHuffman(ref acTable); + int r = s >> 4; + s &= 15; + + if (s != 0) + { + buffer.CheckBits(); + if (buffer.GetBits(1) != 0) + { + s = p1; + } + else + { + s = m1; + } + } + else + { + if (r != 15) + { + this.eobrun = 1 << r; + + if (r != 0) + { + buffer.CheckBits(); + this.eobrun += buffer.GetBits(r); + } + + break; + } + } + + do + { + ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]); + if (coef != 0) + { + buffer.CheckBits(); + if (buffer.GetBits(1) != 0) + { + if ((coef & p1) == 0) + { + coef += (short)(coef >= 0 ? p1 : m1); + } + } + } + else + { + if (--r < 0) + { + break; + } + } + + k++; + } + while (k <= end); + + if ((s != 0) && (k < 64)) + { + Unsafe.Add(ref blockDataRef, zigzag[k]) = (short)s; + } + } + } + + if (this.eobrun > 0) + { + for (; k <= end; k++) + { + ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]); + + if (coef != 0) + { + buffer.CheckBits(); + if (buffer.GetBits(1) != 0) + { + if ((coef & p1) == 0) + { + coef += (short)(coef >= 0 ? p1 : m1); + } + } + } + } + + --this.eobrun; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void Reset() + { + for (int i = 0; i < this.components.Length; i++) + { + 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.scanBuffer.FindNextMarker()) + { + return false; + } + } + + this.todo = this.restartInterval; + + if (this.scanBuffer.HasRestartMarker()) + { + this.Reset(); + return true; + } + + if (this.scanBuffer.HasBadMarker()) + { + this.stream.Position = this.scanBuffer.MarkerPosition; + this.Reset(); + return true; + } + } + + return false; + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 9e11981b12..f18c636278 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -1,127 +1,162 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Memory; - namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Represents a Huffman Table + /// Represents a Huffman coding table containing basic coding data plus tables for accelerated computation. /// [StructLayout(LayoutKind.Sequential)] internal unsafe struct HuffmanTable { + private bool isConfigured; + /// - /// Gets the max code array + /// Derived from the DHT marker. Sizes[k] = # of symbols with codes of length k bits; Sizes[0] is unused. /// - public fixed uint MaxCode[18]; + public fixed byte Sizes[17]; /// - /// Gets the value offset array + /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. /// - public fixed int ValOffset[18]; + public fixed byte Values[256]; /// - /// Gets the huffman value array + /// Contains the largest code of length k (0 if none). MaxCode[17] is a sentinel to + /// ensure terminates. /// - public fixed byte Values[256]; + public fixed ulong MaxCode[18]; /// - /// Gets the lookahead array + /// 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 byte Lookahead[512]; + public fixed int ValOffset[19]; /// - /// Gets the sizes array + /// Contains the length of bits for the given k value. /// - public fixed short Sizes[257]; + 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) + public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) { - // We do some bounds checks in the code here to protect against AccessViolationExceptions - const int HuffCodeLength = 257; - const int MaxSizeLength = HuffCodeLength - 1; - using (IMemoryOwner huffcode = memoryAllocator.Allocate(HuffCodeLength)) + this.isConfigured = false; + 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); + } + + /// + /// Expands the HuffmanTable into its readable form. + /// + public void Configure() + { + if (this.isConfigured) { - ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan()); - ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths); + return; + } - // Figure C.1: make table of Huffman code length for each symbol - ref short sizesRef = ref this.Sizes[0]; - short x = 0; + Span huffSize = stackalloc char[257]; + Span huffCode = stackalloc uint[257]; - for (short i = 1; i < 17; i++) + // Figure C.1: make table of Huffman code length for each symbol + int p = 0; + for (int j = 1; j <= 16; j++) + { + int i = this.Sizes[j]; + while (i-- != 0) { - byte length = Unsafe.Add(ref codeLengthsRef, i); - for (short j = 0; j < length && x < MaxSizeLength; j++) - { - Unsafe.Add(ref sizesRef, x++) = i; - } + huffSize[p++] = (char)j; } + } - Unsafe.Add(ref sizesRef, x) = 0; - - // Figure C.2: generate the codes themselves - int si = 0; - ref int valOffsetRef = ref this.ValOffset[0]; - ref uint maxcodeRef = ref this.MaxCode[0]; + huffSize[p] = (char)0; - uint code = 0; - int k; - for (k = 1; k < 17; k++) + // Figure C.2: generate the codes themselves + uint code = 0; + int si = huffSize[0]; + p = 0; + while (huffSize[p] != 0) + { + while (huffSize[p] == si) { - // Compute delta to add to code to compute symbol id. - Unsafe.Add(ref valOffsetRef, k) = (int)(si - code); - if (Unsafe.Add(ref sizesRef, si) == k) - { - while (Unsafe.Add(ref sizesRef, si) == k && si < HuffCodeLength) - { - Unsafe.Add(ref huffcodeRef, si++) = (short)code++; - } - } + huffCode[p++] = code; + code++; + } - // Figure F.15: generate decoding tables for bit-sequential decoding. - // Compute largest code + 1 for this size. preshifted as we need it later. - Unsafe.Add(ref maxcodeRef, k) = code << (16 - k); - code <<= 1; + code <<= 1; + si++; + } + + // Figure F.15: generate decoding tables for bit-sequential decoding + p = 0; + for (int j = 1; j <= 16; j++) + { + if (this.Sizes[j] != 0) + { + this.ValOffset[j] = p - (int)huffCode[p]; + p += this.Sizes[j]; + this.MaxCode[j] = huffCode[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; } + else + { + this.MaxCode[j] = 0; + } + } - Unsafe.Add(ref maxcodeRef, k) = 0xFFFFFFFF; + this.ValOffset[18] = 0; + this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates - // Generate non-spec lookup tables to speed up decoding. - const int FastBits = ScanDecoder.FastBits; - ref byte lookaheadRef = ref this.Lookahead[0]; - const uint MaxFastLength = 1 << FastBits; - Unsafe.InitBlockUnaligned(ref lookaheadRef, 0xFF, MaxFastLength); // Flag for non-accelerated + // 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); - for (int i = 0; i < si; i++) + p = 0; + for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) + { + int jShift = JpegConstants.Huffman.LookupBits - length; + for (int i = 1; i <= this.Sizes[length]; i++, p++) { - int size = Unsafe.Add(ref sizesRef, i); - if (size <= FastBits) + // length = current code's length, p = its index in huffCode[] & Values[]. + // Generate left-justified code followed by all possible bit sequences + int lookBits = (int)(huffCode[p] << jShift); + for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) { - int huffCode = Unsafe.Add(ref huffcodeRef, i) << (FastBits - size); - int max = 1 << (FastBits - size); - for (int left = 0; left < max; left++) - { - Unsafe.Add(ref lookaheadRef, huffCode + left) = (byte)i; - } + this.LookaheadSize[lookBits] = (byte)length; + this.LookaheadValue[lookBits] = this.Values[p]; + lookBits++; } } } - Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), 256); + this.isConfigured = true; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs deleted file mode 100644 index dc066aa0ac..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - /// - /// Defines a 2 pairs of huffman tables. - /// - internal sealed class HuffmanTables - { - private readonly HuffmanTable[] tables = new HuffmanTable[4]; - - /// - /// Gets or sets the table at the given index. - /// - /// The index - /// The - public ref HuffmanTable this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref this.tables[index]; - } - } -} \ 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 2492a985a8..66f7867b4d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index ace8d7215b..b1ac1f78f5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -1,10 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; - -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index c51a2f4da5..3125ff1232 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -1,13 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Provides information about the JFIF marker segment + /// Provides information about the JFIF marker segment. /// TODO: Thumbnail? /// internal readonly struct JFifMarker : IEquatable @@ -20,31 +20,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// 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 + /// 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) { - Guard.MustBeGreaterThan(xDensity, 0, nameof(xDensity)); - Guard.MustBeGreaterThan(yDensity, 0, nameof(yDensity)); - Guard.MustBeBetweenOrEqualTo(densityUnits, 0, 2, nameof(densityUnits)); + if (xDensity <= 0) + { + JpegThrowHelper.ThrowInvalidImageContentException($"X-Density {xDensity} must be greater than 0."); + } + + if (yDensity <= 0) + { + JpegThrowHelper.ThrowInvalidImageContentException($"Y-Density {yDensity} must be greater than 0."); + } this.MajorVersion = majorVersion; this.MinorVersion = minorVersion; + + // 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 major version + /// Gets the major version. /// public byte MajorVersion { get; } /// - /// Gets the minor version + /// Gets the minor version. /// public byte MinorVersion { get; } @@ -70,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// 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 byte array containing metadata to parse. /// The marker to return. public static bool TryParse(byte[] bytes, out JFifMarker marker) { @@ -104,10 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } /// - public override bool Equals(object obj) - { - return obj is JFifMarker other && this.Equals(other); - } + public override bool Equals(object obj) => obj is JFifMarker other && this.Equals(other); /// public override int GetHashCode() diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs index fe39f41884..40683e25a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { @@ -20,17 +19,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public Block8x8F SourceBlock; /// - /// Temporal block 1 to store intermediate and/or final computation results + /// 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 + /// Temporal block 2 to store intermediate and/or final computation results. /// public Block8x8F WorkspaceBlock2; /// - /// The quantization table as + /// The quantization table as . /// public Block8x8F DequantiazationTable; @@ -40,9 +39,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private Size subSamplingDivisors; /// - /// Defines the maximum value derived from the bitdepth + /// Defines the maximum value derived from the bitdepth. /// - private int maximumValue; + private readonly int maximumValue; /// /// Initializes a new instance of the struct. @@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int qtIndex = component.QuantizationTableIndex; this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); this.subSamplingDivisors = component.SubSamplingDivisors; - this.maximumValue = (int)Math.Pow(2, decoder.Precision) - 1; + this.maximumValue = (int)MathF.Pow(2, decoder.Precision) - 1; this.SourceBlock = default; this.WorkspaceBlock1 = default; @@ -66,14 +65,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// - 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 + /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in . /// /// The source block. - /// The destination buffer area. + /// Reference to the origin of the destination pixel area. + /// The width of the destination pixel buffer. /// The maximum value derived from the bitdepth. public void ProcessBlockColorsInto( ref Block8x8 sourceBlock, - in BufferArea destArea, + ref float destAreaOrigin, + int destAreaStride, float maximumValue) { ref Block8x8F b = ref this.SourceBlock; @@ -89,7 +90,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // 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); + this.WorkspaceBlock1.ScaledCopyTo( + ref destAreaOrigin, + destAreaStride, + 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 index 2861a2c2e9..1ec646bc92 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Identifies the colorspace of a Jpeg image + /// Identifies the colorspace of a Jpeg image. /// internal enum JpegColorSpace { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index ef03582d69..5c3ee6e28e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -1,20 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Represents a single frame component + /// Represents a single frame component. /// - internal class JpegComponent : IDisposable, IJpegComponent + internal sealed class JpegComponent : IDisposable, IJpegComponent { private readonly MemoryAllocator memoryAllocator; @@ -23,20 +19,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.memoryAllocator = memoryAllocator; this.Frame = frame; this.Id = id; + + // Validate sampling factors. + if (horizontalFactor == 0 || verticalFactor == 0) + { + 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; } /// - /// Gets the component Id + /// Gets the component id. /// public byte Id { get; } /// - /// Gets or sets DC coefficient predictor + /// Gets or sets DC coefficient predictor. /// public int DcPredictor { get; set; } @@ -69,22 +78,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public Size SamplingFactors { get; set; } /// - /// Gets the number of blocks per line + /// Gets the number of blocks per line. /// public int WidthInBlocks { get; private set; } /// - /// Gets the number of blocks per column + /// Gets the number of blocks per column. /// public int HeightInBlocks { get; private set; } /// - /// Gets or sets the index for the DC Huffman table + /// Gets or sets the index for the DC Huffman table. /// public int DCHuffmanTableId { get; set; } /// - /// Gets or sets the index for the AC Huffman table + /// Gets or sets the index for the AC Huffman table. /// public int ACHuffmanTableId { get; set; } @@ -109,23 +118,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); - // For 4-component images (either CMYK or YCbCrK), we only support two - // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. - // Theoretically, 4-component JPEG images could mix and match hv values - // but in practice, those two combinations are the only ones in use, - // and it simplifies the applyBlack code below if we can assume that: - // - for CMYK, the C and K channels have full samples, and if the M - // and Y channels subsample, they subsample both horizontally and - // vertically. - // - for YCbCrK, the Y and K channels have full samples. - if (this.Index == 0 || this.Index == 3) - { - this.SubSamplingDivisors = new Size(1, 1); - } - else + JpegComponent c0 = this.Frame.Components[0]; + this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); + + if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) { - JpegComponent c0 = this.Frame.Components[0]; - this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); + JpegThrowHelper.ThrowBadSampling(); } int totalNumberOfBlocks = blocksPerColumnForMcu * (blocksPerLineForMcu + 1); @@ -135,4 +133,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.SpectralBlocks = this.memoryAllocator.Allocate2D(width, height, 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 index e7f3e4fda5..fc1ebaf921 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,8 +6,6 @@ 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 { @@ -33,12 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.Component = component; this.ImagePostProcessor = imagePostProcessor; - this.ColorBuffer = memoryAllocator.Allocate2D( + this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( imagePostProcessor.PostProcessorBufferSize.Width, - imagePostProcessor.PostProcessorBufferSize.Height); + imagePostProcessor.PostProcessorBufferSize.Height, + this.blockAreaSize.Height); this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; - this.blockAreaSize = this.Component.SubSamplingDivisors * 8; } /// @@ -78,7 +77,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public void CopyBlocksToColorBuffer() { var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); - float maximumValue = (float)Math.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1; + float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1; + + int destAreaStride = this.ColorBuffer.Width; for (int y = 0; y < this.BlockRowsPerStep; y++) { @@ -91,26 +92,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int yBuffer = y * this.blockAreaSize.Height; + Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); Span blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); - ref Block8x8 blockRowBase = ref MemoryMarshal.GetReference(blockRow); + // see: https://github.com/SixLabors/ImageSharp/issues/824 + int widthInBlocks = Math.Min(this.Component.SpectralBlocks.Width, this.SizeInBlocks.Width); - for (int xBlock = 0; xBlock < this.SizeInBlocks.Width; xBlock++) + for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) { - ref Block8x8 block = ref Unsafe.Add(ref blockRowBase, xBlock); + ref Block8x8 block = ref blockRow[xBlock]; int xBuffer = xBlock * this.blockAreaSize.Width; + ref float destAreaOrigin = ref colorBufferRow[xBuffer]; - BufferArea destArea = this.ColorBuffer.GetArea( - xBuffer, - yBuffer, - this.blockAreaSize.Width, - this.blockAreaSize.Height); - - blockPp.ProcessBlockColorsInto(ref block, destArea, maximumValue); + blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, 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 d2b0ee26e4..622c01f5b8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 36a3dc2d26..827afe38da 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,68 +11,68 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder internal sealed class JpegFrame : IDisposable { /// - /// Gets or sets a value indicating whether the frame uses the extended specification + /// 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 + /// Gets or sets a value indicating whether the frame uses the progressive specification. /// public bool Progressive { get; set; } /// - /// Gets or sets the precision + /// Gets or sets the precision. /// public byte Precision { get; set; } /// - /// Gets or sets the number of scanlines within the frame + /// Gets or sets the number of scanlines within the frame. /// - public short Scanlines { get; set; } + public int Scanlines { get; set; } /// - /// Gets or sets the number of samples per scanline + /// Gets or sets the number of samples per scanline. /// - public short SamplesPerLine { get; set; } + public int 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 + /// 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 + /// Gets or sets the component id collection. /// public byte[] ComponentIds { get; set; } /// - /// Gets or sets the order in which to process the components + /// 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 + /// Gets or sets the frame component collection. /// public JpegComponent[] Components { get; set; } /// - /// Gets or sets the maximum horizontal sampling factor + /// Gets or sets the maximum horizontal sampling factor. /// public int MaxHorizontalFactor { get; set; } /// - /// Gets or sets the maximum vertical sampling factor + /// Gets or sets the maximum vertical sampling factor. /// public int MaxVerticalFactor { get; set; } /// - /// Gets or sets the number of MCU's per line + /// 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 + /// Gets or sets the number of MCU's per column. /// public int McusPerColumn { get; set; } @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { for (int i = 0; i < this.Components.Length; i++) { - this.Components[i].Dispose(); + this.Components[i]?.Dispose(); } this.Components = null; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } /// - /// Allocates the frame component blocks + /// Allocates the frame component blocks. /// public void InitComponents() { @@ -105,4 +105,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } } -} \ 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 index 438749abf9..5b0331c85c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -1,14 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Numerics; +using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder @@ -21,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// (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 ). + /// which means that size of the allocated memory is limited (does not depend on ). ///
internal class JpegImagePostProcessor : IDisposable { @@ -113,8 +112,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ///
/// The pixel type /// The destination image - public void PostProcess(ImageFrame destination) - where TPixel : struct, IPixel + /// The token to request cancellation. + public void PostProcess(ImageFrame destination, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { this.PixelRowCounter = 0; @@ -125,6 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height) { + cancellationToken.ThrowIfCancellationRequested(); this.DoPostProcessorStep(destination); } } @@ -135,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The pixel type /// The destination image. public void DoPostProcessorStep(ImageFrame destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) { @@ -153,7 +154,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The pixel type /// The destination image private void ConvertColorsInto(ImageFrame destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); @@ -173,8 +174,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder Span destRow = destination.GetPixelRowSpan(yy); // TODO: Investigate if slicing is actually necessary - PixelOperations.Instance.FromVector4(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); + 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 3e7108b151..e1e0e160cd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Text; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -12,35 +11,73 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder internal static class ProfileResolver { /// - /// Describes the EXIF specific markers + /// Gets the JFIF specific markers. /// - public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); + public static ReadOnlySpan JFifMarker => new[] + { + (byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0' + }; + + /// + /// Gets the ICC specific markers. + /// + public static ReadOnlySpan IccMarker => new[] + { + (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 => new[] + { + (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 => new[] + { + (byte)'8', (byte)'B', (byte)'I', (byte)'M' + }; /// - /// Describes the EXIF specific markers + /// Gets a IPTC Image resource ID. /// - public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); + public static ReadOnlySpan AdobeIptcMarker => new[] + { + (byte)4, (byte)4 + }; /// - /// Describes the ICC specific markers + /// Gets the EXIF specific markers. /// - public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); + public static ReadOnlySpan ExifMarker => new[] + { + (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0' + }; /// - /// Describes Adobe specific markers + /// Gets the Adobe specific markers . /// - public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); + public static ReadOnlySpan AdobeMarker => new[] + { + (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 + /// Returns a value indicating whether the passed bytes are a match to the profile identifier. /// - /// The bytes to check - /// The profile identifier - /// The + /// 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 index 4e11568c8d..938459b88e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder @@ -84,9 +84,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int i = 0; i < quantizationTables.Length; i++) { ref Block8x8F qTable = ref quantizationTables[i]; - for (int j = 0; j < Block8x8F.Size; j++) + + if (!qTable.Equals(default)) { - sum += qTable[j]; + for (int j = 0; j < Block8x8F.Size; j++) + { + sum += qTable[j]; + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs deleted file mode 100644 index a1c1b023e4..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs +++ /dev/null @@ -1,960 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.IO; -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 ScanDecoder - { - // The number of bits that can be read via a LUT. - public const int FastBits = 9; - - // LUT mask for n rightmost bits. Bmask[n] = (1 << n) - 1 - private static readonly uint[] Bmask = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 }; - - // LUT Bias[n] = (-1 << n) + 1 - private static readonly int[] Bias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 }; - - private readonly JpegFrame frame; - private readonly HuffmanTables dcHuffmanTables; - private readonly HuffmanTables acHuffmanTables; - private readonly FastACTables fastACTables; - - private readonly DoubleBufferedStreamReader stream; - private readonly JpegComponent[] components; - private readonly ZigZag dctZigZag; - - // 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; - - // The number of valid bits left to read in the buffer. - private int codeBits; - - // The entropy encoded code buffer. - private uint codeBuffer; - - // Whether there is more data to pull from the stream for the current mcu. - private bool nomore; - - // Whether we have prematurely reached the end of the file. - private bool eof; - - // The current, if any, marker in the input stream. - private byte marker; - - // Whether we have a bad marker, I.E. One that is not between RST0 and RST7 - private bool badMarker; - - // The opening position of an identified marker. - private long markerPosition; - - // 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; - - /// - /// Initializes a new instance of the class. - /// - /// The input stream. - /// The image frame. - /// The DC Huffman tables. - /// The AC Huffman tables. - /// The fast AC decoding 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 ScanDecoder( - DoubleBufferedStreamReader stream, - JpegFrame frame, - HuffmanTables dcHuffmanTables, - HuffmanTables acHuffmanTables, - FastACTables fastACTables, - int componentsLength, - int restartInterval, - int spectralStart, - int spectralEnd, - int successiveHigh, - int successiveLow) - { - this.dctZigZag = ZigZag.CreateUnzigTable(); - this.stream = stream; - this.frame = frame; - this.dcHuffmanTables = dcHuffmanTables; - this.acHuffmanTables = acHuffmanTables; - this.fastACTables = fastACTables; - this.components = frame.Components; - this.marker = JpegConstants.Markers.XFF; - this.markerPosition = 0; - this.componentsLength = componentsLength; - this.restartInterval = restartInterval; - this.spectralStart = spectralStart; - this.spectralEnd = spectralEnd; - this.successiveHigh = successiveHigh; - this.successiveLow = successiveLow; - } - - /// - /// Decodes the entropy coded data. - /// - public void ParseEntropyCodedData() - { - this.Reset(); - - if (!this.frame.Progressive) - { - this.ParseBaselineData(); - } - else - { - this.ParseProgressiveData(); - } - - if (this.badMarker) - { - this.stream.Position = this.markerPosition; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint LRot(uint x, int y) => (x << y) | (x >> (32 - y)); - - private void ParseBaselineData() - { - if (this.componentsLength == 1) - { - this.ParseBaselineDataNonInterleaved(); - } - else - { - this.ParseBaselineDataInterleaved(); - } - } - - private void ParseBaselineDataInterleaved() - { - // Interleaved - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - for (int j = 0; j < mcusPerColumn; j++) - { - for (int i = 0; i < mcusPerLine; i++) - { - // Scan an interleaved mcu... process components in order - 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]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; - - int mcuRow = mcu / mcusPerLine; - - // 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++) - { - int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); - for (int x = 0; x < h; x++) - { - if (this.eof) - { - return; - } - - int mcuCol = mcu % mcusPerLine; - int blockCol = (mcuCol * h) + x; - - this.DecodeBlockBaseline( - component, - ref blockSpan[blockCol], - ref dcHuffmanTable, - ref acHuffmanTable, - ref fastACRef); - } - } - } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - if (!this.ContinueOnMcuComplete()) - { - return; - } - } - } - } - - /// - /// Non-interleaved data, we just need to process one block at a time in trivial scanline order - /// number of blocks to do just depends on how many actual "pixels" each component has, - /// independent of interleaved MCU blocking and such. - /// - private void ParseBaselineDataNonInterleaved() - { - JpegComponent component = this.components[this.frame.ComponentOrder[0]]; - - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); - - int mcu = 0; - for (int j = 0; j < h; j++) - { - // TODO: Isn't blockRow == j actually? - int blockRow = mcu / w; - Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); - - for (int i = 0; i < w; i++) - { - if (this.eof) - { - return; - } - - // TODO: Isn't blockCol == i actually? - int blockCol = mcu % w; - - this.DecodeBlockBaseline( - component, - ref blockSpan[blockCol], - ref dcHuffmanTable, - ref acHuffmanTable, - ref fastACRef); - - // Every data block is an MCU, so countdown the restart interval - mcu++; - if (!this.ContinueOnMcuComplete()) - { - return; - } - } - } - } - - private void ParseProgressiveData() - { - if (this.componentsLength == 1) - { - this.ParseProgressiveDataNonInterleaved(); - } - else - { - this.ParseProgressiveDataInterleaved(); - } - } - - private void ParseProgressiveDataInterleaved() - { - // Interleaved - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - for (int j = 0; j < mcusPerColumn; j++) - { - for (int i = 0; i < mcusPerLine; i++) - { - // Scan an interleaved mcu... process components in order - 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]; - 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++) - { - int mcuRow = mcu / mcusPerLine; - int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); - - for (int x = 0; x < h; x++) - { - if (this.eof) - { - return; - } - - int mcuCol = mcu % mcusPerLine; - int blockCol = (mcuCol * h) + x; - - this.DecodeBlockProgressiveDC( - component, - ref blockSpan[blockCol], - ref dcHuffmanTable); - } - } - } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - if (!this.ContinueOnMcuComplete()) - { - return; - } - } - } - } - - /// - /// Non-interleaved data, we just need to process one block at a time, - /// in trivial scanline order - /// number of blocks to do just depends on how many actual "pixels" this - /// component has, independent of interleaved MCU blocking and such - /// - private void ParseProgressiveDataNonInterleaved() - { - JpegComponent component = this.components[this.frame.ComponentOrder[0]]; - - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); - - int mcu = 0; - for (int j = 0; j < h; j++) - { - // TODO: isn't blockRow == j actually? - int blockRow = mcu / w; - Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); - - for (int i = 0; i < w; i++) - { - if (this.eof) - { - return; - } - - // TODO: isn't blockCol == i actually? - int blockCol = mcu % w; - - ref Block8x8 block = ref blockSpan[blockCol]; - - if (this.spectralStart == 0) - { - this.DecodeBlockProgressiveDC( - component, - ref block, - ref dcHuffmanTable); - } - else - { - this.DecodeBlockProgressiveAC( - ref block, - ref acHuffmanTable, - ref fastACRef); - } - - // Every data block is an MCU, so countdown the restart interval - mcu++; - if (!this.ContinueOnMcuComplete()) - { - return; - } - } - } - } - - private void DecodeBlockBaseline( - JpegComponent component, - ref Block8x8 block, - ref HuffmanTable dcTable, - ref HuffmanTable acTable, - ref short fastACRef) - { - this.CheckBits(); - int t = this.DecodeHuffman(ref dcTable); - - if (t < 0) - { - JpegThrowHelper.ThrowBadHuffmanCode(); - } - - ref short blockDataRef = ref Unsafe.As(ref block); - - int diff = t != 0 ? this.ExtendReceive(t) : 0; - int dc = component.DcPredictor + diff; - component.DcPredictor = dc; - blockDataRef = (short)dc; - - // Decode AC Components, See Jpeg Spec - int k = 1; - do - { - int zig; - int s; - - this.CheckBits(); - int c = this.PeekBits(); - int r = Unsafe.Add(ref fastACRef, c); - - if (r != 0) - { - // Fast AC path - k += (r >> 4) & 15; // Run - s = r & 15; // Combined Length - this.codeBuffer <<= s; - this.codeBits -= s; - - // Decode into unzigzag location - zig = this.dctZigZag[k++]; - Unsafe.Add(ref blockDataRef, zig) = (short)(r >> 8); - } - else - { - int rs = this.DecodeHuffman(ref acTable); - - if (rs < 0) - { - JpegThrowHelper.ThrowBadHuffmanCode(); - } - - s = rs & 15; - r = rs >> 4; - - if (s == 0) - { - if (rs != 0xF0) - { - break; // End block - } - - k += 16; - } - else - { - k += r; - - // Decode into unzigzag location - zig = this.dctZigZag[k++]; - Unsafe.Add(ref blockDataRef, zig) = (short)this.ExtendReceive(s); - } - } - } - while (k < 64); - } - - private void DecodeBlockProgressiveDC( - JpegComponent component, - ref Block8x8 block, - ref HuffmanTable dcTable) - { - if (this.spectralEnd != 0) - { - JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC."); - } - - this.CheckBits(); - - ref short blockDataRef = ref Unsafe.As(ref block); - - if (this.successiveHigh == 0) - { - // First scan for DC coefficient, must be first - int t = this.DecodeHuffman(ref dcTable); - int diff = t != 0 ? this.ExtendReceive(t) : 0; - - int dc = component.DcPredictor + diff; - component.DcPredictor = dc; - - blockDataRef = (short)(dc << this.successiveLow); - } - else - { - // Refinement scan for DC coefficient - if (this.GetBit() != 0) - { - blockDataRef += (short)(1 << this.successiveLow); - } - } - } - - private void DecodeBlockProgressiveAC( - ref Block8x8 block, - ref HuffmanTable acTable, - ref short fastACRef) - { - if (this.spectralStart == 0) - { - JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC."); - } - - ref short blockDataRef = ref Unsafe.As(ref block); - - if (this.successiveHigh == 0) - { - // MCU decoding for AC initial scan (either spectral selection, - // or first pass of successive approximation). - int shift = this.successiveLow; - - if (this.eobrun != 0) - { - this.eobrun--; - return; - } - - int k = this.spectralStart; - do - { - int zig; - int s; - - this.CheckBits(); - int c = this.PeekBits(); - int r = Unsafe.Add(ref fastACRef, c); - - if (r != 0) - { - // Fast AC path - k += (r >> 4) & 15; // Run - s = r & 15; // Combined length - this.codeBuffer <<= s; - this.codeBits -= s; - - // Decode into unzigzag location - zig = this.dctZigZag[k++]; - Unsafe.Add(ref blockDataRef, zig) = (short)((r >> 8) << shift); - } - else - { - int rs = this.DecodeHuffman(ref acTable); - - if (rs < 0) - { - JpegThrowHelper.ThrowBadHuffmanCode(); - } - - s = rs & 15; - r = rs >> 4; - - if (s == 0) - { - if (r < 15) - { - this.eobrun = 1 << r; - if (r != 0) - { - this.eobrun += this.GetBits(r); - } - - this.eobrun--; - break; - } - - k += 16; - } - else - { - k += r; - zig = this.dctZigZag[k++]; - Unsafe.Add(ref blockDataRef, zig) = (short)(this.ExtendReceive(s) << shift); - } - } - } - while (k <= this.spectralEnd); - } - else - { - // Refinement scan for these AC coefficients - this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable); - } - } - - private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable) - { - int k; - - // Refinement scan for these AC coefficients - short bit = (short)(1 << this.successiveLow); - - if (this.eobrun != 0) - { - this.eobrun--; - for (k = this.spectralStart; k <= this.spectralEnd; k++) - { - ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k]); - if (p != 0) - { - if (this.GetBit() != 0) - { - if ((p & bit) == 0) - { - if (p > 0) - { - p += bit; - } - else - { - p -= bit; - } - } - } - } - } - } - else - { - k = this.spectralStart; - do - { - int rs = this.DecodeHuffman(ref acTable); - if (rs < 0) - { - JpegThrowHelper.ThrowBadHuffmanCode(); - } - - int s = rs & 15; - int r = rs >> 4; - - if (s == 0) - { - // r=15 s=0 should write 16 0s, so we just do - // a run of 15 0s and then write s (which is 0), - // so we don't have to do anything special here - if (r < 15) - { - this.eobrun = (1 << r) - 1; - - if (r != 0) - { - this.eobrun += this.GetBits(r); - } - - r = 64; // Force end of block - } - } - else - { - if (s != 1) - { - JpegThrowHelper.ThrowBadHuffmanCode(); - } - - // Sign bit - if (this.GetBit() != 0) - { - s = bit; - } - else - { - s = -bit; - } - } - - // Advance by r - while (k <= this.spectralEnd) - { - ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k++]); - if (p != 0) - { - if (this.GetBit() != 0) - { - if ((p & bit) == 0) - { - if (p > 0) - { - p += bit; - } - else - { - p -= bit; - } - } - } - } - else - { - if (r == 0) - { - p = (short)s; - break; - } - - r--; - } - } - } - while (k <= this.spectralEnd); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private int GetBits(int n) - { - if (this.codeBits < n) - { - this.FillBuffer(); - } - - uint k = LRot(this.codeBuffer, n); - uint mask = Bmask[n]; - this.codeBuffer = k & ~mask; - k &= mask; - this.codeBits -= n; - return (int)k; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private int GetBit() - { - if (this.codeBits < 1) - { - this.FillBuffer(); - } - - uint k = this.codeBuffer; - this.codeBuffer <<= 1; - this.codeBits--; - - return (int)(k & 0x80000000); - } - - [MethodImpl(InliningOptions.ColdPath)] - private 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. - do - { - 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; - } - - return; - } - } - - this.codeBuffer |= (uint)b << (24 - this.codeBits); - this.codeBits += 8; - } - while (this.codeBits <= 24); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private unsafe int DecodeHuffman(ref HuffmanTable table) - { - this.CheckBits(); - - // Look at the top FastBits and determine what symbol ID it is, - // if the code is <= FastBits. - int c = this.PeekBits(); - int k = table.Lookahead[c]; - if (k < 0xFF) - { - int s = table.Sizes[k]; - if (s > this.codeBits) - { - return -1; - } - - this.codeBuffer <<= s; - this.codeBits -= s; - return table.Values[k]; - } - - return this.DecodeHuffmanSlow(ref table); - } - - [MethodImpl(InliningOptions.ColdPath)] - private unsafe int DecodeHuffmanSlow(ref HuffmanTable table) - { - // Naive test is to shift the code_buffer down so k bits are - // valid, then test against MaxCode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - uint temp = this.codeBuffer >> 16; - int k; - for (k = FastBits + 1; ; ++k) - { - if (temp < table.MaxCode[k]) - { - break; - } - } - - if (k == 17) - { - // Error! code not found - this.codeBits -= 16; - return -1; - } - - if (k > this.codeBits) - { - return -1; - } - - // Convert the huffman code to the symbol id - int c = (int)(((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]); - - // Convert the id to a symbol - this.codeBits -= k; - this.codeBuffer <<= k; - return table.Values[c]; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private int ExtendReceive(int n) - { - if (this.codeBits < n) - { - this.FillBuffer(); - } - - int sgn = (int)this.codeBuffer >> 31; - uint k = LRot(this.codeBuffer, n); - this.codeBuffer = k & ~Bmask[n]; - k &= Bmask[n]; - this.codeBits -= n; - return (int)(k + (Bias[n] & ~sgn)); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private void CheckBits() - { - if (this.codeBits < 16) - { - this.FillBuffer(); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private int PeekBits() => (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1)); - - [MethodImpl(InliningOptions.ShortMethod)] - private bool ContinueOnMcuComplete() - { - if (--this.todo > 0) - { - return true; - } - - if (this.codeBits < 24) - { - this.FillBuffer(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data rather than no data. - // Reset the stream to before any bad markers to ensure we can read successive segments. - if (this.badMarker) - { - this.stream.Position = this.markerPosition; - } - - if (!this.HasRestart()) - { - return false; - } - - this.Reset(); - - return true; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private bool HasRestart() - { - byte m = this.marker; - return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7; - } - - private void Reset() - { - this.codeBits = 0; - this.codeBuffer = 0; - - for (int i = 0; i < this.components.Length; i++) - { - JpegComponent c = this.components[i]; - c.DcPredictor = 0; - } - - this.nomore = false; - this.marker = JpegConstants.Markers.XFF; - this.markerPosition = 0; - this.badMarker = false; - this.eobrun = 0; - - // No more than 1<<31 MCUs if no restartInterval? that's plenty safe since we don't even allow 1<<30 pixels - this.todo = this.restartInterval > 0 ? this.restartInterval : int.MaxValue; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs deleted file mode 100644 index 7a312138d0..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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/HuffIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs index 633d7ea80f..aa3968a31f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index a31c4bf2f4..bc2c7634b5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index 2e2ee9575c..f9c16c5be7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs index d0933af0c4..5eee5dfde4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs index cb0810985e..236eff27cc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; @@ -96,30 +96,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// 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) + public void ConvertPixelInto( + int r, + int g, + int b, + ref Block8x8F yResult, + ref Block8x8F cbResult, + ref Block8x8F crResult, + int i) { - ref int start = ref Unsafe.As(ref this); + // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); + yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; - 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); + // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); + cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; - 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; + // float cr = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); + crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Fix(float x) - { - return (int)((x * (1L << ScaleBits)) + 0.5F); - } + => (int)((x * (1L << ScaleBits)) + 0.5F); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index 301079b6ae..4d6186e22f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; - using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder ///
/// The pixel type to work on internal ref struct YCbCrForwardConverter - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// The Y component @@ -56,16 +55,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// 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) + public void Convert(ImageFrame frame, int x, int y, in RowOctet currentRows) { - this.pixelBlock.LoadAndStretchEdges(frame, x, y); + this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows); Span rgbSpan = this.rgbBlock.AsSpanUnsafe(); - PixelOperations.Instance.ToRgb24(frame.Configuration, this.pixelBlock.AsSpanUnsafe(), rgbSpan); + PixelOperations.Instance.ToRgb24(frame.GetConfiguration(), 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 Block8x8F yBlock = ref this.Y; + ref Block8x8F cbBlock = ref this.Cb; + ref Block8x8F crBlock = ref this.Cr; ref Rgb24 rgbStart = ref rgbSpan[0]; for (int i = 0; i < 64; i++) @@ -76,10 +75,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder c.R, c.G, c.B, - ref Unsafe.Add(ref yBlockStart, i), - ref Unsafe.Add(ref cbBlockStart, i), - ref Unsafe.Add(ref crBlockStart, i)); + ref yBlock, + ref cbBlock, + ref crBlock, + 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 index dcdc7e9ba7..ee06f2bdeb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs index 0cc729371f..213c48ff38 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt index 28bcea791b..e69de29bb2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt @@ -1,40 +0,0 @@ -<# -// 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 index c795ccc8b5..92ba1afd35 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs @@ -1,13 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Components @@ -17,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// [StructLayout(LayoutKind.Sequential)] internal unsafe partial struct GenericBlock8x8 - where T : struct + where T : unmanaged { public const int Size = 64; @@ -55,24 +53,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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) + public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, in RowOctet currentRows) { int width = Math.Min(8, source.Width - sourceX); int height = Math.Min(8, source.Height - sourceY); @@ -86,15 +71,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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); + Span row = currentRows[y]; + + ref byte s = ref Unsafe.As(ref row[sourceX]); ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); Unsafe.CopyBlock(ref d, ref s, byteWidth); @@ -126,6 +109,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Only for on-stack instances! /// - public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size); + public Span AsSpanUnsafe() + { +#if SUPPORTS_CREATESPAN + Span> s = MemoryMarshal.CreateSpan(ref this, 1); + return MemoryMarshal.Cast, T>(s); +#else + return new Span(Unsafe.AsPointer(ref this), Size); +#endif + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs new file mode 100644 index 0000000000..ae10bfba83 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + /// + /// Cache 8 pixel rows on the stack, which may originate from different buffers of a . + /// + [StructLayout(LayoutKind.Sequential)] + internal readonly ref struct RowOctet + where T : struct + { + private readonly Span row0; + private readonly Span row1; + private readonly Span row2; + private readonly Span row3; + private readonly Span row4; + private readonly Span row5; + private readonly Span row6; + private readonly Span row7; + + public RowOctet(Buffer2D buffer, int startY) + { + int y = startY; + int height = buffer.Height; + this.row0 = y < height ? buffer.GetRowSpan(y++) : default; + this.row1 = y < height ? buffer.GetRowSpan(y++) : default; + this.row2 = y < height ? buffer.GetRowSpan(y++) : default; + this.row3 = y < height ? buffer.GetRowSpan(y++) : default; + this.row4 = y < height ? buffer.GetRowSpan(y++) : default; + this.row5 = y < height ? buffer.GetRowSpan(y++) : default; + this.row6 = y < height ? buffer.GetRowSpan(y++) : default; + this.row7 = y < height ? buffer.GetRowSpan(y) : default; + } + + public Span this[int y] + { + [MethodImpl(InliningOptions.ShortMethod)] + get + { + // No unsafe tricks, since Span can't be used as a generic argument + return 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(InliningOptions.ColdPath)] + private static Span ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs b/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs index 48ad188561..69f3f2a250 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs @@ -1,11 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index a3701f2c1b..737652d4e6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -16,33 +16,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components [StructLayout(LayoutKind.Sequential)] internal unsafe struct ZigZag { + /// + /// 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. + /// + private const int Size = 64 + 16; + /// /// Copy of in a value type /// - public fixed byte Data[64]; + public fixed byte Data[Size]; /// - /// 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). + /// Gets the unzigs map, which maps from the zigzag ordering to the natural ordering. + /// For example, unzig[3] is the column and row of the fourth element in zigzag order. + /// The value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). /// - private static readonly byte[] Unzig = + private static ReadOnlySpan Unzig => new byte[] { - 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 + 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, + 63, 63, 63, 63, 63, 63, 63, 63, // Extra entries for safety in decoder + 63, 63, 63, 63, 63, 63, 63, 63 }; /// @@ -67,8 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public static ZigZag CreateUnzigTable() { ZigZag result = default; - byte* unzigPtr = result.Data; - Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); + ref byte sourceRef = ref MemoryMarshal.GetReference(Unzig); + ref byte destinationRef = ref Unsafe.AsRef(result.Data); + + Unzig.CopyTo(new Span(result.Data, Size)); + return result; } @@ -79,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { Block8x8F result = default; - for (int i = 0; i < 64; i++) + for (int i = 0; i < Block8x8F.Size; i++) { result[Unzig[i]] = qt[i]; } @@ -87,4 +97,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components return result; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs index ef7b377d2b..3dbcd244ae 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index 53108de934..ecd64a7823 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs deleted file mode 100644 index cb7fc19446..0000000000 --- a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs index 9840a2ae88..ab8197af92 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index 49e3b41704..8cc6ee81af 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; namespace SixLabors.ImageSharp.Formats.Jpeg { @@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public static readonly IEnumerable FileExtensions = new[] { "jpg", "jpeg", "jfif" }; /// - /// Contains marker specific constants + /// Contains marker specific constants. /// // ReSharper disable InconsistentNaming internal static class Markers @@ -219,7 +220,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// Contains Adobe specific constants + /// Contains Adobe specific constants. /// internal static class Adobe { @@ -238,5 +239,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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; + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 57b70dd26e..39b8e492f8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -1,7 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -11,32 +15,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector { - /// - /// 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 + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); - using (var decoder = new JpegDecoderCore(configuration, this)) - { - return decoder.Decode(stream); - } + using var decoder = new JpegDecoderCore(configuration, this); + return decoder.Decode(configuration, stream); + } + + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + using var decoder = new JpegDecoderCore(configuration, this); + return decoder.DecodeAsync(configuration, stream, cancellationToken); } + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + /// public IImageInfo Identify(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); + using var decoder = new JpegDecoderCore(configuration, this); + return decoder.Identify(configuration, stream); + } + + /// + public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(stream, nameof(stream)); + + // The introduction of a local variable that refers to an object the implements + // IDisposable means you must use async/await, where the compiler generates the + // state machine and a continuation. using (var decoder = new JpegDecoderCore(configuration, this)) { - return decoder.Identify(stream); + return await decoder.IdentifyAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 06c844d58e..c4355cdbe1 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1,24 +1,21 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; -using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using SixLabors.ImageSharp.Common.Helpers; 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; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +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.Primitives; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg { @@ -27,18 +24,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Originally ported from /// with additional fixes for both performance and common encoding errors. ///
- internal sealed class JpegDecoderCore : IRawJpegData + internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals { /// /// 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. /// @@ -50,52 +42,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private readonly byte[] markerBuffer = new byte[2]; /// - /// The DC Huffman tables + /// The DC Huffman tables. /// - private HuffmanTables dcHuffmanTables; + private HuffmanTable[] dcHuffmanTables; /// /// The AC Huffman tables /// - private HuffmanTables acHuffmanTables; - - /// - /// The fast AC tables used for entropy decoding - /// - private FastACTables fastACTables; + private HuffmanTable[] acHuffmanTables; /// - /// The reset interval determined by RST markers + /// The reset interval determined by RST markers. /// private ushort resetInterval; /// - /// Whether the image has an EXIF marker + /// Whether the image has an EXIF marker. /// private bool isExif; /// - /// Contains exif data + /// Contains exif data. /// private byte[] exifData; /// - /// Whether the image has an ICC marker + /// Whether the image has an ICC marker. /// private bool isIcc; /// - /// Contains ICC data + /// Contains ICC data. /// private byte[] iccData; /// - /// Contains information about the JFIF marker + /// Whether the image has a IPTC data. + /// + private bool isIptc; + + /// + /// Contains IPTC data. + /// + private byte[] iptcData; + + /// + /// Contains information about the JFIF marker. /// private JFifMarker jFif; /// - /// Contains information about the Adobe marker + /// Contains information about the Adobe marker. /// private AdobeMarker adobe; @@ -106,10 +103,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The options. public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) { - this.configuration = configuration ?? Configuration.Default; + this.Configuration = configuration ?? Configuration.Default; this.IgnoreMetadata = options.IgnoreMetadata; } + /// + public Configuration Configuration { get; } + /// /// Gets the frame /// @@ -118,6 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public Size ImageSizeInPixels { get; private set; } + /// + Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels; + /// /// Gets the number of MCU blocks in the image as . /// @@ -138,20 +141,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ///
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. + /// Gets the decoded by this decoder instance. /// - public ImageMetaData MetaData { get; private set; } + public ImageMetadata Metadata { get; private set; } /// public int ComponentCount { get; private set; } @@ -179,7 +177,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The buffer to read file markers to /// The input stream /// The - public static JpegFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream) + public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream) { int value = stream.Read(marker, 0, 2); @@ -210,34 +208,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return new JpegFileMarker(marker[1], stream.Position - 2, true); } - /// - /// 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 + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { - this.ParseStream(stream); + this.ParseStream(stream, cancellationToken: cancellationToken); this.InitExifProfile(); this.InitIccProfile(); - this.InitDerivedMetaDataProperties(); - return this.PostProcessIntoImage(); + this.InitIptcProfile(); + this.InitDerivedMetadataProperties(); + return this.PostProcessIntoImage(cancellationToken); } - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - public IImageInfo Identify(Stream stream) + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - this.ParseStream(stream, true); + this.ParseStream(stream, true, cancellationToken); this.InitExifProfile(); this.InitIccProfile(); - this.InitDerivedMetaDataProperties(); + this.InitIptcProfile(); + this.InitDerivedMetadataProperties(); - return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData); + return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata); } /// @@ -245,30 +237,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The input stream /// Whether to decode metadata only. - public void ParseStream(Stream stream, bool metadataOnly = false) + /// The token to monitor cancellation. + public void ParseStream(BufferedReadStream stream, bool metadataOnly = false, CancellationToken cancellationToken = default) { - this.MetaData = new ImageMetaData(); - this.InputStream = new DoubleBufferedStreamReader(this.configuration.MemoryAllocator, stream); + this.Metadata = new ImageMetadata(); // Check for the Start Of Image marker. - this.InputStream.Read(this.markerBuffer, 0, 2); + stream.Read(this.markerBuffer, 0, 2); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { - throw new ImageFormatException("Missing SOI marker."); + JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } - this.InputStream.Read(this.markerBuffer, 0, 2); + stream.Read(this.markerBuffer, 0, 2); byte marker = this.markerBuffer[1]; - fileMarker = new JpegFileMarker(marker, (int)this.InputStream.Position - 2); + fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); this.QuantizationTables = new Block8x8F[4]; // Only assign what we need if (!metadataOnly) { - this.dcHuffmanTables = new HuffmanTables(); - this.acHuffmanTables = new HuffmanTables(); - this.fastACTables = new FastACTables(this.configuration.MemoryAllocator); + const int maxTables = 4; + this.dcHuffmanTables = new HuffmanTable[maxTables]; + this.acHuffmanTables = new HuffmanTable[maxTables]; } // Break only when we discover a valid EOI marker. @@ -276,23 +268,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { + cancellationToken.ThrowIfCancellationRequested(); + if (!fileMarker.Invalid) { // Get the marker length - int remaining = this.ReadUint16() - 2; + int remaining = this.ReadUint16(stream) - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: - this.ProcessStartOfFrameMarker(remaining, fileMarker, metadataOnly); + this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); break; case JpegConstants.Markers.SOS: if (!metadataOnly) { - this.ProcessStartOfScanMarker(); + this.ProcessStartOfScanMarker(stream, cancellationToken); break; } else @@ -306,41 +300,41 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (metadataOnly) { - this.InputStream.Skip(remaining); + stream.Skip(remaining); } else { - this.ProcessDefineHuffmanTablesMarker(remaining); + this.ProcessDefineHuffmanTablesMarker(stream, remaining); } break; case JpegConstants.Markers.DQT: - this.ProcessDefineQuantizationTablesMarker(remaining); + this.ProcessDefineQuantizationTablesMarker(stream, remaining); break; case JpegConstants.Markers.DRI: if (metadataOnly) { - this.InputStream.Skip(remaining); + stream.Skip(remaining); } else { - this.ProcessDefineRestartIntervalMarker(remaining); + this.ProcessDefineRestartIntervalMarker(stream, remaining); } break; case JpegConstants.Markers.APP0: - this.ProcessApplicationHeaderMarker(remaining); + this.ProcessApplicationHeaderMarker(stream, remaining); break; case JpegConstants.Markers.APP1: - this.ProcessApp1Marker(remaining); + this.ProcessApp1Marker(stream, remaining); break; case JpegConstants.Markers.APP2: - this.ProcessApp2Marker(remaining); + this.ProcessApp2Marker(stream, remaining); break; case JpegConstants.Markers.APP3: @@ -353,39 +347,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: + stream.Skip(remaining); + break; + case JpegConstants.Markers.APP13: - this.InputStream.Skip(remaining); + this.ProcessApp13Marker(stream, remaining); break; case JpegConstants.Markers.APP14: - this.ProcessApp14Marker(remaining); + this.ProcessApp14Marker(stream, remaining); break; case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: - this.InputStream.Skip(remaining); + stream.Skip(remaining); break; } } // Read on. - fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream); + fileMarker = FindNextFileMarker(this.markerBuffer, stream); } } /// public void Dispose() { - this.InputStream?.Dispose(); this.Frame?.Dispose(); - this.fastACTables?.Dispose(); // Set large fields to null. - this.InputStream = null; this.Frame = null; this.dcHuffmanTables = null; this.acHuffmanTables = null; - this.fastACTables = null; } /// @@ -401,15 +394,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (this.ComponentCount == 3) { - if (this.adobe.Equals(default) || this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr) - { - return JpegColorSpace.YCbCr; - } - - if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) + if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) { 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 (this.ComponentCount == 4) @@ -419,7 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg : JpegColorSpace.Cmyk; } - throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}"); + JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}"); + return default; } /// @@ -429,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { if (this.isExif) { - this.MetaData.ExifProfile = new ExifProfile(this.exifData); + this.Metadata.ExifProfile = new ExifProfile(this.exifData); } } @@ -443,21 +436,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg var profile = new IccProfile(this.iccData); if (profile.CheckIsValid()) { - this.MetaData.IccProfile = profile; + this.Metadata.IccProfile = profile; } } } /// - /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. + /// Initializes the IPTC profile. + /// + private void InitIptcProfile() + { + if (this.isIptc) + { + var profile = new IptcProfile(this.iptcData); + this.Metadata.IptcProfile = profile; + } + } + + /// + /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. /// - private void InitDerivedMetaDataProperties() + 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; + this.Metadata.HorizontalResolution = this.jFif.XDensity; + this.Metadata.VerticalResolution = this.jFif.YDensity; + this.Metadata.ResolutionUnits = this.jFif.DensityUnits; } else if (this.isExif) { @@ -466,31 +471,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (horizontalValue > 0 && verticalValue > 0) { - this.MetaData.HorizontalResolution = horizontalValue; - this.MetaData.VerticalResolution = verticalValue; - this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile); + this.Metadata.HorizontalResolution = horizontalValue; + this.Metadata.VerticalResolution = verticalValue; + this.Metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.Metadata.ExifProfile); } } } - private double GetExifResolutionValue(ExifTag tag) + private double GetExifResolutionValue(ExifTag tag) { - if (!this.MetaData.ExifProfile.TryGetValue(tag, out ExifValue exifValue)) - { - return 0; - } + IExifValue resolution = this.Metadata.ExifProfile.GetValue(tag); - switch (exifValue.DataType) - { - 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; - } + return resolution is null ? 0 : resolution.Value.ToDouble(); } /// @@ -509,18 +501,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Processes the application header containing the JFIF identifier plus extra data. /// + /// The input stream. /// The remaining bytes in the segment block. - private void ProcessApplicationHeaderMarker(int remaining) + private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining) { // We can only decode JFif identifiers. if (remaining < JFifMarker.Length) { // Skip the application header length - this.InputStream.Skip(remaining); + stream.Skip(remaining); return; } - this.InputStream.Read(this.temp, 0, JFifMarker.Length); + stream.Read(this.temp, 0, JFifMarker.Length); remaining -= JFifMarker.Length; JFifMarker.TryParse(this.temp, out this.jFif); @@ -528,26 +521,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // TODO: thumbnail if (remaining > 0) { - this.InputStream.Skip(remaining); + if (stream.Position + remaining >= stream.Length) + { + JpegThrowHelper.ThrowInvalidImageContentException("Bad App0 Marker length."); + } + + stream.Skip(remaining); } } /// /// Processes the App1 marker retrieving any stored metadata /// + /// The input stream. /// The remaining bytes in the segment block. - private void ProcessApp1Marker(int remaining) + private void ProcessApp1Marker(BufferedReadStream stream, int remaining) { const int Exif00 = 6; if (remaining < Exif00 || this.IgnoreMetadata) { // Skip the application header length - this.InputStream.Skip(remaining); + stream.Skip(remaining); return; } - byte[] profile = new byte[remaining]; - this.InputStream.Read(profile, 0, remaining); + if (stream.Position + remaining >= stream.Length) + { + JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); + } + + var profile = new byte[remaining]; + stream.Read(profile, 0, remaining); if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) { @@ -568,26 +572,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Processes the App2 marker retrieving any stored ICC profile information /// + /// The input stream. /// The remaining bytes in the segment block. - private void ProcessApp2Marker(int remaining) + 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.IgnoreMetadata) { - this.InputStream.Skip(remaining); + stream.Skip(remaining); return; } - byte[] identifier = new byte[Icclength]; - this.InputStream.Read(identifier, 0, Icclength); + var identifier = new byte[Icclength]; + stream.Read(identifier, 0, Icclength); remaining -= Icclength; // We have read it by this point if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) { this.isIcc = true; - byte[] profile = new byte[remaining]; - this.InputStream.Read(profile, 0, remaining); + var profile = new byte[remaining]; + stream.Read(profile, 0, remaining); if (this.iccData is null) { @@ -602,95 +607,198 @@ namespace SixLabors.ImageSharp.Formats.Jpeg else { // Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this. - this.InputStream.Skip(remaining); + stream.Skip(remaining); + } + } + + /// + /// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop. + /// The content 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.IgnoreMetadata) + { + stream.Skip(remaining); + return; + } + + stream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length); + remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker)) + { + var resourceBlockData = new byte[remaining]; + stream.Read(resourceBlockData, 0, remaining); + Span blockDataSpan = resourceBlockData.AsSpan(); + + while (blockDataSpan.Length > 12) + { + if (!ProfileResolver.IsProfile(blockDataSpan.Slice(0, 4), ProfileResolver.AdobeImageResourceBlockMarker)) + { + return; + } + + blockDataSpan = blockDataSpan.Slice(4); + Span imageResourceBlockId = blockDataSpan.Slice(0, 2); + if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker)) + { + var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); + var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); + int dataStartIdx = 2 + resourceBlockNameLength + 4; + if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) + { + this.isIptc = true; + this.iptcData = blockDataSpan.Slice(dataStartIdx, resourceDataSize).ToArray(); + break; + } + } + else + { + var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); + var 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.Slice(dataStartIdx + resourceDataSize); + } + } } } + /// + /// 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]; + var nameDataSize = nameLength == 0 ? 2 : nameLength; + if (nameDataSize % 2 != 0) + { + nameDataSize++; + } + + 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) + { + return 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(int remaining) + private void ProcessApp14Marker(BufferedReadStream stream, int remaining) { const int MarkerLength = AdobeMarker.Length; if (remaining < MarkerLength) { // Skip the application header length - this.InputStream.Skip(remaining); + stream.Skip(remaining); return; } - this.InputStream.Read(this.temp, 0, MarkerLength); + stream.Read(this.temp, 0, MarkerLength); remaining -= MarkerLength; AdobeMarker.TryParse(this.temp, out this.adobe); if (remaining > 0) { - this.InputStream.Skip(remaining); + stream.Skip(remaining); } } /// /// 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(int remaining) + private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining) { while (remaining > 0) { bool done = false; remaining--; - int quantizationTableSpec = this.InputStream.ReadByte(); + int quantizationTableSpec = stream.ReadByte(); + int tableIndex = quantizationTableSpec & 15; + + // Max index. 4 Tables max. + if (tableIndex > 3) + { + JpegThrowHelper.ThrowBadQuantizationTable(); + } switch (quantizationTableSpec >> 4) { case 0: + { + // 8 bit values + if (remaining < 64) { - // 8 bit values - if (remaining < 64) - { - done = true; - break; - } + done = true; + break; + } - this.InputStream.Read(this.temp, 0, 64); - remaining -= 64; + stream.Read(this.temp, 0, 64); + remaining -= 64; - ref Block8x8F table = ref this.QuantizationTables[quantizationTableSpec & 15]; - for (int j = 0; j < 64; j++) - { - table[j] = this.temp[j]; - } + ref Block8x8F table = ref this.QuantizationTables[tableIndex]; + for (int j = 0; j < 64; j++) + { + table[j] = this.temp[j]; } + } - break; + break; case 1: + { + // 16 bit values + if (remaining < 128) { - // 16 bit values - if (remaining < 128) - { - done = true; - break; - } + done = true; + break; + } - this.InputStream.Read(this.temp, 0, 128); - remaining -= 128; + stream.Read(this.temp, 0, 128); + remaining -= 128; - ref Block8x8F table = ref this.QuantizationTables[quantizationTableSpec & 15]; - for (int j = 0; j < 64; j++) - { - table[j] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]; - } + 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]; } + } + + break; - break; default: - throw new ImageFormatException("Bad Tq index value"); + { + JpegThrowHelper.ThrowBadQuantizationTable(); + break; + } } if (done) @@ -701,31 +809,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (remaining != 0) { - throw new ImageFormatException("DQT has wrong length"); + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); } - this.MetaData.GetFormatMetaData(JpegFormat.Instance).Quality = QualityEvaluator.EstimateQuality(this.QuantizationTables); + this.Metadata.GetFormatMetadata(JpegFormat.Instance).Quality = QualityEvaluator.EstimateQuality(this.QuantizationTables); } /// /// 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. /// Whether to parse metadata only - private void ProcessStartOfFrameMarker(int remaining, in JpegFileMarker frameMarker, bool metadataOnly) + private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, bool metadataOnly) { if (this.Frame != null) { - throw new ImageFormatException("Multiple SOF markers. Only single frame jpegs supported."); + if (metadataOnly) + { + return; + } + + JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); } - this.InputStream.Read(this.temp, 0, remaining); + // Read initial marker definitions. + const int length = 6; + stream.Read(this.temp, 0, length); // We only support 8-bit and 12-bit precision. - if (!this.supportedPrecisions.Contains(this.temp[0])) + if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1) { - throw new ImageFormatException("Only 8-Bit and 12-Bit precision supported."); + JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported."); } this.Precision = this.temp[0]; @@ -735,31 +851,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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]), + Scanlines = (this.temp[1] << 8) | this.temp[2], + SamplesPerLine = (this.temp[3] << 8) | this.temp[4], ComponentCount = this.temp[5] }; - this.ImageSizeInPixels = new Size(this.Frame.SamplesPerLine, this.Frame.Scanlines); - - int maxH = 0; - int maxV = 0; - int index = 6; + if (this.Frame.SamplesPerLine == 0 || this.Frame.Scanlines == 0) + { + JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.SamplesPerLine, this.Frame.Scanlines); + } + this.ImageSizeInPixels = new Size(this.Frame.SamplesPerLine, this.Frame.Scanlines); this.ComponentCount = this.Frame.ComponentCount; if (!metadataOnly) { + remaining -= length; + + const int componentBytes = 3; + if (remaining > this.ComponentCount * componentBytes) + { + JpegThrowHelper.ThrowBadMarker("SOFn", remaining); + } + + stream.Read(this.temp, 0, remaining); + // 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(); + 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 h = (hv >> 4) & 15; int v = hv & 15; if (maxH < h) @@ -772,12 +901,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg maxV = v; } - var component = new JpegComponent(this.configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i); + 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 += 3; + index += componentBytes; } this.Frame.MaxHorizontalFactor = maxH; @@ -792,18 +921,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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(int remaining) + private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining) { - using (IManagedByteBuffer huffmanData = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) + int length = remaining; + + using (IManagedByteBuffer huffmanData = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) { ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan()); for (int i = 2; i < remaining;) { - byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); - this.InputStream.Read(huffmanData.Array, 0, 16); + 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."); + } + + // Max tables of each type + if (tableIndex > 3) + { + JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index."); + } + + stream.Read(huffmanData.Array, 0, 16); - using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean)) + using (IManagedByteBuffer codeLengths = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean)) { ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan()); int codeLengthSum = 0; @@ -813,26 +960,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1); } - using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) + length -= 17; + + if (codeLengthSum > 256 || codeLengthSum > length) { - this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); + JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); + } - i += 17 + codeLengthSum; + using (IManagedByteBuffer huffmanValues = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) + { + stream.Read(huffmanValues.Array, 0, codeLengthSum); - int tableType = huffmanTableSpec >> 4; - int tableIndex = huffmanTableSpec & 15; + i += 17 + codeLengthSum; this.BuildHuffmanTable( tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables, tableIndex, codeLengths.GetSpan(), huffmanValues.GetSpan()); - - if (tableType != 0) - { - // Build a table that decodes both magnitude and value of small ACs in one go. - this.fastACTables.BuildACTableLut(tableIndex, this.acHuffmanTables); - } } } } @@ -843,32 +988,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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. - private void ProcessDefineRestartIntervalMarker(int remaining) + private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining) { if (remaining != 2) { - throw new ImageFormatException($"DRI has wrong length: {remaining}"); + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); } - this.resetInterval = this.ReadUint16(); + this.resetInterval = this.ReadUint16(stream); } /// /// Processes the SOS (Start of scan marker). /// - private void ProcessStartOfScanMarker() + private void ProcessStartOfScanMarker(BufferedReadStream stream, CancellationToken cancellationToken) { if (this.Frame is null) { - throw new ImageFormatException("No readable SOFn (Start Of Frame) marker found."); + JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); } - int selectorsCount = this.InputStream.ReadByte(); + int selectorsCount = stream.ReadByte(); for (int i = 0; i < selectorsCount; i++) { int componentIndex = -1; - int selector = this.InputStream.ReadByte(); + int selector = stream.ReadByte(); for (int j = 0; j < this.Frame.ComponentIds.Length; j++) { @@ -882,34 +1028,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (componentIndex < 0) { - throw new ImageFormatException("Unknown component selector"); + JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}."); } ref JpegComponent component = ref this.Frame.Components[componentIndex]; - int tableSpec = this.InputStream.ReadByte(); + int tableSpec = stream.ReadByte(); component.DCHuffmanTableId = tableSpec >> 4; component.ACHuffmanTableId = tableSpec & 15; this.Frame.ComponentOrder[i] = (byte)componentIndex; } - this.InputStream.Read(this.temp, 0, 3); + stream.Read(this.temp, 0, 3); int spectralStart = this.temp[0]; int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; - var sd = new ScanDecoder( - this.InputStream, + var sd = new HuffmanScanDecoder( + stream, this.Frame, this.dcHuffmanTables, this.acHuffmanTables, - this.fastACTables, selectorsCount, this.resetInterval, spectralStart, spectralEnd, successiveApproximation >> 4, - successiveApproximation & 15); + successiveApproximation & 15, + cancellationToken); sd.ParseEntropyCodedData(); } @@ -922,17 +1068,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The codelengths /// The values [MethodImpl(InliningOptions.ShortMethod)] - private void BuildHuffmanTable(HuffmanTables tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values) - => tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values); + private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values) + => tables[index] = new HuffmanTable(codeLengths, values); /// /// Reads a from the stream advancing it by two bytes /// + /// The input stream. /// The [MethodImpl(InliningOptions.ShortMethod)] - private ushort ReadUint16() + private ushort ReadUint16(BufferedReadStream stream) { - this.InputStream.Read(this.markerBuffer, 0, 2); + stream.Read(this.markerBuffer, 0, 2); return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); } @@ -941,21 +1088,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The pixel format. /// The . - private Image PostProcessIntoImage() - where TPixel : struct, IPixel + private Image PostProcessIntoImage(CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { + if (this.ImageWidth == 0 || this.ImageHeight == 0) + { + JpegThrowHelper.ThrowInvalidImageDimensions(this.ImageWidth, this.ImageHeight); + } + var image = Image.CreateUninitialized( - this.configuration, + this.Configuration, this.ImageWidth, this.ImageHeight, - this.MetaData); + this.Metadata); - using (var postProcessor = new JpegImagePostProcessor(this.configuration, this)) + using (var postProcessor = new JpegImagePostProcessor(this.Configuration, this)) { - postProcessor.PostProcess(image.Frames.RootFrame); + postProcessor.PostProcess(image.Frames.RootFrame, cancellationToken); } return image; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index d649d30418..b549bd8a32 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -1,7 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -30,10 +32,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var encoder = new JpegEncoderCore(this); encoder.Encode(image, stream); } + + /// + /// 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 monitor for cancellation requests. + /// A representing the asynchronous operation. + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new JpegEncoderCore(this); + return encoder.EncodeAsync(image, stream, cancellationToken); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index b3f30f815f..c4ff1c0360 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,13 +6,16 @@ using System.Buffers.Binary; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; 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.Memory; +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; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -20,92 +23,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Image encoder for writing an image to a stream as a jpeg. /// - internal sealed unsafe class JpegEncoderCore + internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals { /// /// The number of quantization tables. /// private const int QuantizationTableCount = 2; - /// - /// Counts the number of bits needed to hold an integer. - /// - private static readonly uint[] BitCountLut = - { - 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, - }; - - /// - /// 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. - /// - private static readonly byte[] SosHeaderYCbCr = - { - 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) - }; - - /// - /// 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. - /// - private static readonly byte[,] UnscaledQuant = - { - { - // 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, - }, - { - // 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, - } - }; - /// /// A scratch buffer to reduce allocations. /// @@ -167,29 +91,128 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 + /// 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)); + cancellationToken.ThrowIfCancellationRequested(); - ushort max = JpegConstants.MaxLength; + const 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}."); } this.outputStream = stream; - ImageMetaData metaData = image.MetaData; + ImageMetadata metadata = image.Metadata; // 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); + int qlty = (this.quality ?? metadata.GetJpegMetadata().Quality).Clamp(1, 100); this.subsample = this.subsample ?? (qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); // Convert from a quality rating to a scaling factor. @@ -208,13 +231,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg InitQuantizationTable(1, scale, ref this.chrominanceQuantTable); // Compute number of components based on input image type. - int componentCount = 3; + const int componentCount = 3; // Write the Start Of Image marker. - this.WriteApplicationHeader(metaData); + this.WriteApplicationHeader(metadata); - // Write Exif and ICC profiles - this.WriteProfiles(metaData); + // Write Exif, ICC and IPTC profiles + this.WriteProfiles(metadata); // Write the quantization tables. this.WriteDefineQuantizationTables(); @@ -226,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.WriteDefineHuffmanTables(componentCount); // Write the image data. - this.WriteStartOfScan(image); + this.WriteStartOfScan(image, cancellationToken); // Write the End Of Image marker. this.buffer[0] = JpegConstants.Markers.XFF; @@ -259,9 +282,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The quantization table. private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) { + DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i)); + ReadOnlySpan unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance; + for (int j = 0; j < Block8x8F.Size; j++) { - int x = UnscaledQuant[i, j]; + int x = unscaledQuant[j]; x = ((x * scale) + 50) / 100; if (x < 1) { @@ -357,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - bt = 8 + BitCountLut[a >> 8]; + bt = 8 + (uint)BitCountLut[a >> 8]; } this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); @@ -372,8 +398,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ///
/// The pixel format. /// The pixel accessor providing access to the image pixels. - private void Encode444(Image pixels) - where TPixel : struct, IPixel + /// The token to monitor for cancellation. + private void Encode444(Image pixels, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // (Partially done with YCbCrForwardConverter) @@ -389,37 +416,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; var pixelConverter = YCbCrForwardConverter.Create(); + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; for (int y = 0; y < pixels.Height; y += 8) { + cancellationToken.ThrowIfCancellationRequested(); + var currentRows = new RowOctet(pixelBuffer, y); + for (int x = 0; x < pixels.Width; x += 8) { - pixelConverter.Convert(pixels.Frames.RootFrame, x, y); + pixelConverter.Convert(frame, x, y, currentRows); prevDCY = this.WriteBlock( QuantIndex.Luminance, prevDCY, - &pixelConverter.Y, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); + ref pixelConverter.Y, + ref temp1, + ref temp2, + ref onStackLuminanceQuantTable, + ref unzig); prevDCCb = this.WriteBlock( QuantIndex.Chrominance, prevDCCb, - &pixelConverter.Cb, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); + ref pixelConverter.Cb, + ref temp1, + ref temp2, + ref onStackChrominanceQuantTable, + ref unzig); prevDCCr = this.WriteBlock( QuantIndex.Chrominance, prevDCCr, - &pixelConverter.Cr, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); + ref pixelConverter.Cr, + ref temp1, + ref temp2, + ref onStackChrominanceQuantTable, + ref unzig); } } } @@ -427,8 +459,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Writes the application header containing the JFIF identifier plus extra data. /// - /// The image meta data. - private void WriteApplicationHeader(ImageMetaData meta) + /// 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; @@ -484,25 +516,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Temporal block to be used as FDCT Destination /// Temporal block 2 /// Quantization table - /// The 8x8 Unzig block pointer + /// The 8x8 Unzig block. /// /// The /// private int WriteBlock( QuantIndex index, int prevDC, - Block8x8F* src, - Block8x8F* tempDest1, - Block8x8F* tempDest2, - Block8x8F* quant, - byte* unzigPtr) + ref Block8x8F src, + ref Block8x8F tempDest1, + ref Block8x8F tempDest2, + ref Block8x8F quant, + ref ZigZag unZig) { - FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2); + FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2); - Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr); - float* unziggedDestPtr = (float*)tempDest2; + Block8x8F.Quantize(ref tempDest1, ref tempDest2, ref quant, ref unZig); - int dc = (int)unziggedDestPtr[0]; + int dc = (int)tempDest2[0]; // Emit the DC delta. this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC); @@ -513,7 +544,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg for (int zig = 1; zig < Block8x8F.Size; zig++) { - int ac = (int)unziggedDestPtr[zig]; + int ac = (int)tempDest2[zig]; if (ac == 0) { @@ -622,67 +653,115 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Writes the EXIF profile. ///
/// The exif profile. - /// - /// Thrown if the EXIF profile size exceeds the limit - /// private void WriteExifProfile(ExifProfile exifProfile) { - if (exifProfile is null) + if (exifProfile is null || exifProfile.Values.Count == 0) { return; } - const int MaxBytesApp1 = 65533; - const int MaxBytesWithExifId = 65527; + const int MaxBytesApp1 = 65533; // 64k - 2 padding bytes + const int MaxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. - byte[] data = exifProfile?.ToByteArray(); + byte[] data = exifProfile.ToByteArray(); - if (data is null || data.Length == 0) + if (data.Length == 0) { return; } - data = ProfileResolver.ExifMarker.Concat(data).ToArray(); - - int remaining = data.Length; + // We can write up to a maximum of 64 data to the initial marker so calculate boundaries. + int exifMarkerLength = ProfileResolver.ExifMarker.Length; + int remaining = exifMarkerLength + data.Length; int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining; int app1Length = bytesToWrite + 2; + // Write the app marker, EXIF marker, and data this.WriteApp1Header(app1Length); - - // write the exif data - this.outputStream.Write(data, 0, bytesToWrite); + this.outputStream.Write(ProfileResolver.ExifMarker); + this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength); remaining -= bytesToWrite; - // if the exif data exceeds 64K, write it in multiple APP1 Markers - for (int idx = MaxBytesApp1; idx < data.Length; idx += MaxBytesWithExifId) + // 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 + 6; + app1Length = bytesToWrite + 2 + exifMarkerLength; this.WriteApp1Header(app1Length); - // write Exif00 marker - ProfileResolver.ExifMarker.AsSpan().CopyTo(this.buffer.AsSpan()); - this.outputStream.Write(this.buffer, 0, 6); + // Write Exif00 marker + this.outputStream.Write(ProfileResolver.ExifMarker); - // write the exif data + // Write the exif data this.outputStream.Write(data, idx, bytesToWrite); remaining -= bytesToWrite; } } + /// + /// Writes the IPTC metadata. + /// + /// The iptc metadata to write. + /// + /// Thrown if the IPTC profile size exceeds the limit of 65533 bytes. + /// + private void WriteIptcProfile(IptcProfile iptcProfile) + { + const int Max = 65533; + if (iptcProfile is null || !iptcProfile.Values.Any()) + { + return; + } + + iptcProfile.UpdateData(); + byte[] data = iptcProfile.Data; + if (data.Length == 0) + { + return; + } + + if (data.Length > Max) + { + throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes"); + } + + var app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length + + ProfileResolver.AdobeImageResourceBlockMarker.Length + + ProfileResolver.AdobeIptcMarker.Length + + 2 + 4 + data.Length; + this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13); + this.outputStream.Write(ProfileResolver.AdobePhotoshopApp13Marker); + this.outputStream.Write(ProfileResolver.AdobeImageResourceBlockMarker); + this.outputStream.Write(ProfileResolver.AdobeIptcMarker); + this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even) + this.outputStream.WriteByte(0); + BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length); + this.outputStream.Write(this.buffer, 0, 4); + this.outputStream.Write(data, 0, data.Length); + } + /// /// Writes the App1 header. /// - /// The length of the data the app1 marker contains + /// The length of the data the app1 marker contains. private void WriteApp1Header(int app1Length) + { + this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1); + } + + /// + /// Writes a AppX header. + /// + /// The length of the data the app marker contains. + /// The app marker to write. + private void WriteAppHeader(int length, byte appMarker) { 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); + this.buffer[1] = appMarker; + this.buffer[2] = (byte)((length >> 8) & 0xFF); + this.buffer[3] = (byte)(length & 0xFF); this.outputStream.Write(this.buffer, 0, 4); } @@ -770,17 +849,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Writes the metadata profiles to the image. /// - /// The image meta data. - private void WriteProfiles(ImageMetaData metaData) + /// The image metadata. + private void WriteProfiles(ImageMetadata metadata) { - if (metaData is null) + if (metadata is null) { return; } - metaData.SyncProfiles(); - this.WriteExifProfile(metaData.ExifProfile); - this.WriteIccProfile(metaData.IccProfile); + metadata.SyncProfiles(); + this.WriteExifProfile(metadata.ExifProfile); + this.WriteIccProfile(metadata.IccProfile); + this.WriteIptcProfile(metadata.IptcProfile); } /// @@ -866,20 +946,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void WriteStartOfScan(Image image) - where TPixel : struct, IPixel + /// The token to monitor for cancellation. + private void WriteStartOfScan(Image image, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { // 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, 0, SosHeaderYCbCr.Length); + this.outputStream.Write(SosHeaderYCbCr); switch (this.subsample) { case JpegSubsample.Ratio444: - this.Encode444(image); + this.Encode444(image, cancellationToken); break; case JpegSubsample.Ratio420: - this.Encode420(image); + this.Encode420(image, cancellationToken); break; } @@ -893,16 +974,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void Encode420(Image pixels) - where TPixel : struct, IPixel + /// The token to monitor for cancellation. + private void Encode420(Image pixels, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default; - - BlockQuad cb = default; - BlockQuad cr = default; - var cbPtr = (Block8x8F*)cb.Data; - var crPtr = (Block8x8F*)cr.Data; + Span cb = stackalloc Block8x8F[4]; + Span cr = stackalloc Block8x8F[4]; Block8x8F temp1 = default; Block8x8F temp2 = default; @@ -916,9 +995,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; for (int y = 0; y < pixels.Height; y += 16) { + cancellationToken.ThrowIfCancellationRequested(); for (int x = 0; x < pixels.Width; x += 16) { for (int i = 0; i < 4; i++) @@ -926,40 +1008,43 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff); + // TODO: Try pushing this to the outer loop! + var currentRows = new RowOctet(pixelBuffer, y + yOff); + + pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows); - cbPtr[i] = pixelConverter.Cb; - crPtr[i] = pixelConverter.Cr; + cb[i] = pixelConverter.Cb; + cr[i] = pixelConverter.Cr; prevDCY = this.WriteBlock( QuantIndex.Luminance, prevDCY, - &pixelConverter.Y, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); + ref pixelConverter.Y, + ref temp1, + ref temp2, + ref onStackLuminanceQuantTable, + ref unzig); } - Block8x8F.Scale16X16To8X8(&b, cbPtr); + Block8x8F.Scale16X16To8X8(ref b, cb); prevDCCb = this.WriteBlock( QuantIndex.Chrominance, prevDCCb, - &b, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); + ref b, + ref temp1, + ref temp2, + ref onStackChrominanceQuantTable, + ref unzig); - Block8x8F.Scale16X16To8X8(&b, crPtr); + Block8x8F.Scale16X16To8X8(ref b, cr); prevDCCr = this.WriteBlock( QuantIndex.Chrominance, prevDCCr, - &b, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); + ref b, + ref temp1, + ref temp2, + ref onStackChrominanceQuantTable, + ref unzig); } } } @@ -979,4 +1064,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.outputStream.Write(this.buffer, 0, 4); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs index 6b23ceac7a..f4a8a8bf2d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// - public sealed class JpegFormat : IImageFormat + public sealed class JpegFormat : IImageFormat { private JpegFormat() { @@ -32,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public IEnumerable FileExtensions => JpegConstants.FileExtensions; /// - public JpegMetaData CreateDefaultFormatMetaData() => new JpegMetaData(); + 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 7594f44770..660ed38148 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs deleted file mode 100644 index fcad29e5d0..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 new file mode 100644 index 0000000000..c9dded6352 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// 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/JpegSubsample.cs b/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs index 8558157059..6597e0ccb8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index c7f3666604..fa9eb83917 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -1,6 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -8,19 +9,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg internal static class JpegThrowHelper { /// - /// Cold path optimization for throwing -s + /// 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); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowBadHuffmanCode() - { - throw new ImageFormatException("Bad Huffman code."); - } + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + /// The exception that is the cause of the current exception, or a null reference + /// if no inner exception is specified. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException); + + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotImplementedException(string errorMessage) + => throw new NotImplementedException(errorMessage); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadQuantizationTable() => throw new InvalidImageContentException("Bad Quantization Table index."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor."); + + [MethodImpl(InliningOptions.ColdPath)] + 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}."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}."); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs new file mode 100644 index 0000000000..d154f3490f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the jpeg format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/itu-t81.pdf b/src/ImageSharp/Formats/Jpeg/itu-t81.pdf index 83fba254b4..1d57c76c2d 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/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs index ed21b91bfc..d53d496fa5 100644 --- a/src/ImageSharp/Formats/PixelTypeInfo.cs +++ b/src/ImageSharp/Formats/PixelTypeInfo.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Formats { /// @@ -21,5 +25,9 @@ namespace SixLabors.ImageSharp.Formats /// Gets color depth, in number of bits per pixel. /// public int BitsPerPixel { get; } + + internal static PixelTypeInfo Create() + where TPixel : unmanaged, IPixel => + new PixelTypeInfo(Unsafe.SizeOf() * 8); } } diff --git a/src/ImageSharp/Formats/Png/Adam7.cs b/src/ImageSharp/Formats/Png/Adam7.cs index 4e6485b55f..50792cae18 100644 --- a/src/ImageSharp/Formats/Png/Adam7.cs +++ b/src/ImageSharp/Formats/Png/Adam7.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -31,6 +31,34 @@ namespace SixLabors.ImageSharp.Formats.Png /// public static readonly int[] RowIncrement = { 8, 8, 8, 4, 4, 2, 2 }; + /// + /// Gets the width of the block. + /// + /// The width. + /// The pass. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ComputeBlockWidth(int width, int pass) + { + 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. /// @@ -53,4 +81,4 @@ namespace SixLabors.ImageSharp.Formats.Png } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs index 1d0e280bd3..daf653532d 100644 --- a/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs +++ b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Png.Chunks { @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Chunks /// /// The metadata. /// The constructed PngPhysicalChunkData instance. - public static PhysicalChunkData FromMetadata(ImageMetaData meta) + public static PhysicalChunkData FromMetadata(ImageMetadata meta) { byte unitSpecifier = 0; uint x; diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index bc5a54e8b9..8d9f6e4156 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Png/Filters/FilterType.cs b/src/ImageSharp/Formats/Png/Filters/FilterType.cs index 83a005380a..bd6aa3c595 100644 --- a/src/ImageSharp/Formats/Png/Filters/FilterType.cs +++ b/src/ImageSharp/Formats/Png/Filters/FilterType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Png.Filters diff --git a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs index 97e16ef233..cdf47a24fb 100644 --- a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 4cd61e043d..7b5c71a010 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 6af5f0b648..c448e71f43 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -25,12 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); // Sub(x) + Raw(x-bpp) - int x = 1; - for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - } - + int x = bytesPerPixel + 1; + Unsafe.Add(ref scanBaseRef, x); for (; x < scanline.Length; ++x) { ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 5d9dc6a890..2a77bccb97 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs index 5b650ac2a0..4b09c5b1ca 100644 --- a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Text; - namespace SixLabors.ImageSharp.Formats.Png { /// @@ -14,10 +12,5 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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 index 7e5a9fa6b8..2c05019eda 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Png { /// - /// The options available for manipulating the encoder pipeline + /// The options available for manipulating the encoder pipeline. /// internal interface IPngEncoderOptions { @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Png PngBitDepth? BitDepth { get; } /// - /// Gets the color type + /// Gets the color type. /// PngColorType? ColorType { get; } @@ -28,12 +28,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Gets the compression level 1-9. - /// Defaults to 6. + /// Defaults to . /// - int CompressionLevel { get; } + PngCompressionLevel CompressionLevel { get; } /// - /// Gets the gamma value, that will be written the the image. + /// Gets the threshold of characters in text metadata, when compression should be used. + /// + int TextCompressionThreshold { get; } + + /// + /// Gets the gamma value, that will be written the image. /// /// The gamma value of the image. float? Gamma { get; } @@ -47,5 +52,27 @@ namespace SixLabors.ImageSharp.Formats.Png /// Gets the transparency threshold. /// byte Threshold { get; } + + /// + /// Gets a value indicating whether this instance should write an Adam7 interlaced image. + /// + PngInterlaceMode? InterlaceMethod { get; } + + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. + /// When set to true, all ancillary chunks will be skipped. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the chunk filter method. This allows to filter ancillary chunks. + /// + PngChunkFilter? ChunkFilter { get; } + + /// + /// Gets a value indicating whether fully transparent pixels that may contain R, G, B values which are not 0, + /// should be converted to transparent black, which can yield in better compression in some cases. + /// + PngTransparentColorMode TransparentColorMode { get; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs deleted file mode 100644 index c73ec6f57e..0000000000 --- a/src/ImageSharp/Formats/Png/ImageExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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/MetadataExtensions.cs b/src/ImageSharp/Formats/Png/MetadataExtensions.cs new file mode 100644 index 0000000000..b7c2cccf5b --- /dev/null +++ b/src/ImageSharp/Formats/Png/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the png format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static PngMetadata GetPngMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PngFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Png/PngBitDepth.cs b/src/ImageSharp/Formats/Png/PngBitDepth.cs index 0321b532ab..e03f80f286 100644 --- a/src/ImageSharp/Formats/Png/PngBitDepth.cs +++ b/src/ImageSharp/Formats/Png/PngBitDepth.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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. diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index c75f9465af..fd11ba1b6b 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Png { @@ -10,12 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal readonly struct PngChunk { - public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = 0) + public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null) { this.Length = length; this.Type = type; this.Data = data; - this.Crc = crc; } /// @@ -38,20 +37,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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; + this.Type == PngChunkType.Data; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngChunkFilter.cs b/src/ImageSharp/Formats/Png/PngChunkFilter.cs new file mode 100644 index 0000000000..4d6a20bc91 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngChunkFilter.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 1b251a5748..39f19f232b 100644 --- a/src/ImageSharp/Formats/Png/PngChunkType.cs +++ b/src/ImageSharp/Formats/Png/PngChunkType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Png @@ -55,11 +55,76 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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. + /// + CompressedText = 0x7A545874U, + + /// + /// The iTXt 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. + /// + InternationalText = 0x69545874U, + /// /// 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 + Transparency = 0x74524E53U, + + /// + /// The tIME chunk gives the time of the last image modification (not the time of initial image creation). + /// + Time = 0x74494d45, + + /// + /// The bKGD 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. + /// + Background = 0x624b4744, + + /// + /// The iCCP 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. + /// + EmbeddedColorProfile = 0x69434350, + + /// + /// The sBIT 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. + /// + SignificantBits = 0x73424954, + + /// + /// If the sRGB 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. + /// + StandardRgbColourSpace = 0x73524742, + + /// + /// The hIST chunk gives the approximate usage frequency of each colour in the palette. + /// + Histogram = 0x68495354, + + /// + /// The sPLT chunk contains the suggested palette. + /// + SuggestedPalette = 0x73504c54, + + /// + /// The cHRM 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. + /// + Chroma = 0x6348524d, + + /// + /// 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 + /// + ProprietaryApple = 0x43674249 } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngColorType.cs b/src/ImageSharp/Formats/Png/PngColorType.cs index fc376ca161..cb9d819ba5 100644 --- a/src/ImageSharp/Formats/Png/PngColorType.cs +++ b/src/ImageSharp/Formats/Png/PngColorType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Png diff --git a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs new file mode 100644 index 0000000000..7516e0987d --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Provides enumeration of available PNG compression levels. + /// + 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 3c9fddbad4..0306691890 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Png diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index e1f978e1ac..b4ef28083e 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -1,52 +1,51 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text; namespace SixLabors.ImageSharp.Formats.Png { /// - /// Defines png constants defined in the specification. + /// Defines Png constants defined in the specification. /// internal static class PngConstants { /// - /// The default encoding for text metadata. + /// The character encoding to use when reading and writing textual data keywords and text - (Latin-1 ISO-8859-1). /// - public static readonly Encoding DefaultEncoding = Encoding.ASCII; + public static readonly Encoding Encoding = Encoding.GetEncoding("ISO-8859-1"); /// - /// The list of mimetypes that equate to a png. + /// 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 = new[] { "image/png" }; /// - /// The list of file extensions that equate to a 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. + /// 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() + public static readonly Dictionary ColorTypes = new Dictionary { [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 }, [PngColorType.Rgb] = new byte[] { 8, 16 }, @@ -54,5 +53,30 @@ namespace SixLabors.ImageSharp.Formats.Png [PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 }, [PngColorType.RgbWithAlpha] = new byte[] { 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 => new byte[] + { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 39dfb1d0bd..479c24b7e8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -1,63 +1,57 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; -using System.Text; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png { /// - /// Encoder for generating an image out of a png encoded stream. + /// Decoder for generating an image out of a png encoded stream. /// - /// - /// 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 { - /// - /// 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 + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoderCore(configuration, this); + return decoder.Decode(configuration, stream); + } + + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - return decoder.Decode(stream); + return decoder.DecodeAsync(configuration, stream, cancellationToken); } + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + /// public IImageInfo Identify(Configuration configuration, Stream stream) { var decoder = new PngDecoderCore(configuration, this); - return decoder.Identify(stream); + return decoder.Identify(configuration, stream); + } + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + var decoder = new PngDecoderCore(configuration, this); + return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 9ffac5e626..3fa0e3f586 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1,49 +1,37 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; +using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using SixLabors.ImageSharp.Advanced; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Png { /// /// Performs the png decoding operation. /// - internal sealed class PngDecoderCore + internal sealed class PngDecoderCore : IImageDecoderInternals { /// /// 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. /// @@ -57,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The stream to decode from. /// - private Stream currentStream; + private BufferedReadStream currentStream; /// /// The png header. @@ -70,22 +58,22 @@ namespace SixLabors.ImageSharp.Formats.Png private int bytesPerPixel; /// - /// The number of bytes per sample + /// The number of bytes per sample. /// private int bytesPerSample; /// - /// The number of bytes per scanline + /// The number of bytes per scanline. /// private int bytesPerScanline; /// - /// The palette containing color information for indexed png's + /// The palette containing color information for indexed png's. /// private byte[] palette; /// - /// The palette containing alpha channel color information for indexed png's + /// The palette containing alpha channel color information for indexed png's. /// private byte[] paletteAlpha; @@ -95,37 +83,32 @@ namespace SixLabors.ImageSharp.Formats.Png private bool isEndChunkReached; /// - /// Previous scanline processed + /// Previous scanline processed. /// private IManagedByteBuffer previousScanline; /// - /// The current scanline that is being processed + /// The current scanline that is being processed. /// private IManagedByteBuffer scanline; /// - /// The index of the current scanline being processed + /// 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 + /// The current number of bytes read in the current scanline. /// private int currentRowBytesRead; /// - /// Gets or sets the png color type + /// Gets or sets the png color type. /// private PngColorType pngColorType; /// - /// The next chunk of data to return + /// The next chunk of data to return. /// private PngChunk? nextChunk; @@ -136,29 +119,25 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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.Configuration = configuration ?? Configuration.Default; + this.memoryAllocator = this.Configuration.MemoryAllocator; this.ignoreMetadata = options.IgnoreMetadata; } + /// + public Configuration Configuration { get; } + /// - /// Decodes the stream to the image. + /// Gets the dimensions of 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 + public Size Dimensions => new Size(this.header.Width, this.header.Height); + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { - var metaData = new ImageMetaData(); - PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); + var metadata = new ImageMetadata(); + PngMetadata pngMetadata = metadata.GetPngMetadata(); this.currentStream = stream; this.currentStream.Skip(8); Image image = null; @@ -171,53 +150,58 @@ namespace SixLabors.ImageSharp.Formats.Png switch (chunk.Type) { case PngChunkType.Header: - this.ReadHeaderChunk(pngMetaData, chunk.Data.Array); + this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); break; case PngChunkType.Physical: - this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan()); + this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Gamma: - this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan()); + this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Data: if (image is null) { - this.InitializeImage(metaData, out image); + 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); - } + this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata); break; case PngChunkType.Palette: - byte[] pal = new byte[chunk.Length]; + var pal = new byte[chunk.Length]; Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); this.palette = pal; break; case PngChunkType.Transparency: - byte[] alpha = new byte[chunk.Length]; + var alpha = new byte[chunk.Length]; Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); this.paletteAlpha = alpha; - this.AssignTransparentMarkers(alpha, pngMetaData); + this.AssignTransparentMarkers(alpha, pngMetadata); break; case PngChunkType.Text: - this.ReadTextChunk(metaData, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.CompressedText: + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.InternationalText: + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); break; case PngChunkType.Exif: if (!this.ignoreMetadata) { - byte[] exifData = new byte[chunk.Length]; + var exifData = new byte[chunk.Length]; Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); - metaData.ExifProfile = new ExifProfile(exifData); + metadata.ExifProfile = new ExifProfile(exifData); } break; case PngChunkType.End: this.isEndChunkReached = true; break; + case PngChunkType.ProprietaryApple: + PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded."); + break; } } finally @@ -228,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (image is null) { - throw new ImageFormatException("PNG Image does not contain a data chunk"); + PngThrowHelper.ThrowNoData(); } return image; @@ -240,14 +224,11 @@ namespace SixLabors.ImageSharp.Formats.Png } } - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - public IImageInfo Identify(Stream stream) + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - var metaData = new ImageMetaData(); - PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); + var metadata = new ImageMetadata(); + PngMetadata pngMetadata = metadata.GetPngMetadata(); this.currentStream = stream; this.currentStream.Skip(8); try @@ -259,19 +240,34 @@ namespace SixLabors.ImageSharp.Formats.Png switch (chunk.Type) { case PngChunkType.Header: - this.ReadHeaderChunk(pngMetaData, chunk.Data.Array); + this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); break; case PngChunkType.Physical: - this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan()); + this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Gamma: - this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan()); + 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)); + this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.CompressedText: + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.InternationalText: + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.Exif: + if (!this.ignoreMetadata) + { + var exifData = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + metadata.ExifProfile = new ExifProfile(exifData); + } + break; case PngChunkType.End: this.isEndChunkReached = true; @@ -292,10 +288,10 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.Width == 0 && this.header.Height == 0) { - throw new ImageFormatException("PNG Image does not contain a header chunk"); + PngThrowHelper.ThrowNoHeader(); } - return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metaData); + return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); } /// @@ -350,7 +346,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The metadata to read to. /// The data containing physical data. - private void ReadPhysicalChunk(ImageMetaData metadata, ReadOnlySpan data) + private void ReadPhysicalChunk(ImageMetadata metadata, ReadOnlySpan data) { var physicalChunk = PhysicalChunkData.Parse(data); @@ -367,7 +363,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The metadata to read to. /// The data containing physical data. - private void ReadGammaChunk(PngMetaData pngMetadata, ReadOnlySpan data) + private void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data) { // 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. @@ -380,10 +376,15 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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 + private void InitializeImage(ImageMetadata metadata, out Image image) + where TPixel : unmanaged, IPixel { - image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); + image = Image.CreateUninitialized( + this.Configuration, + this.header.Width, + this.header.Height, + metadata); + this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; this.bytesPerSample = 1; @@ -393,7 +394,7 @@ namespace SixLabors.ImageSharp.Formats.Png } this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); - this.scanline = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.scanline = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); } /// @@ -414,7 +415,8 @@ namespace SixLabors.ImageSharp.Formats.Png case PngColorType.RgbWithAlpha: return this.header.BitDepth * 4; default: - throw new NotSupportedException("Unsupported PNG color type"); + PngThrowHelper.ThrowNotSupportedColor(); + return -1; } } @@ -469,19 +471,25 @@ namespace SixLabors.ImageSharp.Formats.Png /// Reads the scanlines within the image. /// /// The pixel format. - /// The containing data. + /// The png chunk containing the compressed scanline data. /// The pixel data. - /// The png meta data - private void ReadScanlines(Stream dataStream, ImageFrame image, PngMetaData pngMetaData) - where TPixel : struct, IPixel + /// The png metadata + private void ReadScanlines(PngChunk chunk, ImageFrame image, PngMetadata pngMetadata) + where TPixel : unmanaged, IPixel { - if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) + using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) { - this.DecodeInterlacedPixelData(dataStream, image, pngMetaData); - } - else - { - this.DecodePixelData(dataStream, image, pngMetaData); + deframeStream.AllocateNewBytes(chunk.Length, true); + DeflateStream dataStream = deframeStream.CompressedStream; + + if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) + { + this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); + } + else + { + this.DecodePixelData(dataStream, image, pngMetadata); + } } } @@ -491,9 +499,9 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The compressed pixel data stream. /// The image to decode to. - /// The png meta data - private void DecodePixelData(Stream compressedStream, ImageFrame image, PngMetaData pngMetaData) - where TPixel : struct, IPixel + /// The png metadata + private void DecodePixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata) + where TPixel : unmanaged, IPixel { while (this.currentRow < this.header.Height) { @@ -529,10 +537,11 @@ namespace SixLabors.ImageSharp.Formats.Png break; default: - throw new ImageFormatException("Unknown filter type."); + PngThrowHelper.ThrowUnknownFilter(); + break; } - this.ProcessDefilteredScanline(scanlineSpan, image, pngMetaData); + this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); this.SwapBuffers(); this.currentRow++; @@ -546,17 +555,19 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The compressed pixel data stream. /// The current image. - /// The png meta data - private void DecodeInterlacedPixelData(Stream compressedStream, ImageFrame image, PngMetaData pngMetaData) - where TPixel : struct, IPixel + /// The png metadata. + private void DecodeInterlacedPixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata) + where TPixel : unmanaged, IPixel { + int pass = 0; + int width = this.header.Width; while (true) { - int numColumns = Adam7.ComputeColumns(this.header.Width, this.pass); + int numColumns = Adam7.ComputeColumns(width, pass); if (numColumns == 0) { - this.pass++; + pass++; // This pass contains no data; skip to next pass continue; @@ -600,27 +611,28 @@ namespace SixLabors.ImageSharp.Formats.Png break; default: - throw new ImageFormatException("Unknown filter type."); + PngThrowHelper.ThrowUnknownFilter(); + break; } Span rowSpan = image.GetPixelRowSpan(this.currentRow); - this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetaData, Adam7.FirstColumn[this.pass], Adam7.ColumnIncrement[this.pass]); + this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); this.SwapBuffers(); - this.currentRow += Adam7.RowIncrement[this.pass]; + this.currentRow += Adam7.RowIncrement[pass]; } - this.pass++; + pass++; this.previousScanline.Clear(); - if (this.pass < 7) + if (pass < 7) { - this.currentRow = Adam7.FirstRow[this.pass]; + this.currentRow = Adam7.FirstRow[pass]; } else { - this.pass = 0; + pass = 0; break; } } @@ -632,9 +644,9 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The de-filtered scanline /// The image - /// The png meta data - private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetaData pngMetaData) - where TPixel : struct, IPixel + /// The png metadata. + private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) + where TPixel : unmanaged, IPixel { Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); @@ -653,9 +665,9 @@ namespace SixLabors.ImageSharp.Formats.Png this.header, scanlineSpan, rowSpan, - pngMetaData.HasTrans, - pngMetaData.TransparentGray16.GetValueOrDefault(), - pngMetaData.TransparentGray8.GetValueOrDefault()); + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); break; @@ -681,21 +693,21 @@ namespace SixLabors.ImageSharp.Formats.Png case PngColorType.Rgb: PngScanlineProcessor.ProcessRgbScanline( - this.configuration, + this.Configuration, this.header, scanlineSpan, rowSpan, this.bytesPerPixel, this.bytesPerSample, - pngMetaData.HasTrans, - pngMetaData.TransparentRgb48.GetValueOrDefault(), - pngMetaData.TransparentRgb24.GetValueOrDefault()); + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); break; case PngColorType.RgbWithAlpha: PngScanlineProcessor.ProcessRgbaScanline( - this.configuration, + this.Configuration, this.header, scanlineSpan, rowSpan, @@ -714,11 +726,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The de-filtered scanline /// The current image row. - /// The png meta data + /// 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 + private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1) + where TPixel : unmanaged, IPixel { // Trim the first marker byte from the buffer ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); @@ -737,9 +749,9 @@ namespace SixLabors.ImageSharp.Formats.Png rowSpan, pixelOffset, increment, - pngMetaData.HasTrans, - pngMetaData.TransparentGray16.GetValueOrDefault(), - pngMetaData.TransparentGray8.GetValueOrDefault()); + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); break; @@ -776,9 +788,9 @@ namespace SixLabors.ImageSharp.Formats.Png increment, this.bytesPerPixel, this.bytesPerSample, - pngMetaData.HasTrans, - pngMetaData.TransparentRgb48.GetValueOrDefault(), - pngMetaData.TransparentRgb24.GetValueOrDefault()); + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); break; @@ -799,11 +811,11 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - /// Decodes and assigns marker colors that identify transparent pixels in non indexed images + /// Decodes and assigns marker colors that identify transparent pixels in non indexed images. /// - /// The alpha tRNS array - /// The png meta data - private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetaData pngMetaData) + /// The alpha tRNS array. + /// The png metadata. + private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetadata pngMetadata) { if (this.pngColorType == PngColorType.Rgb) { @@ -815,16 +827,16 @@ namespace SixLabors.ImageSharp.Formats.Png ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); - pngMetaData.TransparentRgb48 = new Rgb48(rc, gc, bc); - pngMetaData.HasTrans = true; + pngMetadata.TransparentRgb48 = new Rgb48(rc, gc, bc); + pngMetadata.HasTransparency = true; return; } 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; + pngMetadata.TransparentRgb24 = new Rgb24(r, g, b); + pngMetadata.HasTransparency = true; } } else if (this.pngColorType == PngColorType.Grayscale) @@ -833,14 +845,14 @@ namespace SixLabors.ImageSharp.Formats.Png { if (this.header.BitDepth == 16) { - pngMetaData.TransparentGray16 = new Gray16(BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2))); + pngMetadata.TransparentL16 = new L16(BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2))); } else { - pngMetaData.TransparentGray8 = new Gray8(ReadByteLittleEndian(alpha, 0)); + pngMetadata.TransparentL8 = new L8(ReadByteLittleEndian(alpha, 0)); } - pngMetaData.HasTrans = true; + pngMetadata.HasTransparency = true; } } } @@ -848,16 +860,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Reads a header chunk from the data. /// - /// The png metadata. + /// The png metadata. /// The containing data. - private void ReadHeaderChunk(PngMetaData pngMetaData, ReadOnlySpan data) + private void ReadHeaderChunk(PngMetadata pngMetadata, ReadOnlySpan data) { this.header = PngHeader.Parse(data); this.header.Validate(); - pngMetaData.BitDepth = (PngBitDepth)this.header.BitDepth; - pngMetaData.ColorType = this.header.ColorType; + pngMetadata.BitDepth = (PngBitDepth)this.header.BitDepth; + pngMetadata.ColorType = this.header.ColorType; + pngMetadata.InterlaceMethod = this.header.InterlaceMethod; this.pngColorType = this.header.ColorType; } @@ -867,7 +880,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The metadata to decode to. /// The containing the data. - private void ReadTextChunk(ImageMetaData metadata, ReadOnlySpan data) + private void ReadTextChunk(PngMetadata metadata, ReadOnlySpan data) { if (this.ignoreMetadata) { @@ -876,10 +889,167 @@ namespace SixLabors.ImageSharp.Formats.Png int zeroIndex = data.IndexOf((byte)0); - string name = this.textEncoding.GetString(data.Slice(0, zeroIndex)); - string value = this.textEncoding.GetString(data.Slice(zeroIndex + 1)); + // Keywords are restricted to 1 to 79 bytes in length. + if (zeroIndex < PngConstants.MinTextKeywordLength || zeroIndex > PngConstants.MaxTextKeywordLength) + { + return; + } + + ReadOnlySpan keywordBytes = data.Slice(0, zeroIndex); + if (!this.TryReadTextKeyword(keywordBytes, out string name)) + { + return; + } + + string value = PngConstants.Encoding.GetString(data.Slice(zeroIndex + 1)); - metadata.Properties.Add(new ImageProperty(name, value)); + metadata.TextData.Add(new PngTextData(name, value, string.Empty, string.Empty)); + } + + /// + /// Reads the compressed text chunk. Contains a uncompressed keyword and a compressed text string. + /// + /// The metadata to decode to. + /// The containing the data. + private void ReadCompressedTextChunk(PngMetadata metadata, ReadOnlySpan data) + { + if (this.ignoreMetadata) + { + return; + } + + int zeroIndex = data.IndexOf((byte)0); + if (zeroIndex < PngConstants.MinTextKeywordLength || zeroIndex > PngConstants.MaxTextKeywordLength) + { + return; + } + + byte compressionMethod = data[zeroIndex + 1]; + if (compressionMethod != 0) + { + // Only compression method 0 is supported (zlib datastream with deflate compression). + return; + } + + ReadOnlySpan keywordBytes = data.Slice(0, zeroIndex); + if (!this.TryReadTextKeyword(keywordBytes, out string name)) + { + return; + } + + ReadOnlySpan compressedData = data.Slice(zeroIndex + 2); + + if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed)) + { + metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); + } + } + + /// + /// 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(PngMetadata metadata, ReadOnlySpan data) + { + if (this.ignoreMetadata) + { + return; + } + + int zeroIndexKeyword = data.IndexOf((byte)0); + if (zeroIndexKeyword < PngConstants.MinTextKeywordLength || zeroIndexKeyword > PngConstants.MaxTextKeywordLength) + { + return; + } + + byte compressionFlag = data[zeroIndexKeyword + 1]; + if (!(compressionFlag == 0 || compressionFlag == 1)) + { + return; + } + + byte compressionMethod = data[zeroIndexKeyword + 2]; + if (compressionMethod != 0) + { + // Only compression method 0 is supported (zlib datastream with deflate compression). + return; + } + + int langStartIdx = zeroIndexKeyword + 3; + int languageLength = data.Slice(langStartIdx).IndexOf((byte)0); + if (languageLength < 0) + { + return; + } + + string language = PngConstants.LanguageEncoding.GetString(data.Slice(langStartIdx, languageLength)); + + int translatedKeywordStartIdx = langStartIdx + languageLength + 1; + int translatedKeywordLength = data.Slice(translatedKeywordStartIdx).IndexOf((byte)0); + string translatedKeyword = PngConstants.TranslatedEncoding.GetString(data.Slice(translatedKeywordStartIdx, translatedKeywordLength)); + + ReadOnlySpan keywordBytes = data.Slice(0, zeroIndexKeyword); + if (!this.TryReadTextKeyword(keywordBytes, out string keyword)) + { + return; + } + + int dataStartIdx = translatedKeywordStartIdx + translatedKeywordLength + 1; + if (compressionFlag == 1) + { + ReadOnlySpan compressedData = data.Slice(dataStartIdx); + + if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string uncompressed)) + { + metadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword)); + } + } + else + { + string value = PngConstants.TranslatedEncoding.GetString(data.Slice(dataStartIdx)); + metadata.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 TryUncompressTextData(ReadOnlySpan compressedData, Encoding encoding, out string value) + { + using (var memoryStream = new MemoryStream(compressedData.ToArray())) + using (var bufferedStream = new BufferedReadStream(this.Configuration, memoryStream)) + using (var inflateStream = new ZlibInflateStream(bufferedStream)) + { + if (!inflateStream.AllocateNewBytes(compressedData.Length, false)) + { + value = null; + return false; + } + + var uncompressedBytes = new List(); + + // Note: this uses a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here. + int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); + while (bytesRead != 0) + { + uncompressedBytes.AddRange(this.buffer.AsSpan().Slice(0, bytesRead).ToArray()); + bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); + } + + value = encoding.GetString(uncompressedBytes.ToArray()); + return true; + } } /// @@ -936,10 +1106,7 @@ namespace SixLabors.ImageSharp.Formats.Png while (length < 0 || length > (this.currentStream.Length - this.currentStream.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; - + // Not a valid chunk so try again until we reach a known chunk. if (!this.TryReadChunkLength(out length)) { chunk = default; @@ -961,13 +1128,9 @@ namespace SixLabors.ImageSharp.Formats.Png chunk = new PngChunk( length: length, type: type, - data: this.ReadChunkData(length), - crc: this.ReadChunkCrc()); + data: this.ReadChunkData(length)); - if (chunk.IsCritical) - { - this.ValidateChunk(chunk); - } + this.ValidateChunk(chunk); return true; } @@ -978,39 +1141,44 @@ namespace SixLabors.ImageSharp.Formats.Png /// The . private void ValidateChunk(in PngChunk chunk) { - Span chunkType = stackalloc byte[4]; - - BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); + uint inputCrc = this.ReadChunkCrc(); - this.crc.Reset(); - this.crc.Update(chunkType); - this.crc.Update(chunk.Data.GetSpan()); - - if (this.crc.Value != chunk.Crc) + if (chunk.IsCritical) { - string chunkTypeName = Encoding.ASCII.GetString(chunkType); + Span chunkType = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); + + uint validCrc = Crc32.Calculate(chunkType); + validCrc = Crc32.Calculate(validCrc, chunk.Data.GetSpan()); - throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); + if (validCrc != inputCrc) + { + string chunkTypeName = Encoding.ASCII.GetString(chunkType); + PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); + } } } /// /// Reads the cycle redundancy chunk from the data. /// - /// - /// Thrown if the input stream is not valid or corrupt. - /// + [MethodImpl(InliningOptions.ShortMethod)] private uint ReadChunkCrc() { - return this.currentStream.Read(this.buffer, 0, 4) == 4 - ? BinaryPrimitives.ReadUInt32BigEndian(this.buffer) - : throw new ImageFormatException("Image stream is not valid!"); + uint crc = 0; + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + } + + 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); @@ -1021,10 +1189,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// Reads the chunk data from the stream. /// /// The length of the chunk data to read. + [MethodImpl(InliningOptions.ShortMethod)] 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); + IManagedByteBuffer buffer = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); this.currentStream.Read(buffer.Array, 0, length); @@ -1037,19 +1206,29 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Thrown if the input stream is not valid. /// + [MethodImpl(InliningOptions.ShortMethod)] private PngChunkType ReadChunkType() { - return this.currentStream.Read(this.buffer, 0, 4) == 4 - ? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer) - : throw new ImageFormatException("Invalid PNG data."); + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + } + else + { + PngThrowHelper.ThrowInvalidChunkType(); + + // The IDE cannot detect the throw here. + return default; + } } /// /// Attempts to read the length of the next chunk. /// /// - /// Whether the the length was read. + /// Whether the length was read. /// + [MethodImpl(InliningOptions.ShortMethod)] private bool TryReadChunkLength(out int result) { if (this.currentStream.Read(this.buffer, 0, 4) == 4) @@ -1060,10 +1239,40 @@ namespace SixLabors.ImageSharp.Formats.Png } result = default; - return false; } + /// + /// 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 bool TryReadTextKeyword(ReadOnlySpan keywordBytes, out string name) + { + name = string.Empty; + + // Keywords shall contain only printable Latin-1. + foreach (byte c in keywordBytes) + { + if (!((c >= 32 && c <= 126) || (c >= 161 && c <= 255))) + { + return false; + } + } + + // Keywords should not be empty or have leading or trailing whitespace. + name = PngConstants.Encoding.GetString(keywordBytes); + if (string.IsNullOrWhiteSpace(name) || name.StartsWith(" ") || name.EndsWith(" ")) + { + return false; + } + + return true; + } + private void SwapBuffers() { IManagedByteBuffer temp = this.previousScanline; @@ -1071,4 +1280,4 @@ namespace SixLabors.ImageSharp.Formats.Png this.scanline = temp; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 96e97a305f..e72e8d3d55 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -1,7 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -13,43 +15,41 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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 or sets the color type. - /// + /// public PngColorType? ColorType { get; set; } - /// - /// Gets or sets the filter method. - /// + /// public PngFilterMethod? FilterMethod { get; set; } - /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. - /// - public int CompressionLevel { get; set; } = 6; + /// + public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression; - /// - /// Gets or sets the gamma value, that will be written the the image. - /// + /// + public int TextCompressionThreshold { get; set; } = 1024; + + /// public float? Gamma { get; set; } - /// - /// Gets or sets quantizer for reducing the color count. - /// Defaults to the - /// + /// public IQuantizer Quantizer { get; set; } - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 255; + /// + public byte Threshold { get; set; } = byte.MaxValue; + + /// + public PngInterlaceMode? InterlaceMethod { get; set; } + + /// + public PngChunkFilter? ChunkFilter { get; set; } + + /// + public bool IgnoreMetadata { get; set; } + + /// + public PngTransparentColorMode TransparentColorMode { get; set; } /// /// Encodes the image to the specified stream from the . @@ -58,12 +58,32 @@ namespace SixLabors.ImageSharp.Formats.Png /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), this)) + using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) { encoder.Encode(image, stream); } } + + /// + /// 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 monitor for cancellation requests. + /// A representing the asynchronous operation. + public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + // The introduction of a local variable that refers to an object the implements + // IDisposable means you must use async/await, where the compiler generates the + // state machine and a continuation. + using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) + { + await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 216f3129f9..5cf11099cd 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -1,42 +1,33 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; 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.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 + internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { /// - /// The dictionary of available color types. + /// The maximum block size, defaults at 64k for uncompressed blocks. /// - private 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 } - }; + private const int MaxBlockSize = 65535; /// /// Used the manage memory allocations. @@ -44,14 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Png private readonly MemoryAllocator memoryAllocator; /// - /// The configuration instance for the decoding operation - /// - private Configuration configuration; - - /// - /// The maximum block size, defaults at 64k for uncompressed blocks. + /// The configuration instance for the decoding operation. /// - private const int MaxBlockSize = 65535; + private readonly Configuration configuration; /// /// Reusable buffer for writing general data. @@ -64,39 +50,14 @@ namespace SixLabors.ImageSharp.Formats.Png 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 + /// The encoder options /// - private bool writeGamma; + private readonly PngEncoderOptions options; /// - /// The png bit depth + /// The bit depth. /// - private PngBitDepth? pngBitDepth; + private byte bitDepth; /// /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types. @@ -104,14 +65,9 @@ namespace SixLabors.ImageSharp.Formats.Png private bool use16Bit; /// - /// The png color type. - /// - private PngColorType? pngColorType; - - /// - /// Gets or sets the Gamma value + /// The number of bytes per pixel. /// - private float? gamma; + private int bytesPerPixel; /// /// The image width. @@ -124,74 +80,46 @@ namespace SixLabors.ImageSharp.Formats.Png 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. + /// The raw data of previous scanline. /// private IManagedByteBuffer previousScanline; /// - /// The raw scanline. + /// The raw data of current scanline. /// - private IManagedByteBuffer rawScanline; + private IManagedByteBuffer currentScanline; /// - /// The filtered scanline result. + /// The common buffer for the filters. /// - private IManagedByteBuffer result; + private IManagedByteBuffer filterBuffer; /// - /// The buffer for the sub filter + /// The ext buffer for the sub filter, . /// - private IManagedByteBuffer sub; + private IManagedByteBuffer subFilter; /// - /// The buffer for the up filter + /// The ext buffer for the average filter, . /// - private IManagedByteBuffer up; + private IManagedByteBuffer averageFilter; /// - /// The buffer for the average filter + /// The ext buffer for the Paeth filter, . /// - private IManagedByteBuffer average; + private IManagedByteBuffer paethFilter; /// - /// The buffer for the Paeth filter + /// Initializes a new instance of the class. /// - private IManagedByteBuffer paeth; - - /// - /// Initializes a new instance of the class. - /// - /// The to use for buffer allocations. + /// The to use for buffer allocations. + /// The configuration. /// The options for influencing the encoder - public PngEncoderCore(MemoryAllocator memoryAllocator, IPngEncoderOptions options) + public PngEncoderCore(MemoryAllocator memoryAllocator, Configuration configuration, PngEncoderOptions 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.Equals(PngColorType.Palette) - ? PngFilterMethod.None - : PngFilterMethod.Paeth); - this.compressionLevel = options.CompressionLevel; - this.gamma = options.Gamma; - this.quantizer = options.Quantizer; - this.threshold = options.Threshold; + this.configuration = configuration; + this.options = options; } /// @@ -200,150 +128,140 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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 + /// 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.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.Equals(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) - { - 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. - quantized = this.quantizer.CreateFrameQuantizer(image.GetConfiguration()) - .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; - } + ImageMetadata metadata = image.Metadata; - this.bitDepth = bits; - } - else + PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); + Image clonedImage = null; + bool clearTransparency = this.options.TransparentColorMode == PngTransparentColorMode.Clear; + if (clearTransparency) { - this.bitDepth = (byte)this.pngBitDepth; - if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth)) - { - throw new NotSupportedException("Bit depth is not supported or not valid."); - } + clonedImage = image.Clone(); + ClearTransparentPixels(clonedImage); } - this.bytesPerPixel = this.CalculateBytesPerPixel(); + IndexedImageFrame quantized = this.CreateQuantizedImage(image, clonedImage); - 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); + stream.Write(PngConstants.HeaderBytes); - // Collect the indexed pixel data - if (quantized != null) - { - this.WritePaletteChunk(stream, quantized); - } - - if (pngMetaData.HasTrans) - { - this.WriteTransparencyChunk(stream, pngMetaData); - } - - this.WritePhysicalChunk(stream, metaData); + this.WriteHeaderChunk(stream); this.WriteGammaChunk(stream); - this.WriteExifChunk(stream, metaData); - this.WriteDataChunks(image.Frames.RootFrame, quantized, stream); + this.WritePaletteChunk(stream, quantized); + this.WriteTransparencyChunk(stream, pngMetadata); + this.WritePhysicalChunk(stream, metadata); + this.WriteExifChunk(stream, metadata); + this.WriteTextChunks(stream, pngMetadata); + this.WriteDataChunks(clearTransparency ? clonedImage : image, quantized, stream); this.WriteEndChunk(stream); + stream.Flush(); quantized?.Dispose(); + clonedImage?.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(); + this.currentScanline?.Dispose(); + this.subFilter?.Dispose(); + this.averageFilter?.Dispose(); + this.paethFilter?.Dispose(); + this.filterBuffer?.Dispose(); + + this.previousScanline = null; + this.currentScanline = null; + this.subFilter = null; + this.averageFilter = null; + this.paethFilter = null; + this.filterBuffer = null; } /// - /// Collects a row of grayscale pixels. + /// Convert transparent pixels, to transparent black pixels, which can yield to better compression in some cases. /// + /// The type of the pixel. + /// The cloned image where the transparent pixels will be changed. + private static void ClearTransparentPixels(Image image) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba32 = default; + for (int y = 0; y < image.Height; y++) + { + Span span = image.GetPixelRowSpan(y); + for (int x = 0; x < image.Width; x++) + { + span[x].ToRgba32(ref rgba32); + + if (rgba32.A == 0) + { + span[x].FromRgba32(Color.Transparent); + } + } + } + } + + /// + /// Creates the quantized image and sets calculates and sets the bit depth. + /// + /// The type of the pixel. + /// The image to quantize. + /// Cloned image with transparent pixels are changed to black. + /// The quantized image. + private IndexedImageFrame CreateQuantizedImage(Image image, Image clonedImage) + where TPixel : unmanaged, IPixel + { + IndexedImageFrame quantized; + if (this.options.TransparentColorMode == PngTransparentColorMode.Clear) + { + quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, clonedImage); + this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized); + } + else + { + quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); + this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized); + } + + return quantized; + } + + /// Collects a row of grayscale pixels. /// The pixel format. /// The image row span. private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - Span rawScanlineSpan = this.rawScanline.GetSpan(); + Span rawScanlineSpan = this.currentScanline.GetSpan(); ref byte rawScanlineSpanRef = ref MemoryMarshal.GetReference(rawScanlineSpan); - if (this.pngColorType.Equals(PngColorType.Grayscale)) + if (this.options.ColorType == PngColorType.Grayscale) { if (this.use16Bit) { // 16 bit grayscale - using (IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + 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); + 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. + // 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); + L16 luminance = Unsafe.Add(ref luminanceRef, x); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance.PackedValue); } } @@ -353,7 +271,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.bitDepth == 8) { // 8 bit grayscale - PixelOperations.Instance.ToGray8Bytes( + PixelOperations.Instance.ToL8Bytes( this.configuration, rowSpan, rawScanlineSpan, @@ -370,12 +288,12 @@ namespace SixLabors.ImageSharp.Formats.Png 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( + PixelOperations.Instance.ToL8Bytes( this.configuration, rowSpan, tempSpan, rowSpan.Length); - this.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); + PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); } } } @@ -424,78 +342,78 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The row span. private void CollectTPixelBytes(ReadOnlySpan rowSpan) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Span rawScanlineSpan = this.rawScanline.GetSpan(); + Span rawScanlineSpan = this.currentScanline.GetSpan(); switch (this.bytesPerPixel) { case 4: - { - // 8 bit Rgba - PixelOperations.Instance.ToRgba32Bytes( - this.configuration, - rowSpan, - rawScanlineSpan, - this.width); - break; - } + { + // 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, - this.width); - break; - } + { + // 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)) { - // 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) { - 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); - } + 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; } + break; + } + default: + { + // 16 bit Rgb + using (IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) { - // 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) { - 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); - } + 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; } + + break; + } } } @@ -507,22 +425,20 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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 + private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + where TPixel : unmanaged, IPixel { - switch (this.pngColorType) + switch (this.options.ColorType) { case PngColorType.Palette: if (this.bitDepth < 8) { - this.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.rawScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); } else { - int stride = this.rawScanline.Length(); - quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan()); + quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan()); } break; @@ -534,34 +450,75 @@ namespace SixLabors.ImageSharp.Formats.Png this.CollectTPixelBytes(rowSpan); break; } + } - switch (this.pngFilterMethod) + /// + /// Apply filter for the raw scanline. + /// + private IManagedByteBuffer FilterPixelBytes() + { + switch (this.options.FilterMethod) { case PngFilterMethod.None: - NoneFilter.Encode(this.rawScanline.GetSpan(), this.result.GetSpan()); - return this.result; + NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); + return this.filterBuffer; case PngFilterMethod.Sub: - SubFilter.Encode(this.rawScanline.GetSpan(), this.sub.GetSpan(), this.bytesPerPixel, out int _); - return this.sub; + SubFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); + return this.filterBuffer; case PngFilterMethod.Up: - UpFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.up.GetSpan(), out int _); - return this.up; + UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), out int _); + return this.filterBuffer; case PngFilterMethod.Average: - AverageFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.average.GetSpan(), this.bytesPerPixel, out int _); - return this.average; + AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); + return this.filterBuffer; case PngFilterMethod.Paeth: - PaethFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.paeth.GetSpan(), this.bytesPerPixel, out int _); - return this.paeth; + PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); + return this.filterBuffer; default: return this.GetOptimalFilteredScanline(); } } + /// + /// 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, IndexedImageFrame quantized, int row) + where TPixel : unmanaged, IPixel + { + this.CollectPixelBytes(rowSpan, quantized, row); + return this.FilterPixelBytes(); + } + + /// + /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. + /// + /// The row span. + private IManagedByteBuffer EncodeAdam7IndexedPixelRow(ReadOnlySpan rowSpan) + { + // CollectPixelBytes + if (this.bitDepth < 8) + { + PngEncoderHelpers.ScaleDownFrom8BitArray(rowSpan, this.currentScanline.GetSpan(), this.bitDepth); + } + else + { + rowSpan.CopyTo(this.currentScanline.GetSpan()); + } + + return this.FilterPixelBytes(); + } + /// /// 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. @@ -570,84 +527,67 @@ namespace SixLabors.ImageSharp.Formats.Png private IManagedByteBuffer GetOptimalFilteredScanline() { // Palette images don't compress well with adaptive filtering. - if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) + if (this.options.ColorType == PngColorType.Palette || this.bitDepth < 8) { - NoneFilter.Encode(this.rawScanline.GetSpan(), this.result.GetSpan()); - return this.result; + NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); + return this.filterBuffer; } - Span scanSpan = this.rawScanline.GetSpan(); + this.AllocateExtBuffers(); + Span scanSpan = this.currentScanline.GetSpan(); Span prevSpan = this.previousScanline.GetSpan(); // 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); + UpFilter.Encode(scanSpan, prevSpan, this.filterBuffer.GetSpan(), out int currentSum); // 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; + IManagedByteBuffer actualResult = this.filterBuffer; - PaethFilter.Encode(scanSpan, prevSpan, this.paeth.GetSpan(), this.bytesPerPixel, out currentSum); + PaethFilter.Encode(scanSpan, prevSpan, this.paethFilter.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; - actualResult = this.paeth; + actualResult = this.paethFilter; } - SubFilter.Encode(scanSpan, this.sub.GetSpan(), this.bytesPerPixel, out currentSum); + SubFilter.Encode(scanSpan, this.subFilter.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; - actualResult = this.sub; + actualResult = this.subFilter; } - AverageFilter.Encode(scanSpan, prevSpan, this.average.GetSpan(), this.bytesPerPixel, out currentSum); + AverageFilter.Encode(scanSpan, prevSpan, this.averageFilter.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { - actualResult = this.average; + actualResult = this.averageFilter; } return actualResult; } - /// - /// 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; - - case PngColorType.GrayscaleWithAlpha: - return this.use16Bit ? 4 : 2; - - case PngColorType.Palette: - return 1; - - case PngColorType.Rgb: - return this.use16Bit ? 6 : 3; - - // PngColorType.RgbWithAlpha - default: - return this.use16Bit ? 8 : 4; - } - } - /// /// Writes the header chunk to the stream. /// /// The containing image data. - /// The . - private void WriteHeaderChunk(Stream stream, in PngHeader header) + private void WriteHeaderChunk(Stream stream) { + var header = new PngHeader( + width: this.width, + height: this.height, + bitDepth: this.bitDepth, + colorType: this.options.ColorType.Value, + compressionMethod: 0, // None + filterMethod: 0, + interlaceMethod: this.options.InterlaceMethod.Value); + header.WriteTo(this.chunkDataBuffer); this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size); @@ -655,97 +595,215 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// 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, QuantizedFrame quantized) - where TPixel : struct, IPixel + private void WritePaletteChunk(Stream stream, IndexedImageFrame quantized) + where TPixel : unmanaged, 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; - - using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength)) + if (quantized is null) { - ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); - ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - Span quantizedSpan = quantized.GetPixelSpan(); + return; + } - Rgba32 rgba = default; + // 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; - for (int i = 0; i < paletteLength; i++) - { - if (quantizedSpan.IndexOf((byte)i) > -1) - { - int offset = i * 3; - palette[i].ToRgba32(ref rgba); + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); + using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); - byte alpha = rgba.A; + ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); + ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - Unsafe.Add(ref colorTableRef, offset) = rgba.R; - Unsafe.Add(ref colorTableRef, offset + 1) = rgba.G; - Unsafe.Add(ref colorTableRef, offset + 2) = rgba.B; + // 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); - if (alpha > this.threshold) - { - alpha = byte.MaxValue; - } + // Loop, assign, and extract alpha values from the palette. + for (int i = 0; i < paletteLength; i++) + { + Rgba32 rgba = Unsafe.Add(ref rgbaPaletteRef, i); + byte alpha = rgba.A; - anyAlpha = anyAlpha || alpha < byte.MaxValue; - Unsafe.Add(ref alphaTableRef, i) = alpha; - } + Unsafe.Add(ref colorTableRef, i) = rgba.Rgb; + if (alpha > this.options.Threshold) + { + alpha = byte.MaxValue; } - this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + hasAlpha = hasAlpha || alpha < byte.MaxValue; + Unsafe.Add(ref alphaTableRef, i) = alpha; + } + + this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); - // Write the transparency data - if (anyAlpha) - { - this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); - } + // Write the transparency data + if (hasAlpha) + { + this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); } } /// /// Writes the physical dimension information to the stream. + /// Should be written before IDAT chunk. /// /// The containing image data. - /// The image meta data. - private void WritePhysicalChunk(Stream stream, ImageMetaData meta) + /// The image metadata. + private void WritePhysicalChunk(Stream stream, ImageMetadata meta) { + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludePhysicalChunk) == PngChunkFilter.ExcludePhysicalChunk) + { + return; + } + PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer); this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size); } /// - /// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the meta data. + /// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the metadata. /// /// The containing image data. - /// The image meta data. - private void WriteExifChunk(Stream stream, ImageMetaData meta) + /// The image metadata. + private void WriteExifChunk(Stream stream, ImageMetadata meta) { - if (meta.ExifProfile?.Values.Count > 0) + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeExifChunk) == PngChunkFilter.ExcludeExifChunk) + { + return; + } + + if (meta.ExifProfile is null || meta.ExifProfile.Values.Count == 0) { - meta.SyncProfiles(); - this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); + return; + } + + meta.SyncProfiles(); + this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); + } + + /// + /// 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.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) + { + return; + } + + const int MaxLatinCode = 255; + for (int i = 0; i < meta.TextData.Count; i++) + { + PngTextData textData = meta.TextData[i]; + bool hasUnicodeCharacters = false; + foreach (var c in textData.Value) + { + if (c > MaxLatinCode) + { + hasUnicodeCharacters = true; + break; + } + } + + 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.options.TextCompressionThreshold + ? this.GetCompressedTextBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value)) + : PngConstants.TranslatedEncoding.GetBytes(textData.Value); + + byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword); + byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag); + + Span outputBytes = new byte[keywordBytes.Length + textBytes.Length + + translatedKeyword.Length + languageTag.Length + 5]; + keywordBytes.CopyTo(outputBytes); + if (textData.Value.Length > this.options.TextCompressionThreshold) + { + // Indicate that the text is compressed. + outputBytes[keywordBytes.Length + 1] = 1; + } + + int keywordStart = keywordBytes.Length + 3; + languageTag.CopyTo(outputBytes.Slice(keywordStart)); + int translatedKeywordStart = keywordStart + languageTag.Length + 1; + translatedKeyword.CopyTo(outputBytes.Slice(translatedKeywordStart)); + textBytes.CopyTo(outputBytes.Slice(translatedKeywordStart + translatedKeyword.Length + 1)); + this.WriteChunk(stream, PngChunkType.InternationalText, outputBytes.ToArray()); + } + else + { + if (textData.Value.Length > this.options.TextCompressionThreshold) + { + // Write zTXt chunk. + byte[] compressedData = + this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value)); + Span outputBytes = new byte[textData.Keyword.Length + compressedData.Length + 2]; + PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); + compressedData.CopyTo(outputBytes.Slice(textData.Keyword.Length + 2)); + this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes.ToArray()); + } + else + { + // Write tEXt chunk. + Span outputBytes = new byte[textData.Keyword.Length + textData.Value.Length + 1]; + PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); + PngConstants.Encoding.GetBytes(textData.Value) + .CopyTo(outputBytes.Slice(textData.Keyword.Length + 1)); + this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray()); + } + } + } + } + + /// + /// Compresses a given text using Zlib compression. + /// + /// The text bytes to compress. + /// The compressed text byte array. + private byte[] GetCompressedTextBytes(byte[] textBytes) + { + using (var memoryStream = new MemoryStream()) + { + using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) + { + deflateStream.Write(textBytes); + } + + 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.writeGamma) + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeGammaChunk) == PngChunkFilter.ExcludeGammaChunk) + { + return; + } + + if (this.options.Gamma > 0) { // 4-byte unsigned integer of gamma * 100,000. - uint gammaValue = (uint)(this.gamma * 100_000F); + uint gammaValue = (uint)(this.options.Gamma * 100_000F); BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue); @@ -754,45 +812,51 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - /// Writes the transparency chunk to the stream + /// Writes the transparency chunk to the stream. + /// Should be written after PLTE and before IDAT. /// /// The containing image data. - /// The image meta data. - private void WriteTransparencyChunk(Stream stream, PngMetaData pngMetaData) + /// The image metadata. + private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata) { + if (!pngMetadata.HasTransparency) + { + return; + } + Span alpha = this.chunkDataBuffer.AsSpan(); - if (pngMetaData.ColorType.Equals(PngColorType.Rgb)) + if (pngMetadata.ColorType == PngColorType.Rgb) { - if (pngMetaData.TransparentRgb48.HasValue && this.use16Bit) + if (pngMetadata.TransparentRgb48.HasValue && this.use16Bit) { - Rgb48 rgb = pngMetaData.TransparentRgb48.Value; + 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) + else if (pngMetadata.TransparentRgb24.HasValue) { alpha.Clear(); - Rgb24 rgb = pngMetaData.TransparentRgb24.Value; + 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.Equals(PngColorType.Grayscale)) + else if (pngMetadata.ColorType == PngColorType.Grayscale) { - if (pngMetaData.TransparentGray16.HasValue && this.use16Bit) + if (pngMetadata.TransparentL16.HasValue && this.use16Bit) { - BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetaData.TransparentGray16.Value.PackedValue); + BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentL16.Value.PackedValue); this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); } - else if (pngMetaData.TransparentGray8.HasValue) + else if (pngMetadata.TransparentL8.HasValue) { alpha.Clear(); - alpha[1] = pngMetaData.TransparentGray8.Value.PackedValue; + alpha[1] = pngMetadata.TransparentL8.Value.PackedValue; this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); } } @@ -805,60 +869,30 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image. /// The quantized pixel data. Can be null. /// The stream. - private void WriteDataChunks(ImageFrame pixels, QuantizedFrame quantized, Stream stream) - where TPixel : struct, IPixel + private void WriteDataChunks(Image pixels, IndexedImageFrame quantized, Stream stream) + where TPixel : unmanaged, IPixel { - 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) - { - 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; - } - byte[] buffer; int bufferLength; using (var memoryStream = new MemoryStream()) { - using (var deflateStream = new ZlibDeflateStream(memoryStream, this.compressionLevel)) + using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) { - for (int y = 0; y < this.height; y++) + if (this.options.InterlaceMethod == PngInterlaceMode.Adam7) { - IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)pixels.GetPixelRowSpan(y), quantized, y); - deflateStream.Write(r.Array, 0, resultLength); - - IManagedByteBuffer temp = this.rawScanline; - this.rawScanline = this.previousScanline; - this.previousScanline = temp; + if (quantized != null) + { + this.EncodeAdam7IndexedPixels(quantized, deflateStream); + } + else + { + this.EncodeAdam7Pixels(pixels, deflateStream); + } + } + else + { + this.EncodePixels(pixels, quantized, deflateStream); } } @@ -888,6 +922,177 @@ namespace SixLabors.ImageSharp.Formats.Png } } + /// + /// Allocates the buffers for each scanline. + /// + /// The bytes per scanline. + /// Length of the result. + private void AllocateBuffers(int bytesPerScanline, int resultLength) + { + // Clean up from any potential previous runs. + this.subFilter?.Dispose(); + this.averageFilter?.Dispose(); + this.paethFilter?.Dispose(); + this.subFilter = null; + this.averageFilter = null; + this.paethFilter = null; + + this.previousScanline?.Dispose(); + this.currentScanline?.Dispose(); + this.filterBuffer?.Dispose(); + this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); + this.currentScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); + this.filterBuffer = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + } + + /// + /// Allocates the ext buffers for adaptive filter. + /// + private void AllocateExtBuffers() + { + if (this.subFilter == null) + { + int resultLength = this.filterBuffer.Length(); + + this.subFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.averageFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.paethFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + } + } + + /// + /// Encodes the pixels. + /// + /// The type of the pixel. + /// The pixels. + /// The quantized pixels span. + /// The deflate stream. + private void EncodePixels(Image pixels, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel + { + int bytesPerScanline = this.CalculateScanlineLength(this.width); + int resultLength = bytesPerScanline + 1; + this.AllocateBuffers(bytesPerScanline, resultLength); + + for (int y = 0; y < this.height; y++) + { + IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); + deflateStream.Write(r.Array, 0, resultLength); + + IManagedByteBuffer temp = this.currentScanline; + this.currentScanline = this.previousScanline; + this.previousScanline = temp; + } + } + + /// + /// Interlaced encoding the pixels. + /// + /// The type of the pixel. + /// The pixels. + /// The deflate stream. + private void EncodeAdam7Pixels(Image pixels, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + for (int pass = 0; pass < 7; pass++) + { + int startRow = Adam7.FirstRow[pass]; + int startCol = Adam7.FirstColumn[pass]; + int blockWidth = Adam7.ComputeBlockWidth(width, pass); + + int bytesPerScanline = this.bytesPerPixel <= 1 + ? ((blockWidth * this.bitDepth) + 7) / 8 + : blockWidth * this.bytesPerPixel; + + int resultLength = bytesPerScanline + 1; + + this.AllocateBuffers(bytesPerScanline, resultLength); + + using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + { + Span destSpan = passData.Memory.Span; + for (int row = startRow; + row < height; + row += Adam7.RowIncrement[pass]) + { + // collect data + Span srcRow = pixels.GetPixelRowSpan(row); + for (int col = startCol, i = 0; + col < width; + col += Adam7.ColumnIncrement[pass]) + { + destSpan[i++] = srcRow[col]; + } + + // encode data + // note: quantized parameter not used + // note: row parameter not used + IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)destSpan, null, -1); + deflateStream.Write(r.Array, 0, resultLength); + + IManagedByteBuffer temp = this.currentScanline; + this.currentScanline = this.previousScanline; + this.previousScanline = temp; + } + } + } + } + + /// + /// 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 + { + int width = quantized.Width; + int height = quantized.Height; + for (int pass = 0; pass < 7; pass++) + { + int startRow = Adam7.FirstRow[pass]; + int startCol = Adam7.FirstColumn[pass]; + int blockWidth = Adam7.ComputeBlockWidth(width, pass); + + int bytesPerScanline = this.bytesPerPixel <= 1 + ? ((blockWidth * this.bitDepth) + 7) / 8 + : blockWidth * this.bytesPerPixel; + + int resultLength = bytesPerScanline + 1; + + this.AllocateBuffers(bytesPerScanline, resultLength); + + using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + { + Span destSpan = passData.Memory.Span; + for (int row = startRow; + row < height; + row += Adam7.RowIncrement[pass]) + { + // collect data + ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); + for (int col = startCol, i = 0; + col < width; + col += Adam7.ColumnIncrement[pass]) + { + destSpan[i++] = srcRow[col]; + } + + // encode data + IManagedByteBuffer r = this.EncodeAdam7IndexedPixelRow(destSpan); + deflateStream.Write(r.Array, 0, resultLength); + + IManagedByteBuffer temp = this.currentScanline; + this.currentScanline = this.previousScanline; + this.previousScanline = temp; + } + } + } + } + /// /// Writes the chunk end to the stream. /// @@ -917,64 +1122,20 @@ namespace SixLabors.ImageSharp.Formats.Png stream.Write(this.buffer, 0, 8); - this.crc.Reset(); - - this.crc.Update(this.buffer.AsSpan(4, 4)); // Write the type buffer + uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer if (data != null && length > 0) { stream.Write(data, offset, length); - this.crc.Update(data.AsSpan(offset, length)); + crc = Crc32.Calculate(crc, data.AsSpan(offset, length)); } - BinaryPrimitives.WriteUInt32BigEndian(this.buffer, (uint)this.crc.Value); + BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc); stream.Write(this.buffer, 0, 4); // write the crc } - /// - /// 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) - { - 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, i) / scale)) & mask; - v |= value << shift; - - if (shift == 0) - { - shift = shift0; - Unsafe.Add(ref resultRef, resultOffset) = (byte)v; - resultOffset++; - v = 0; - } - else - { - shift -= bits; - } - } - - if (shift != shift0) - { - Unsafe.Add(ref resultRef, resultOffset) = (byte)v; - } - } - /// /// Calculates the scanline length. /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs new file mode 100644 index 0000000000..5717ff0d63 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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, i) / scale)) & mask; + v |= value << shift; + + if (shift == 0) + { + shift = shift0; + Unsafe.Add(ref resultRef, resultOffset) = (byte)v; + resultOffset++; + v = 0; + } + else + { + shift -= bits; + } + } + + if (shift != shift0) + { + Unsafe.Add(ref resultRef, resultOffset) = (byte)v; + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs new file mode 100644 index 0000000000..3c17c24636 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// The options structure for the . + /// + internal class PngEncoderOptions : IPngEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + /// The source. + public PngEncoderOptions(IPngEncoderOptions source) + { + this.BitDepth = source.BitDepth; + this.ColorType = source.ColorType; + + // Specification recommends default filter method None for paletted images and Paeth for others. + this.FilterMethod = source.FilterMethod ?? (source.ColorType == PngColorType.Palette + ? PngFilterMethod.None + : PngFilterMethod.Paeth); + this.CompressionLevel = source.CompressionLevel; + this.TextCompressionThreshold = source.TextCompressionThreshold; + this.Gamma = source.Gamma; + this.Quantizer = source.Quantizer; + this.Threshold = source.Threshold; + this.InterlaceMethod = source.InterlaceMethod; + this.ChunkFilter = source.ChunkFilter; + this.IgnoreMetadata = source.IgnoreMetadata; + this.TransparentColorMode = source.TransparentColorMode; + } + + /// + public PngBitDepth? BitDepth { get; set; } + + /// + public PngColorType? ColorType { get; set; } + + /// + public PngFilterMethod? FilterMethod { get; } + + /// + public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression; + + /// + public int TextCompressionThreshold { get; } + + /// + public float? Gamma { get; set; } + + /// + public IQuantizer Quantizer { get; set; } + + /// + public byte Threshold { get; } + + /// + public PngInterlaceMode? InterlaceMethod { get; set; } + + /// + public PngChunkFilter? ChunkFilter { get; set; } + + /// + public bool IgnoreMetadata { get; set; } + + /// + public PngTransparentColorMode TransparentColorMode { get; set; } + } +} diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs new file mode 100644 index 0000000000..b0311f0887 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -0,0 +1,206 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// The helper methods for the PNG encoder options. + /// + internal static class PngEncoderOptionsHelpers + { + /// + /// Adjusts the options based upon the given metadata. + /// + /// The options. + /// The PNG metadata. + /// if set to true [use16 bit]. + /// The bytes per pixel. + public static void AdjustOptions( + PngEncoderOptions options, + PngMetadata pngMetadata, + out bool use16Bit, + out int bytesPerPixel) + where TPixel : unmanaged, IPixel + { + // Always take the encoder options over the metadata values. + options.Gamma ??= pngMetadata.Gamma; + + // Use options, then check metadata, if nothing set there then we suggest + // a sensible default based upon the pixel format. + options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType(); + options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth(); + + options.InterlaceMethod ??= pngMetadata.InterlaceMethod; + + use16Bit = options.BitDepth == PngBitDepth.Bit16; + bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit); + + if (options.IgnoreMetadata) + { + options.ChunkFilter = PngChunkFilter.ExcludeAll; + } + + // Ensure we are not allowing impossible combinations. + if (!PngConstants.ColorTypes.ContainsKey(options.ColorType.Value)) + { + throw new NotSupportedException("Color type is not supported or not valid."); + } + } + + /// + /// Creates the quantized frame. + /// + /// The type of the pixel. + /// The options. + /// The image. + public static IndexedImageFrame CreateQuantizedFrame( + PngEncoderOptions options, + Image image) + where TPixel : unmanaged, IPixel + { + if (options.ColorType != PngColorType.Palette) + { + return null; + } + + byte bits = (byte)options.BitDepth; + if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bits) == -1) + { + 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 (options.Quantizer is null) + { + var maxColors = ImageMaths.GetColorCountForBitDepth(bits); + options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); + } + + // Create quantized frame returning the palette and set the bit depth. + using (IQuantizer frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer(image.GetConfiguration())) + { + ImageFrame frame = image.Frames.RootFrame; + return frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + } + } + + /// + /// Calculates the bit depth value. + /// + /// The type of the pixel. + /// The options. + /// The quantized frame. + public static byte CalculateBitDepth( + PngEncoderOptions options, + IndexedImageFrame quantizedFrame) + where TPixel : unmanaged, IPixel + { + byte bitDepth; + if (options.ColorType == PngColorType.Palette) + { + byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantizedFrame.Palette.Length).Clamp(1, 8); + byte bits = Math.Max((byte)options.BitDepth, 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; + } + + bitDepth = bits; + } + else + { + bitDepth = (byte)options.BitDepth; + } + + if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bitDepth) == -1) + { + throw new NotSupportedException("Bit depth is not supported or not valid."); + } + + return bitDepth; + } + + /// + /// Calculates the correct number of bytes per pixel for the given color type. + /// + /// Bytes per pixel. + private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit) + { + return pngColorType switch + { + PngColorType.Grayscale => use16Bit ? 2 : 1, + PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2, + PngColorType.Palette => 1, + PngColorType.Rgb => use16Bit ? 6 : 3, + + // PngColorType.RgbWithAlpha + _ => use16Bit ? 8 : 4, + }; + } + + /// + /// Returns a suggested for the given + /// This is not exhaustive but covers many common pixel formats. + /// + private static PngColorType SuggestColorType() + where TPixel : unmanaged, IPixel + { + return typeof(TPixel) switch + { + Type t when t == typeof(A8) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(Argb32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(Bgr24) => PngColorType.Rgb, + Type t when t == typeof(Bgra32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(L8) => PngColorType.Grayscale, + Type t when t == typeof(L16) => PngColorType.Grayscale, + Type t when t == typeof(La16) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(La32) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(Rgb24) => PngColorType.Rgb, + Type t when t == typeof(Rgba32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(Rgb48) => PngColorType.Rgb, + Type t when t == typeof(Rgba64) => PngColorType.RgbWithAlpha, + Type t when t == typeof(RgbaVector) => PngColorType.RgbWithAlpha, + _ => PngColorType.RgbWithAlpha + }; + } + + /// + /// Returns a suggested for the given + /// This is not exhaustive but covers many common pixel formats. + /// + private static PngBitDepth SuggestBitDepth() + where TPixel : unmanaged, IPixel + { + return typeof(TPixel) switch + { + Type t when t == typeof(A8) => PngBitDepth.Bit8, + Type t when t == typeof(Argb32) => PngBitDepth.Bit8, + Type t when t == typeof(Bgr24) => PngBitDepth.Bit8, + Type t when t == typeof(Bgra32) => PngBitDepth.Bit8, + Type t when t == typeof(L8) => PngBitDepth.Bit8, + Type t when t == typeof(L16) => PngBitDepth.Bit16, + Type t when t == typeof(La16) => PngBitDepth.Bit8, + Type t when t == typeof(La32) => PngBitDepth.Bit16, + Type t when t == typeof(Rgb24) => PngBitDepth.Bit8, + Type t when t == typeof(Rgba32) => PngBitDepth.Bit8, + Type t when t == typeof(Rgb48) => PngBitDepth.Bit16, + Type t when t == typeof(Rgba64) => PngBitDepth.Bit16, + Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16, + _ => PngBitDepth.Bit8 + }; + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngFilterMethod.cs b/src/ImageSharp/Formats/Png/PngFilterMethod.cs new file mode 100644 index 0000000000..e24d86b100 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngFilterMethod.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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, + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index 210e2a837d..d90893fea1 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Registers the image encoders, decoders and mime type detectors for the png format. /// - public sealed class PngFormat : IImageFormat + public sealed class PngFormat : IImageFormat { private PngFormat() { @@ -32,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats.Png public IEnumerable FileExtensions => PngConstants.FileExtensions; /// - public PngMetaData CreateDefaultFormatMetaData() => new PngMetaData(); + public PngMetadata CreateDefaultFormatMetadata() => new PngMetadata(); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs index ea43ba96a5..d54050e029 100644 --- a/src/ImageSharp/Formats/Png/PngHeader.cs +++ b/src/ImageSharp/Formats/Png/PngHeader.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs index 5deed86e30..14498e5f1c 100644 --- a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs index 10ebcc7bbe..e524c17e96 100644 --- a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs +++ b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Png @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Provides enumeration of available PNG interlace modes. /// - internal enum PngInterlaceMode : byte + public enum PngInterlaceMode : byte { /// /// Non interlaced diff --git a/src/ImageSharp/Formats/Png/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs deleted file mode 100644 index d5ab3d2554..0000000000 --- a/src/ImageSharp/Formats/Png/PngMetaData.cs +++ /dev/null @@ -1,80 +0,0 @@ -// 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 new file mode 100644 index 0000000000..185e7bf4aa --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +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.InterlaceMethod = other.InterlaceMethod; + this.HasTransparency = other.HasTransparency; + this.TransparentL8 = other.TransparentL8; + this.TransparentL16 = other.TransparentL16; + this.TransparentRgb24 = other.TransparentRgb24; + this.TransparentRgb48 = other.TransparentRgb48; + + 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; } + + /// + /// Gets or sets the color type. + /// + public PngColorType? ColorType { get; set; } + + /// + /// 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 Rgb24 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 Rgb48 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 L8? TransparentL8 { 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 L16? TransparentL16 { get; set; } + + /// + /// Gets or sets a value indicating whether the image contains a transparency chunk and markers were decoded. + /// + public bool HasTransparency { 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; } = new List(); + + /// + public IDeepCloneable DeepClone() => new PngMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs index 4242b2d554..48ec9bdcdc 100644 --- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan scanlineSpan, Span rowSpan, bool hasTrans, - Gray16 luminance16Trans, - Gray8 luminanceTrans) - where TPixel : struct, IPixel + L16 luminance16Trans, + L8 luminanceTrans) + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Png 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)); + pixel.FromL16(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int x = 0; x < header.Width; x++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); - pixel.FromGray8(new Gray8(luminance)); + pixel.FromL8(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -55,32 +55,28 @@ namespace SixLabors.ImageSharp.Formats.Png if (header.BitDepth == 16) { - Rgba64 rgba64 = default; + La32 source = default; for (int x = 0, o = 0; x < header.Width; x++, 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; + source.L = luminance; + source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; - pixel.FromRgba64(rgba64); + pixel.FromLa32(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { + La16 source = default; byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); - Rgba32 rgba32 = default; for (int x = 0; x < header.Width; x++) { 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; + source.L = luminance; + source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; - pixel.FromRgba32(rgba32); + pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -93,9 +89,9 @@ namespace SixLabors.ImageSharp.Formats.Png int pixelOffset, int increment, bool hasTrans, - Gray16 luminance16Trans, - Gray8 luminanceTrans) - where TPixel : struct, IPixel + L16 luminance16Trans, + L8 luminanceTrans) + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -109,7 +105,7 @@ namespace SixLabors.ImageSharp.Formats.Png 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)); + pixel.FromL16(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -118,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Png 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)); + pixel.FromL8(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -128,32 +124,28 @@ namespace SixLabors.ImageSharp.Formats.Png if (header.BitDepth == 16) { - Rgba64 rgba64 = default; + La32 source = 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; + source.L = luminance; + source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; - pixel.FromRgba64(rgba64); + pixel.FromLa32(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { + La16 source = default; byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); - Rgba32 rgba32 = default; - for (int x = pixelOffset; x < header.Width; x += increment) + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) { - 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; + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); + source.L = luminance; + source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; - pixel.FromRgba32(rgba32); + pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -165,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -173,35 +165,26 @@ namespace SixLabors.ImageSharp.Formats.Png if (header.BitDepth == 16) { - Rgba64 rgba64 = default; + La32 source = 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; + source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - pixel.FromRgba64(rgba64); + pixel.FromLa32(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - Rgba32 rgba32 = default; + La16 source = 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); + source.L = Unsafe.Add(ref scanlineSpanRef, offset); + source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = alpha; - - pixel.FromRgba32(rgba32); + pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -215,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -223,35 +206,28 @@ namespace SixLabors.ImageSharp.Formats.Png if (header.BitDepth == 16) { - Rgba64 rgba64 = default; + La32 source = 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; + source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - pixel.FromRgba64(rgba64); + pixel.FromLa32(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - Rgba32 rgba32 = default; + int offset = 0; + La16 source = default; for (int x = pixelOffset; x < header.Width; x += increment) { - int offset = x * bytesPerPixel; - 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; + source.L = Unsafe.Add(ref scanlineSpanRef, offset); + source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); - pixel.FromRgba32(rgba32); + pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; + offset += bytesPerPixel; } } } @@ -262,7 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, ReadOnlySpan palette, byte[] paletteAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -308,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, ReadOnlySpan palette, byte[] paletteAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -355,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Png bool hasTrans, Rgb48 rgb48Trans, Rgb24 rgb24Trans) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -402,12 +378,12 @@ namespace SixLabors.ImageSharp.Formats.Png } else { + Rgba32 rgba32 = default; ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan); ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span); for (int x = 0; x < header.Width; x++) { 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; @@ -428,7 +404,7 @@ namespace SixLabors.ImageSharp.Formats.Png bool hasTrans, Rgb48 rgb48Trans, Rgb24 rgb24Trans) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -506,7 +482,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -539,7 +515,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -575,4 +551,4 @@ namespace SixLabors.ImageSharp.Formats.Png } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngTextData.cs b/src/ImageSharp/Formats/Png/PngTextData.cs new file mode 100644 index 0000000000..6c391d62a3 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngTextData.cs @@ -0,0 +1,143 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// 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) + { + return 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) + { + 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 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) + { + return this.Keyword.Equals(other.Keyword) + && this.Value.Equals(other.Value) + && this.LanguageTag.Equals(other.LanguageTag) + && this.TranslatedKeyword.Equals(other.TranslatedKeyword); + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs new file mode 100644 index 0000000000..8700438bd9 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Cold path optimizations for throwing png format based exceptions. + /// + internal static class PngThrowHelper + { + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) + => throw new InvalidImageContentException(errorMessage, innerException); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoHeader() => throw new InvalidImageContentException("PNG Image does not contain a header chunk"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkType(string message) => throw new InvalidImageContentException(message); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedColor() => throw new NotSupportedException("Unsupported PNG color type"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowUnknownFilter() => throw new InvalidImageContentException("Unknown filter type."); + } +} diff --git a/src/ImageSharp/Formats/Png/PngTransparentColorMode.cs b/src/ImageSharp/Formats/Png/PngTransparentColorMode.cs new file mode 100644 index 0000000000..fe92e7fbf6 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngTransparentColorMode.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Enum indicating how the transparency should be handled on encoding. + /// + public enum PngTransparentColorMode + { + /// + /// The transparency will be kept as is. + /// + Preserve = 0, + + /// + /// Converts fully transparent pixels that may contain R, G, B values which are not 0, + /// to transparent black, which can yield in better compression in some cases. + /// + Clear = 1, + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs index a06983b9ed..534aba8f5a 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs @@ -1,147 +1,260 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif +#pragma warning disable IDE0007 // Use implicit type 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. + /// Calculates the 32 bit Adler checksum of a given buffer according to + /// RFC 1950. ZLIB Compressed Data Format Specification version 3.3) /// - /// - /// 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 + internal static class Adler32 { /// - /// largest prime smaller than 65536 + /// The default initial seed value of a Adler32 checksum calculation. /// - private const uint Base = 65521; + public const uint SeedValue = 1U; - /// - /// The checksum calculated to far. - /// - private uint checksum; + // Largest prime smaller than 65536 + private const uint BASE = 65521; - /// - /// Initializes a new instance of the class. - /// The checksum starts off with a value of 1. - /// - public Adler32() - { - this.Reset(); - } + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + private const uint NMAX = 5552; - /// - public long Value - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.checksum; - } +#if SUPPORTS_RUNTIME_INTRINSICS + private const int MinBufferSize = 64; - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset() + // The C# compiler emits this as a compile-time constant embedded in the PE file. + private static ReadOnlySpan Tap1Tap2 => new byte[] { - this.checksum = 1; - } + 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 + }; +#endif + + /// + /// 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); /// - /// Updates the checksum with a byte value. + /// Calculates the Adler32 checksum with the bytes taken from the span and seed. /// - /// - /// The data value to add. The high byte of the int is ignored. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(int value) + /// The input Adler32 value. + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + public static uint Calculate(uint adler, ReadOnlySpan buffer) { - // 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; + if (buffer.IsEmpty) + { + return adler; + } - s1 = (s1 + ((uint)value & 0xFF)) % Base; - s2 = (s1 + s2) % Base; +#if SUPPORTS_RUNTIME_INTRINSICS + if (Ssse3.IsSupported && buffer.Length >= MinBufferSize) + { + return CalculateSse(adler, buffer); + } - this.checksum = (s2 << 16) + s1; + return CalculateScalar(adler, buffer); +#else + return CalculateScalar(adler, buffer); +#endif } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(ReadOnlySpan data) + // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c +#if SUPPORTS_RUNTIME_INTRINSICS + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer) { - // (By Per Bothner) - uint s1 = this.checksum & 0xFFFF; - uint s2 = this.checksum >> 16; + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; - int count = data.Length; - int offset = 0; + // Process the data in blocks. + const int BLOCK_SIZE = 1 << 5; - while (count > 0) + uint length = (uint)buffer.Length; + uint blocks = length / BLOCK_SIZE; + length -= blocks * BLOCK_SIZE; + + int index = 0; + fixed (byte* bufferPtr = buffer) + fixed (byte* tapPtr = Tap1Tap2) { - // 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) + index += (int)blocks * BLOCK_SIZE; + var localBufferPtr = bufferPtr; + + // _mm_setr_epi8 on x86 + Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr); + Vector128 tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10)); + Vector128 zero = Vector128.Zero; + var ones = Vector128.Create((short)1); + + while (blocks > 0) { - n = count; + uint n = NMAX / BLOCK_SIZE; /* 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 += BLOCK_SIZE; + } + 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; } - count -= n; - while (--n >= 0) + if (length > 0) { - s1 = s1 + (uint)(data[offset++] & 0xff); - s2 = s2 + s1; + 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; } - s1 %= Base; - s2 %= Base; + return s1 | (s2 << 16); } + } +#endif - this.checksum = (s2 << 16) | s1; + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer) + { + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; + uint k; + + fixed (byte* bufferPtr = buffer) + { + var localBufferPtr = bufferPtr; + uint length = (uint)buffer.Length; + + while (length > 0) + { + 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; + } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs new file mode 100644 index 0000000000..5007833539 --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Contains precalulated tables for scalar calculations. + /// + internal static partial class Crc32 + { + /// + /// The table of all possible eight bit values for fast scalar 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 + }; + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs index d1588c384f..6b19987cb1 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs @@ -1,152 +1,215 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.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. + /// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer + /// according to the IEEE 802.3 specification. /// - /// - /// - /// 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 + internal static partial class Crc32 { /// - /// The cycle redundancy check seed + /// The default initial seed value of a Crc32 checksum calculation. /// - private const uint CrcSeed = 0xFFFFFFFF; + public const uint SeedValue = 0U; - /// - /// The table of all possible eight bit values for fast lookup. - /// - private static readonly uint[] CrcTable = +#if SUPPORTS_RUNTIME_INTRINSICS + private const int MinBufferSize = 64; + private const int ChunksizeMask = 15; + + // Definitions of the bit-reflected domain constants k1, k2, k3, etc and + // the CRC32+Barrett polynomials given at the end of the paper. + private static readonly ulong[] K05Poly = { - 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 + 0x0154442bd4, 0x01c6e41596, // k1, k2 + 0x01751997d0, 0x00ccaa009e, // k3, k4 + 0x0163cd6124, 0x0000000000, // k5, k0 + 0x01db710641, 0x01f7011641 // polynomial }; +#endif /// - /// The data checksum so far. + /// Calculates the CRC checksum with the bytes taken from the span. /// - private uint crc; + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(ReadOnlySpan buffer) + => Calculate(SeedValue, buffer); - /// - public long Value + /// + /// Calculates the CRC checksum with the bytes taken from the span and seed. + /// + /// The input CRC value. + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(uint crc, ReadOnlySpan buffer) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.crc; + if (buffer.IsEmpty) + { + return crc; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.crc = (uint)value; +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse41.IsSupported && Pclmulqdq.IsSupported && buffer.Length >= MinBufferSize) + { + return ~CalculateSse(~crc, buffer); + } + else + { + return ~CalculateScalar(~crc, buffer); + } +#else + return ~CalculateScalar(~crc, buffer); +#endif } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset() +#if SUPPORTS_RUNTIME_INTRINSICS + // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/crc32_simd.c + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateSse(uint crc, ReadOnlySpan buffer) { - this.crc = 0; - } + int chunksize = buffer.Length & ~ChunksizeMask; + int length = chunksize; - /// - /// 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; + fixed (byte* bufferPtr = buffer) + fixed (ulong* k05PolyPtr = K05Poly) + { + byte* localBufferPtr = bufferPtr; + ulong* localK05PolyPtr = k05PolyPtr; + + // There's at least one block of 64. + Vector128 x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); + Vector128 x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); + Vector128 x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); + Vector128 x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30)); + Vector128 x5; + + x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64()); + + // k1, k2 + Vector128 x0 = Sse2.LoadVector128(localK05PolyPtr + 0x0); + + localBufferPtr += 64; + length -= 64; + + // Parallel fold blocks of 64, if any. + while (length >= 64) + { + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + Vector128 x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00); + Vector128 x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00); + Vector128 x8 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x00); + + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); + x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x11); + x3 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x11); + x4 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x11); + + Vector128 y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); + Vector128 y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); + Vector128 y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); + Vector128 y8 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30)); + + x1 = Sse2.Xor(x1, x5); + x2 = Sse2.Xor(x2, x6); + x3 = Sse2.Xor(x3, x7); + x4 = Sse2.Xor(x4, x8); + + x1 = Sse2.Xor(x1, y5); + x2 = Sse2.Xor(x2, y6); + x3 = Sse2.Xor(x3, y7); + x4 = Sse2.Xor(x4, y8); + + localBufferPtr += 64; + length -= 64; + } + + // Fold into 128-bits. + // k3, k4 + x0 = Sse2.LoadVector128(k05PolyPtr + 0x2); + + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); + x1 = Sse2.Xor(x1, x2); + x1 = Sse2.Xor(x1, x5); + + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); + x1 = Sse2.Xor(x1, x3); + x1 = Sse2.Xor(x1, x5); + + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); + x1 = Sse2.Xor(x1, x4); + x1 = Sse2.Xor(x1, x5); + + // Single fold blocks of 16, if any. + while (length >= 16) + { + x2 = Sse2.LoadVector128((ulong*)localBufferPtr); + + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); + x1 = Sse2.Xor(x1, x2); + x1 = Sse2.Xor(x1, x5); + + localBufferPtr += 16; + length -= 16; + } + + // Fold 128 - bits to 64 - bits. + x2 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x10); + x3 = Vector128.Create(~0, 0, ~0, 0).AsUInt64(); // _mm_setr_epi32 on x86 + x1 = Sse2.ShiftRightLogical128BitLane(x1, 8); + x1 = Sse2.Xor(x1, x2); + + // k5, k0 + x0 = Sse2.LoadScalarVector128(localK05PolyPtr + 0x4); + + x2 = Sse2.ShiftRightLogical128BitLane(x1, 4); + x1 = Sse2.And(x1, x3); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Sse2.Xor(x1, x2); + + // Barret reduce to 32-bits. + // polynomial + x0 = Sse2.LoadVector128(localK05PolyPtr + 0x6); + + x2 = Sse2.And(x1, x3); + x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x10); + x2 = Sse2.And(x2, x3); + x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00); + x1 = Sse2.Xor(x1, x2); + + crc = (uint)Sse41.Extract(x1.AsInt32(), 1); + return buffer.Length - chunksize == 0 ? crc : CalculateScalar(crc, buffer.Slice(chunksize)); + } } +#endif - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(ReadOnlySpan data) + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static uint CalculateScalar(uint crc, ReadOnlySpan buffer) { - this.crc ^= CrcSeed; + ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan()); + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - for (int i = 0; i < data.Length; i++) + for (int i = 0; i < buffer.Length; i++) { - this.crc = CrcTable[(this.crc ^ data[i]) & 0xFF] ^ (this.crc >> 8); + crc = Unsafe.Add(ref crcTableRef, (int)((crc ^ Unsafe.Add(ref bufferRef, i)) & 0xFF)) ^ (crc >> 8); } - this.crc ^= CrcSeed; + return crc; } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs new file mode 100644 index 0000000000..a5d129c92c --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + internal static class DeflateThrowHelper + { + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNull(string name) => throw new ArgumentNullException(name); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input."); + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs new file mode 100644 index 0000000000..8389215813 --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs @@ -0,0 +1,293 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.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(byte[] 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.engine = null; + this.isDisposed = true; + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs new file mode 100644 index 0000000000..ec224d748d --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs @@ -0,0 +1,149 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// +using System; + +namespace SixLabors.ImageSharp.Formats.Png.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/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs new file mode 100644 index 0000000000..797f5d2101 --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs @@ -0,0 +1,858 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.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 IManagedByteBuffer windowMemoryOwner; + private MemoryHandle windowMemoryHandle; + private readonly byte[] 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.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE); + this.window = this.windowMemoryOwner.Array; + this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin(); + this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; + + this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); + this.head = this.headMemoryOwner.Memory; + this.headMemoryHandle = this.headMemoryOwner.Memory.Pin(); + this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; + + this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE); + this.prev = this.prevMemoryOwner.Memory; + this.prevMemoryHandle = this.prevMemoryOwner.Memory.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.Slice(0, DeflaterConstants.HASH_SIZE).Clear(); + this.prev.Span.Slice(0, DeflaterConstants.WSIZE).Clear(); + } + + /// + /// Set the deflate level (0-9) + /// + /// The value to set the level to. + public void SetLevel(int level) + { + if ((level < 0) || (level > 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, 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, 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, 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; + } + + Buffer.BlockCopy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, 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.windowMemoryOwner = null; + this.headMemoryOwner = null; + this.prevMemoryOwner = null; + this.huffman = null; + + 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 this.window[0], ref this.window[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; + this.matchLen = Math.Max(this.matchLen, DeflaterConstants.MIN_MATCH - 1); + int matchLength = this.matchLen; + + if (scan + matchLength > scanMax) + { + return false; + } + + byte* pinnedWindow = this.pinnedWindowPointer; + int scanStart = this.strstart; + byte scanEnd1 = pinnedWindow[scan + matchLength - 1]; + byte scanEnd = pinnedWindow[scan + matchLength]; + + // 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; + + if (pinnedWindow[match + matchLength] != scanEnd + || pinnedWindow[match + matchLength - 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, 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; + } + + while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (this.lookahead == 0) + { + // We are flushing everything + this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); + this.blockStart = this.strstart; + return false; + } + + if (this.strstart > (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD) + { + // 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, 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; + } + + 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, this.blockStart, this.strstart - this.blockStart, finish); + this.blockStart = this.strstart; + return false; + } + + if (this.strstart >= (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD) + { + // 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, this.blockStart, len, lastBlock); + this.blockStart += len; + return !lastBlock; + } + } + + return true; + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs new file mode 100644 index 0000000000..96b47fb24b --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -0,0 +1,989 @@ +// Copyright (c) Six Labors. +// 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; + +namespace SixLabors.ImageSharp.Formats.Png.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 distanceManagedBuffer; + private readonly short* pinnedDistanceBuffer; + private MemoryHandle distanceBufferHandle; + + private readonly IMemoryOwner literalManagedBuffer; + 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.distanceManagedBuffer = memoryAllocator.Allocate(BufferSize); + this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin(); + this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; + + this.literalManagedBuffer = memoryAllocator.Allocate(BufferSize); + this.literalBufferHandle = this.literalManagedBuffer.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 = new short[] + { + 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 => new byte[] + { + 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 = new short[] + { + 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 => new byte[] + { + 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 => new byte[] + { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + private static ReadOnlySpan Bit4Reverse => new byte[] + { + 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 = (lc - 261) / 4; + if (bits > 0 && bits <= 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(byte[] 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(byte[] 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 (int i = 0; i < LiteralNumber; i++) + { + static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i); + } + + ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); + for (int 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 += (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, toReverse & 0xF) << 12 + | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 4) & 0xF) << 8 + | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 8) & 0xF) << 4 + | Unsafe.Add(ref bit4ReverseRef, toReverseRightShiftBy12)); + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.Pending.Dispose(); + this.distanceBufferHandle.Dispose(); + this.distanceManagedBuffer.Dispose(); + this.literalBufferHandle.Dispose(); + this.literalManagedBuffer.Dispose(); + + this.literalTree.Dispose(); + this.blTree.Dispose(); + this.distTree.Dispose(); + + this.Pending = null; + this.literalTree = null; + this.blTree = null; + this.distTree = null; + 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 IManagedByteBuffer 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.AllocateManagedByteBuffer(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, bits) = code; + code += Unsafe.Add(ref bitLengthCountsRef, 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, bits - 1)); + Unsafe.Add(ref nextCodeRef, 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, ppos = (pos - 1) >> 1)] > freq) + { + Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos); + pos = ppos; + } + + Unsafe.Add(ref heapRef, 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, 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 (int i = 0; i < heapLen; i++) + { + int node = Unsafe.Add(ref heapRef, i); + int 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) = 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, --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, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) + { + path++; + } + + Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, 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, last); + while ((path = ppos) > 0 + && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) + { + Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + } + + Unsafe.Add(ref heapRef, path) = last; + + int second = Unsafe.Add(ref heapRef, 0); + + // Create a new node father of first and second + last = numNodes++; + Unsafe.Add(ref childrenRef, 2 * last) = first; + Unsafe.Add(ref childrenRef, (2 * last) + 1) = second; + int mindepth = Math.Min(Unsafe.Add(ref valuesRef, first) & 0xFF, Unsafe.Add(ref valuesRef, second) & 0xFF); + Unsafe.Add(ref valuesRef, last) = lastVal = Unsafe.Add(ref valuesRef, first) + Unsafe.Add(ref valuesRef, 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, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) + { + path++; + } + + Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); + ppos = path; + path = (ppos * 2) + 1; + } + + // Now propagate the new element down along path + while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) + { + Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + } + + Unsafe.Add(ref heapRef, 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, i) + 1; + if (bitLength > maxLen) + { + bitLength = maxLen; + overflow++; + } + + Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, 2 * i)) = Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, (2 * i) + 1)) = bitLength; + } + else + { + // A leaf node + int bitLength = Unsafe.Add(ref lengthsRef, i); + Unsafe.Add(ref bitLengthCountsRef, bitLength - 1)++; + lengthPtr[Unsafe.Add(ref childrenRef, 2 * i)] = (byte)Unsafe.Add(ref lengthsRef, i); + } + } + } + + if (overflow == 0) + { + return; + } + + int incrBitLen = maxLen - 1; + do + { + // Find the first bit length which could increase: + while (Unsafe.Add(ref bitLengthCountsRef, --incrBitLen) == 0) + { + } + + // Move this node one down and remove a corresponding + // number of overflow nodes. + do + { + Unsafe.Add(ref bitLengthCountsRef, incrBitLen)--; + Unsafe.Add(ref bitLengthCountsRef, ++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, maxLen - 1) += overflow; + Unsafe.Add(ref bitLengthCountsRef, 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, bits - 1); + while (n > 0) + { + int childIndex = 2 * Unsafe.Add(ref childrenRef, nodeIndex++); + if (Unsafe.Add(ref childrenRef, childIndex + 1) == -1) + { + // We found another leaf + lengthPtr[Unsafe.Add(ref childrenRef, 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.frequenciesMemoryOwner = null; + this.lengthsMemoryOwner = null; + this.codesMemoryOwner = null; + + this.isDisposed = true; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs new file mode 100644 index 0000000000..5c5651996f --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs @@ -0,0 +1,153 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.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 IManagedByteBuffer memoryOwner; + private readonly byte[] 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.AllocateManagedByteBuffer(BufferLength); + this.buffer = this.memoryOwner.Array; + 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 + { + return 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, 0, BufferLength); + + if (deflateCount <= 0) + { + break; + } + + this.rawStream.Write(this.buffer, 0, deflateCount); + } + + if (!this.deflater.IsNeedingInput) + { + DeflateThrowHelper.ThrowNoDeflate(); + } + } + + private void Finish() + { + this.deflater.Finish(); + while (!this.deflater.IsFinished) + { + int len = this.deflater.Deflate(this.buffer, 0, BufferLength); + if (len <= 0) + { + break; + } + + this.rawStream.Write(this.buffer, 0, 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.deflater = null; + this.memoryOwner = null; + this.isDisposed = true; + base.Dispose(disposing); + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs new file mode 100644 index 0000000000..f702a7eada --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs @@ -0,0 +1,177 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Stores pending data for writing data to the Deflater. + /// + internal sealed unsafe class DeflaterPendingBuffer : IDisposable + { + private readonly byte[] buffer; + private readonly byte* pinnedBuffer; + private IManagedByteBuffer 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.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE); + this.buffer = this.bufferMemoryOwner.Array; + this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.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(byte[] block, int offset, int length) + { + Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref 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(byte[] 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[this.start], unchecked((uint)length)); + this.start = 0; + this.end = 0; + } + else + { + Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + this.start += length; + } + + return length; + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.bufferMemoryHandle.Dispose(); + this.bufferMemoryOwner.Dispose(); + this.bufferMemoryOwner = null; + this.isDisposed = true; + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs b/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs deleted file mode 100644 index da5deb49ef..0000000000 --- a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 index c297a91d5e..3875f98841 100644 --- a/src/ImageSharp/Formats/Png/Zlib/README.md +++ b/src/ImageSharp/Formats/Png/Zlib/README.md @@ -1,2 +1,11 @@ -Adler32.cs and Crc32.cs have been copied from -https://github.com/ygrenier/SharpZipLib.Portable +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/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs index 8e0bac938f..06c6e3dea4 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -1,9 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; -using System.IO.Compression; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -20,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// Computes the checksum for the data stream. /// - private readonly Adler32 adler32 = new Adler32(); + private uint adler = Adler32.SeedValue; /// /// A value indicating whether this instance of the given entity has been disposed. @@ -38,15 +39,18 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// The stream responsible for compressing the input stream. /// - private System.IO.Compression.DeflateStream deflateStream; + // private DeflateStream deflateStream; + 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(Stream stream, int compressionLevel) + /// 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 @@ -60,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // +---+---+ // |CMF|FLG| // +---+---+ - int cmf = 0x78; + const int Cmf = 0x78; int flg = 218; // http://stackoverflow.com/a/2331025/277304 @@ -78,29 +82,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } // Just in case - flg -= ((cmf * 256) + flg) % 31; + flg -= ((Cmf * 256) + flg) % 31; if (flg < 0) { flg += 31; } - this.rawStream.WriteByte((byte)cmf); + this.rawStream.WriteByte(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); + this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel); } /// @@ -110,47 +102,43 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public override bool CanSeek => false; /// - public override bool CanWrite => true; + public override bool CanWrite => this.rawStream.CanWrite; /// - public override long Length => throw new NotSupportedException(); + public override long Length => this.rawStream.Length; /// public override long Position { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); + get + { + return this.rawStream.Position; + } + + set + { + throw new NotSupportedException(); + } } /// - public override void Flush() - { - this.deflateStream?.Flush(); - } + public override void Flush() => this.deflateStream.Flush(); /// - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + 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 long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); /// - public override void SetLength(long value) - { - 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.adler32.Update(buffer.AsSpan(offset, count)); + this.adler = Adler32.Calculate(this.adler, buffer.AsSpan(offset, count)); } /// @@ -164,31 +152,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib 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); - } + this.deflateStream.Dispose(); // Add the crc - uint crc = (uint)this.adler32.Value; + 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.deflateStream = null; - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. + base.Dispose(disposing); this.isDisposed = true; } } diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index 583175b566..52ef0e85ba 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -1,9 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; using System.IO.Compression; +using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -13,21 +14,21 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib internal sealed class ZlibInflateStream : Stream { /// - /// Used to read the Adler-32 and Crc-32 checksums + /// 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 + /// A default delegate to get more data from the inner stream. /// - private readonly Stream innerStream; + private static readonly Func GetDataNoOp = () => 0; /// - /// The compressed stream sitting over the top of the deframer + /// The inner raw memory stream. /// - private DeflateStream compressedStream; + private readonly BufferedReadStream innerStream; /// /// A value indicating whether this instance of the given entity has been disposed. @@ -43,21 +44,30 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private bool isDisposed; /// - /// The current data remaining to be read + /// The current data remaining to be read. /// private int currentDataRemaining; /// - /// Delegate to get more data once we've exhausted the current data remaining + /// 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) + /// 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; @@ -76,31 +86,32 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public override long Length => throw new NotSupportedException(); /// - public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } /// - /// Gets the compressed stream over the deframed inner stream + /// Gets the compressed stream over the deframed inner stream. /// - public DeflateStream CompressedStream => this.compressedStream; + public DeflateStream CompressedStream { get; private set; } /// - /// Adds new bytes from a frame found in the original stream + /// Adds new bytes from a frame found in the original stream. /// - /// blabla - public void AllocateNewBytes(int bytes) + /// The current remaining data according to the chunk length. + /// Whether the chunk to be inflated is a critical chunk. + /// The . + public bool AllocateNewBytes(int bytes, bool isCriticalChunk) { this.currentDataRemaining = bytes; - if (this.compressedStream is null) + if (this.CompressedStream is null) { - this.InitializeInflateStream(); + return this.InitializeInflateStream(isCriticalChunk); } + + return true; } /// - public override void Flush() - { - throw new NotSupportedException(); - } + public override void Flush() => throw new NotSupportedException(); /// public override int ReadByte() @@ -114,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { if (this.currentDataRemaining == 0) { - // last buffer was read in its entirety, let's make sure we don't actually have more + // 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 == 0) @@ -125,25 +136,35 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib int bytesToRead = Math.Min(count, this.currentDataRemaining); this.currentDataRemaining -= bytesToRead; - int bytesRead = this.innerStream.Read(buffer, offset, 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 - while (this.currentDataRemaining == 0 && bytesRead < count) + // Keep reading data until we've reached the end of the stream or filled the buffer. + int bytesRead = 0; + offset += totalBytesRead; + while (this.currentDataRemaining == 0 && totalBytesRead < count) { this.currentDataRemaining = this.getData(); if (this.currentDataRemaining == 0) { - return bytesRead; + return totalBytesRead; } offset += bytesRead; - bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining); + + if (offset >= innerStreamLength || offset >= count) + { + return totalBytesRead; + } + + bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); this.currentDataRemaining -= bytesToRead; - bytesRead += this.innerStream.Read(buffer, offset, bytesToRead); + bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + totalBytesRead += bytesRead; } - return bytesRead; + return totalBytesRead; } /// @@ -174,11 +195,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (disposing) { - // dispose managed resources - if (this.compressedStream != null) + // Dispose managed resources. + if (this.CompressedStream != null) { - this.compressedStream.Dispose(); - this.compressedStream = null; + this.CompressedStream.Dispose(); + this.CompressedStream = null; } } @@ -190,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.isDisposed = true; } - private void InitializeInflateStream() + private bool InitializeInflateStream(bool isCriticalChunk) { // Read the zlib header : http://tools.ietf.org/html/rfc1950 // CMF(Compression Method and flags) @@ -208,7 +229,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.currentDataRemaining -= 2; if (cmf == -1 || flag == -1) { - return; + return false; } if ((cmf & 0x0F) == 8) @@ -218,14 +239,28 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib 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}"); + 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}"); + } + else + { + return false; + } } } else { - throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); + if (isCriticalChunk) + { + throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); + } + else + { + return false; + } } // The preset dictionary. @@ -238,8 +273,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.currentDataRemaining -= 4; } - // Initialize the deflate Stream. - this.compressedStream = new DeflateStream(this, CompressionMode.Decompress, true); + // Initialize the deflate BufferedReadStream. + this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true); + + return true; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf b/src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf new file mode 100644 index 0000000000..d0eca86b33 Binary files /dev/null and b/src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf differ diff --git a/src/ImageSharp/Formats/PngFilterMethod.cs b/src/ImageSharp/Formats/PngFilterMethod.cs deleted file mode 100644 index 73c4056257..0000000000 --- a/src/ImageSharp/Formats/PngFilterMethod.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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/README.md b/src/ImageSharp/Formats/README.md new file mode 100644 index 0000000000..4a2b401b1d --- /dev/null +++ b/src/ImageSharp/Formats/README.md @@ -0,0 +1,6 @@ +# 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/ITgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs new file mode 100644 index 0000000000..240b8b9b3a --- /dev/null +++ b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// The options for decoding tga images. Currently empty, but this may change in the future. + /// + internal interface ITgaDecoderOptions + { + } +} diff --git a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs new file mode 100644 index 0000000000..21599902a2 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Configuration options for use during tga encoding. + /// + internal interface ITgaEncoderOptions + { + /// + /// Gets the number of bits per pixel. + /// + TgaBitsPerPixel? BitsPerPixel { get; } + + /// + /// Gets a value indicating whether run length compression should be used. + /// + TgaCompression Compression { get; } + } +} diff --git a/src/ImageSharp/Formats/Tga/MetadataExtensions.cs b/src/ImageSharp/Formats/Tga/MetadataExtensions.cs new file mode 100644 index 0000000000..00bdb01880 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the tga format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static TgaMetadata GetTgaMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TgaFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Tga/TGA_Specification.pdf b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf new file mode 100644 index 0000000000..09c9a4ddda Binary files /dev/null and b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf differ diff --git a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs new file mode 100644 index 0000000000..7c04ed4d8b --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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. + /// + Pixel8 = 8, + + /// + /// 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 + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaCompression.cs b/src/ImageSharp/Formats/Tga/TgaCompression.cs new file mode 100644 index 0000000000..526d3b3e52 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaCompression.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..eecd5f0ea4 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the tga format. + /// + public sealed class TgaConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder()); + configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, new TgaDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaConstants.cs b/src/ImageSharp/Formats/Tga/TgaConstants.cs new file mode 100644 index 0000000000..990c2bcb10 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaConstants.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + internal static class TgaConstants + { + /// + /// The list of mimetypes that equate to a targa file. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/x-tga", "image/x-targa" }; + + /// + /// The list of file extensions that equate to a targa file. + /// + public static readonly IEnumerable FileExtensions = new[] { "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 new file mode 100644 index 0000000000..e06a0ee887 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Image decoder for Truevision TGA images. + /// + public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector + { + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new TgaDecoderCore(configuration, this); + return decoder.Decode(configuration, stream); + } + + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new TgaDecoderCore(configuration, this); + return decoder.DecodeAsync(configuration, stream, cancellationToken); + } + + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + return new TgaDecoderCore(configuration, this).Identify(configuration, stream); + } + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(stream, nameof(stream)); + + return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs new file mode 100644 index 0000000000..eef6e7362b --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -0,0 +1,899 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading; +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 : IImageDecoderInternals + { + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] scratchBuffer = new byte[4]; + + /// + /// 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; + + /// + /// The stream to decode from. + /// + private BufferedReadStream currentStream; + + /// + /// The bitmap decoder options. + /// + private readonly ITgaDecoderOptions options; + + /// + /// Indicates whether there is a alpha channel present. + /// + private bool hasAlpha; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options) + { + this.Configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; + } + + /// + public Configuration Configuration { get; } + + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + try + { + TgaImageOrigin origin = this.ReadFileHeader(stream); + this.currentStream.Skip(this.fileHeader.IdLength); + + // Parse the color map, if present. + if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 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"); + } + + var image = Image.CreateUninitialized(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 (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean)) + { + this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes); + + if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) + { + this.ReadPalettedRle( + this.fileHeader.Width, + this.fileHeader.Height, + pixels, + palette.Array, + colorMapPixelSizeInBytes, + origin); + } + else + { + this.ReadPaletted( + this.fileHeader.Width, + this.fileHeader.Height, + pixels, + palette.Array, + 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; + this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); + } + + switch (this.fileHeader.PixelDepth) + { + case 8: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); + } + else + { + this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } + + break; + + case 15: + case 16: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); + } + else + { + this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } + + break; + + case 24: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); + } + else + { + this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } + + break; + + case 32: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); + } + else + { + this.ReadBgra32(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 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(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.GetRowSpan(newY); + + switch (colorMapPixelSizeInBytes) + { + case 2: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + else + { + for (int x = 0; x < width; x++) + { + this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + + break; + + case 3: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + else + { + for (int x = 0; x < width; x++) + { + this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + + break; + + case 4: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + else + { + for (int x = 0; x < width; x++) + { + this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + + break; + } + } + } + + /// + /// Reads a run length encoded TGA image with a palette. + /// + /// The pixel type. + /// 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(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + int bytesPerPixel = 1; + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) + { + TPixel color = default; + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); + + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.GetRowSpan(newY); + int rowStartIdx = y * width * bytesPerPixel; + for (int x = 0; x < width; x++) + { + int idx = rowStartIdx + x; + switch (colorMapPixelSizeInBytes) + { + case 1: + color.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + case 2: + this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color); + break; + case 3: + color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + case 4: + color.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 width of the image. + /// The height of the image. + /// The to assign the palette to. + /// the image origin. + private void ReadMonoChrome(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + bool invertX = InvertX(origin); + if (invertX) + { + TPixel color = default; + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + for (int x = width - 1; x >= 0; x--) + { + this.ReadL8Pixel(color, x, pixelSpan); + } + } + + return; + } + + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) + { + bool invertY = InvertY(origin); + if (invertY) + { + for (int y = height - 1; y >= 0; y--) + { + this.ReadL8Row(width, pixels, row, y); + } + } + else + { + for (int y = 0; y < height; y++) + { + this.ReadL8Row(width, pixels, row, y); + } + } + } + } + + /// + /// Reads a uncompressed TGA image where each pixels has 16 bit. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgra16(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) + { + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.currentStream.Read(this.scratchBuffer, 0, 2); + if (!this.hasAlpha) + { + this.scratchBuffer[1] |= 1 << 7; + } + + if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) + { + color.FromLa16(Unsafe.As(ref this.scratchBuffer[0])); + } + else + { + color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0])); + } + + pixelSpan[x] = color; + } + } + else + { + this.currentStream.Read(row); + Span rowSpan = row.GetSpan(); + + 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 width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgr24(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + bool invertX = InvertX(origin); + if (invertX) + { + TPixel color = default; + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + for (int x = width - 1; x >= 0; x--) + { + this.ReadBgr24Pixel(color, x, pixelSpan); + } + } + + return; + } + + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) + { + bool invertY = InvertY(origin); + + if (invertY) + { + for (int y = height - 1; y >= 0; y--) + { + this.ReadBgr24Row(width, pixels, row, y); + } + } + else + { + for (int y = 0; y < height; y++) + { + this.ReadBgr24Row(width, pixels, row, y); + } + } + } + } + + /// + /// Reads a uncompressed TGA image where each pixels has 32 bit. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgra32(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) + { + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) + { + if (InvertY(origin)) + { + for (int y = height - 1; y >= 0; y--) + { + this.ReadBgra32Row(width, pixels, row, y); + } + } + else + { + for (int y = 0; y < height; y++) + { + this.ReadBgra32Row(width, pixels, row, y); + } + } + } + + return; + } + + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.GetRowSpan(newY); + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.ReadBgra32Pixel(x, color, pixelRow); + } + } + else + { + for (int x = 0; x < width; x++) + { + this.ReadBgra32Pixel(x, color, pixelRow); + } + } + } + } + + /// + /// Reads a run length encoded TGA image. + /// + /// The pixel type. + /// 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(int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + var alphaBits = this.tgaMetadata.AlphaChannelBits; + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) + { + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle(width, height, bufferSpan, bytesPerPixel); + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.GetRowSpan(newY); + int rowStartIdx = y * width * bytesPerPixel; + for (int x = 0; x < width; x++) + { + int idx = rowStartIdx + (x * bytesPerPixel); + switch (bytesPerPixel) + { + case 1: + color.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.FromLa16(Unsafe.As(ref bufferSpan[idx])); + } + else + { + color.FromBgra5551(Unsafe.As(ref bufferSpan[idx])); + } + + break; + case 3: + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + break; + case 4: + if (this.hasAlpha) + { + color.FromBgra32(Unsafe.As(ref bufferSpan[idx])); + } + else + { + var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; + color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha)); + } + + break; + } + + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; + } + } + } + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ReadFileHeader(stream); + return new ImageInfo( + new PixelTypeInfo(this.fileHeader.PixelDepth), + this.fileHeader.Width, + this.fileHeader.Height, + this.metadata); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadL8Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(row); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromL8Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadL8Pixel(TPixel color, int x, Span pixelSpan) + where TPixel : unmanaged, IPixel + { + var pixelValue = (byte)this.currentStream.ReadByte(); + color.FromL8(Unsafe.As(ref pixelValue)); + pixelSpan[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(this.scratchBuffer, 0, 3); + color.FromBgr24(Unsafe.As(ref this.scratchBuffer[0])); + pixelSpan[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgr24Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(row); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(this.scratchBuffer, 0, 4); + var alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3]; + color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha)); + pixelRow[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgra32Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(row); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra16Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color); + pixelRow[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra16Pixel(byte[] palette, int index, int colorMapPixelSizeInBytes, ref TPixel color) + where TPixel : unmanaged, IPixel + { + Bgra5551 bgra = default; + bgra.FromBgra5551(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); + } + + color.FromBgra5551(bgra); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgr24Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); + pixelRow[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra32Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); + pixelRow[x] = color; + } + + /// + /// Produce uncompressed tga data from a run length encoded stream. + /// + /// The width of the image. + /// The height of the image. + /// Buffer for uncompressed data. + /// The bytes used per pixel. + private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel) + { + int uncompressedPixels = 0; + var pixel = new byte[bytesPerPixel]; + int totalPixels = width * height; + while (uncompressedPixels < totalPixels) + { + byte runLengthByte = (byte)this.currentStream.ReadByte(); + + // The high bit of a run length packet is set to 1. + int highBit = runLengthByte >> 7; + if (highBit == 1) + { + int runLength = runLengthByte & 127; + this.currentStream.Read(pixel, 0, bytesPerPixel); + int bufferIdx = uncompressedPixels * bytesPerPixel; + for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) + { + pixel.AsSpan().CopyTo(buffer.Slice(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++) + { + this.currentStream.Read(pixel, 0, bytesPerPixel); + pixel.AsSpan().CopyTo(buffer.Slice(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) + { + switch (origin) + { + case TgaImageOrigin.BottomLeft: + case TgaImageOrigin.BottomRight: + return true; + default: + return 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) + { + switch (origin) + { + case TgaImageOrigin.TopRight: + case TgaImageOrigin.BottomRight: + return true; + default: + return false; + } + } + + /// + /// Reads the tga file header from the stream. + /// + /// The containing image data. + /// The image origin. + private TgaImageOrigin ReadFileHeader(BufferedReadStream stream) + { + this.currentStream = stream; + + Span buffer = stackalloc byte[TgaFileHeader.Size]; + + this.currentStream.Read(buffer, 0, TgaFileHeader.Size); + this.fileHeader = TgaFileHeader.Parse(buffer); + this.metadata = new ImageMetadata(); + this.tgaMetadata = this.metadata.GetTgaMetadata(); + this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; + + var alphaBits = this.fileHeader.ImageDescriptor & 0xf; + if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8) + { + TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits"); + } + + this.tgaMetadata.AlphaChannelBits = (byte)alphaBits; + this.hasAlpha = alphaBits > 0; + + // Bits 4 and 5 describe the image origin. + var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); + return origin; + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs new file mode 100644 index 0000000000..529d951cd3 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Image encoder for writing an image to a stream as a targa truevision image. + /// + public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions + { + /// + /// Gets or sets the number of bits per pixel. + /// + public TgaBitsPerPixel? BitsPerPixel { get; set; } + + /// + /// Gets or sets a value indicating whether no compression or run length compression should be used. + /// + public TgaCompression Compression { get; set; } = TgaCompression.RunLength; + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs new file mode 100644 index 0000000000..d3a628531e --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -0,0 +1,371 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +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 : IImageEncoderInternals + { + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// Reusable buffer for writing data. + /// + private readonly byte[] buffer = new byte[2]; + + /// + /// The color depth, in number of bits per pixel. + /// + private TgaBitsPerPixel? bitsPerPixel; + + /// + /// Indicates if run length compression should be used. + /// + private readonly TgaCompression compression; + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.bitsPerPixel = options.BitsPerPixel; + this.compression = options.Compression; + } + + /// + /// 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.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + TgaMetadata tgaMetadata = metadata.GetTgaMetadata(); + this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel; + + TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; + if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8) + { + 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.Pixel32) + { + // Indicate, that 8 bit are used for the alpha channel. + imageDescriptor |= 0x8; + } + + if (this.bitsPerPixel is TgaBitsPerPixel.Pixel16) + { + // Indicate, that 1 bit is used for the alpha channel. + imageDescriptor |= 0x1; + } + + var fileHeader = new TgaFileHeader( + idLength: 0, + colorMapType: 0, + imageType: imageType, + cMapStart: 0, + cMapLength: 0, + cMapDepth: 0, + xOffset: 0, + yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left. + 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); + + if (this.compression is TgaCompression.RunLength) + { + this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame); + } + else + { + this.WriteImage(stream, image.Frames.RootFrame); + } + + stream.Flush(); + } + + /// + /// 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 : unmanaged, IPixel + { + Buffer2D pixels = image.PixelBuffer; + switch (this.bitsPerPixel) + { + case TgaBitsPerPixel.Pixel8: + this.Write8Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel16: + this.Write16Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel24: + this.Write24Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel32: + this.Write32Bit(stream, pixels); + 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. + private void WriteRunLengthEncodedImage(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + Rgba32 color = default; + Buffer2D pixels = image.PixelBuffer; + int totalPixels = image.Width * image.Height; + int encodedPixels = 0; + while (encodedPixels < totalPixels) + { + int x = encodedPixels % pixels.Width; + int y = encodedPixels / pixels.Width; + TPixel currentPixel = pixels[x, y]; + currentPixel.ToRgba32(ref color); + byte equalPixelCount = this.FindEqualPixels(pixels, x, y); + + // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. + stream.WriteByte((byte)(equalPixelCount | 128)); + switch (this.bitsPerPixel) + { + case TgaBitsPerPixel.Pixel8: + int luminance = GetLuminance(currentPixel); + stream.WriteByte((byte)luminance); + break; + + case TgaBitsPerPixel.Pixel16: + var bgra5551 = new Bgra5551(color.ToVector4()); + BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); + stream.WriteByte(this.buffer[0]); + stream.WriteByte(this.buffer[1]); + + break; + + case TgaBitsPerPixel.Pixel24: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + break; + + case TgaBitsPerPixel.Pixel32: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + stream.WriteByte(color.A); + break; + } + + encodedPixels += equalPixelCount + 1; + } + } + + /// + /// Finds consecutive pixels which have the same value. + /// + /// The pixel type. + /// The pixels of the image. + /// X coordinate to start searching for the same pixels. + /// Y coordinate to start searching for the same pixels. + /// The number of equal pixels. + private byte FindEqualPixels(Buffer2D pixels, int xStart, int yStart) + where TPixel : unmanaged, IPixel + { + byte equalPixelCount = 0; + bool firstRow = true; + TPixel startPixel = pixels[xStart, yStart]; + for (int y = yStart; y < pixels.Height; y++) + { + for (int x = firstRow ? xStart + 1 : 0; x < pixels.Width; x++) + { + TPixel nextPixel = pixels[x, y]; + if (startPixel.Equals(nextPixel)) + { + equalPixelCount++; + } + else + { + return equalPixelCount; + } + + if (equalPixelCount >= 127) + { + return equalPixelCount; + } + } + + firstRow = false; + } + + return equalPixelCount; + } + + private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); + + /// + /// Writes the 8bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write8Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToL8Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); + } + } + } + + /// + /// Writes the 16bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write16Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); + } + } + } + + /// + /// Writes the 24bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write24Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) + { + 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 32bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write32Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); + } + } + } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The pixel to get the luminance from. + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(TPixel sourcePixel) + where TPixel : unmanaged, IPixel + { + var vector = sourcePixel.ToVector4(); + return ImageMaths.GetBT709Luminance(ref vector, 256); + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs new file mode 100644 index 0000000000..7ea2479dd5 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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) + { + return 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 new file mode 100644 index 0000000000..9d72ee64ff --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaFormat.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +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 current instance. + /// + public static TgaFormat Instance { get; } = new TgaFormat(); + + /// + public string Name => "TGA"; + + /// + public string DefaultMimeType => "image/tga"; + + /// + public IEnumerable MimeTypes => TgaConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => TgaConstants.FileExtensions; + + /// + public TgaMetadata CreateDefaultFormatMetadata() => new TgaMetadata(); + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs new file mode 100644 index 0000000000..018dbc7ca5 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Detects tga file headers. + /// + public sealed class TgaImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 16; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : 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. + var 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 new file mode 100644 index 0000000000..43e0a357ba --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..9525cc7e78 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageType.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..f323c93ae1 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..3a86b9551a --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Provides TGA specific metadata information for the image. + /// + public class TgaMetadata : IDeepCloneable + { + /// + /// 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.Pixel24; + + /// + /// Gets or sets the the number of alpha bits per pixel. + /// + public byte AlphaChannelBits { get; set; } = 0; + + /// + public IDeepCloneable DeepClone() => new TgaMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs new file mode 100644 index 0000000000..cf27a56561 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + internal static class TgaThrowHelper + { + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); + + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + /// The exception that is the cause of the current exception, or a null reference + /// if no inner exception is specified. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) + => throw new InvalidImageContentException(errorMessage, innerException); + + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedException(string errorMessage) + => throw new NotSupportedException(errorMessage); + } +} diff --git a/src/ImageSharp/GeometryUtilities.cs b/src/ImageSharp/GeometryUtilities.cs new file mode 100644 index 0000000000..d9c6ffa5a2 --- /dev/null +++ b/src/ImageSharp/GeometryUtilities.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 new file mode 100644 index 0000000000..80cdfd153e --- /dev/null +++ b/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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) + { + var 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) + { + var 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 var 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 var options) && options is GraphicsOptions go) + { + return go; + } + + var configOptions = new GraphicsOptions(); + + // 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 2d20c17732..99f974201c 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -8,172 +8,82 @@ namespace SixLabors.ImageSharp /// /// Options for influencing the drawing functions. /// - public struct GraphicsOptions + public class GraphicsOptions : IDeepCloneable { - /// - /// Represents the default . - /// - public static readonly GraphicsOptions Default = new GraphicsOptions(true); - - private float? blendPercentage; - - private int? antialiasSubpixelDepth; - - private bool? antialias; - - private PixelColorBlendingMode colorBlendingMode; - - private PixelAlphaCompositionMode alphaCompositionMode; + private int antialiasSubpixelDepth = 16; + private float blendPercentage = 1F; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the class. /// - /// If set to true [enable antialiasing]. - public GraphicsOptions(bool enableAntialiasing) + public GraphicsOptions() { - 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; + private GraphicsOptions(GraphicsOptions source) + { + this.AlphaCompositionMode = source.AlphaCompositionMode; + this.Antialias = source.Antialias; + this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth; + this.BlendPercentage = source.BlendPercentage; + this.ColorBlendingMode = source.ColorBlendingMode; } /// /// Gets or sets a value indicating whether antialiasing should be applied. + /// Defaults to true. /// - public bool Antialias - { - get => this.antialias ?? true; - set => this.antialias = value; - } + public bool Antialias { get; set; } = true; /// /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. + /// Defaults to 16. /// public int AntialiasSubpixelDepth { - get => this.antialiasSubpixelDepth ?? 16; - set => this.antialiasSubpixelDepth = value; + get + { + return this.antialiasSubpixelDepth; + } + + set + { + Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth)); + this.antialiasSubpixelDepth = value; + } } /// - /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// 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 => (this.blendPercentage ?? 1).Clamp(0, 1); - set => this.blendPercentage = value; - } + get + { + return this.blendPercentage; + } - // 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. + set + { + Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage)); + this.blendPercentage = value; + } + } /// - /// Gets or sets a value indicating the color blending mode to apply to the drawing operation + /// Gets or sets a value indicating the color blending mode to apply to the drawing operation. + /// Defaults to . /// - public PixelColorBlendingMode ColorBlendingMode - { - get => this.colorBlendingMode; - set => this.colorBlendingMode = value; - } - + 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 => 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; - } + public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver; + + /// + public GraphicsOptions DeepClone() => new GraphicsOptions(this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/IConfigurationModule.cs b/src/ImageSharp/IConfigurationModule.cs index 3ca8ed9182..9db719fcb1 100644 --- a/src/ImageSharp/IConfigurationModule.cs +++ b/src/ImageSharp/IConfigurationModule.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp diff --git a/src/ImageSharp/IDeepCloneable.cs b/src/ImageSharp/IDeepCloneable.cs index f80247a5d0..f6fb4e2671 100644 --- a/src/ImageSharp/IDeepCloneable.cs +++ b/src/ImageSharp/IDeepCloneable.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp diff --git a/src/ImageSharp/IImage.cs b/src/ImageSharp/IImage.cs index b9e2cee616..74a18b831f 100644 --- a/src/ImageSharp/IImage.cs +++ b/src/ImageSharp/IImage.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,4 +11,4 @@ namespace SixLabors.ImageSharp public interface IImage : IImageInfo, IDisposable { } -} \ No newline at end of file +} diff --git a/src/ImageSharp/IImageInfo.cs b/src/ImageSharp/IImageInfo.cs index 6e64aa679a..426c7ab919 100644 --- a/src/ImageSharp/IImageInfo.cs +++ b/src/ImageSharp/IImageInfo.cs @@ -1,14 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp { /// /// Encapsulates properties that describe basic image information including dimensions, pixel type information - /// and additional metadata + /// and additional metadata. /// public interface IImageInfo { @@ -30,6 +30,6 @@ namespace SixLabors.ImageSharp /// /// Gets the metadata of the image. /// - ImageMetaData MetaData { get; } + ImageMetadata Metadata { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs new file mode 100644 index 0000000000..acba3eff0a --- /dev/null +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -0,0 +1,423 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +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 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. + public BufferedReadStream(Configuration configuration, Stream stream) + { + 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."); + + // 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.Position = (int)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 = this.BufferSize; + } + + /// + /// 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)); + Guard.MustBeLessThanOrEqualTo(value, this.Length, nameof(this.Position)); + + // 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 = this.BufferSize; + } + } + } + + /// + public override bool CanRead { get; } = true; + + /// + public override bool CanSeek { get; } = true; + + /// + public override bool CanWrite { get; } = false; + + /// + /// Gets the underlying stream. + /// + public Stream BaseStream + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int ReadByte() + { + if (this.readerPosition >= this.Length) + { + return -1; + } + + // Our buffer has been read. + // We need to refill and start again. + if (this.readBufferIndex > 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) + { + // Too big for our buffer. Read directly from the stream. + if (count > this.BufferSize) + { + 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 > this.BufferSize) + { + return this.ReadToBufferViaCopySlow(buffer, offset, count); + } + + return this.ReadToBufferViaCopyFast(buffer, offset, count); + } + +#if SUPPORTS_SPAN_STREAM + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int Read(Span buffer) + { + // 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 (count + this.readBufferIndex > this.BufferSize) + { + return this.ReadToBufferViaCopySlow(buffer); + } + + return this.ReadToBufferViaCopyFast(buffer); + } +#endif + + /// + 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 = (int)baseStream.Position; + } + + // Reset to trigger full read on next attempt. + this.readBufferIndex = this.BufferSize; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override 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; + } + + /// + /// + /// 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() + { + 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; + + 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.Slice(n, count - n)); + n += i; + } + while (n < count && i > 0); + + this.Position += 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); + } + } + } +} diff --git a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs deleted file mode 100644 index 94a2f2cbfc..0000000000 --- a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -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 class DoubleBufferedStreamReader : IDisposable - { - /// - /// The length, in bytes, of the buffering chunk - /// - public const int ChunkLength = 4096; - - private const int ChunkLengthMinusOne = ChunkLength - 1; - - private readonly Stream stream; - - private readonly IManagedByteBuffer managedBuffer; - - private readonly byte[] bufferChunk; - - private readonly int length; - - private int bytesRead; - - 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.length = (int)stream.Length; - this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength, AllocationOptions.Clean); - this.bufferChunk = this.managedBuffer.Array; - } - - /// - /// 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 - { - // Reset everything. It's easier than tracking. - this.position = (int)value; - this.stream.Seek(this.position, SeekOrigin.Begin); - this.bytesRead = 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(MethodImplOptions.AggressiveInlining)] - public int ReadByte() - { - if (this.position >= this.length) - { - return -1; - } - - if (this.position == 0 || this.bytesRead > ChunkLengthMinusOne) - { - return this.ReadByteSlow(); - } - - this.position++; - return this.bufferChunk[this.bytesRead++]; - } - - /// - /// Skips the number of bytes in the stream - /// - /// The number of bytes to skip - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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(MethodImplOptions.AggressiveInlining)] - public int Read(byte[] buffer, int offset, int count) - { - if (buffer.Length > ChunkLength) - { - return this.ReadToBufferSlow(buffer, offset, count); - } - - if (this.position == 0 || count + this.bytesRead > ChunkLength) - { - return this.ReadToChunkSlow(buffer, offset, count); - } - - int n = this.GetCount(count); - this.CopyBytes(buffer, offset, n); - - this.position += n; - this.bytesRead += n; - - return n; - } - - /// - public void Dispose() - { - this.managedBuffer?.Dispose(); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadByteSlow() - { - if (this.position != this.stream.Position) - { - this.stream.Seek(this.position, SeekOrigin.Begin); - } - - this.stream.Read(this.bufferChunk, 0, ChunkLength); - this.bytesRead = 0; - - this.position++; - return this.bufferChunk[this.bytesRead++]; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadToChunkSlow(byte[] buffer, int offset, int count) - { - // Refill our buffer then copy. - if (this.position != this.stream.Position) - { - this.stream.Seek(this.position, SeekOrigin.Begin); - } - - this.stream.Read(this.bufferChunk, 0, ChunkLength); - this.bytesRead = 0; - - int n = this.GetCount(count); - this.CopyBytes(buffer, offset, n); - - this.position += n; - this.bytesRead += n; - - return n; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - 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(MethodImplOptions.AggressiveInlining)] - private int GetCount(int count) - { - int n = this.length - this.position; - if (n > count) - { - n = count; - } - - if (n < 0) - { - n = 0; - } - - return n; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyBytes(byte[] buffer, int offset, int count) - { - if (count < 9) - { - int byteCount = count; - int read = this.bytesRead; - byte[] chunk = this.bufferChunk; - - while (--byteCount > -1) - { - buffer[offset + byteCount] = chunk[read + byteCount]; - } - } - else - { - Buffer.BlockCopy(this.bufferChunk, this.bytesRead, buffer, offset, count); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/IO/Endianness.cs b/src/ImageSharp/IO/Endianness.cs deleted file mode 100644 index 59b2ae77c1..0000000000 --- a/src/ImageSharp/IO/Endianness.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.IO -{ - /// - /// Endianness of a converter - /// - internal enum Endianness - { - /// - /// Little endian - least significant byte first - /// - LittleEndian, - - /// - /// Big endian - most significant byte first - /// - BigEndian - } -} diff --git a/src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs b/src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs new file mode 100644 index 0000000000..74864d45e6 --- /dev/null +++ b/src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.IO +{ + /// + /// A memory stream constructed from a pooled buffer of known length. + /// + internal sealed class FixedCapacityPooledMemoryStream : MemoryStream + { + private readonly IManagedByteBuffer buffer; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The length of the stream buffer to rent. + /// The allocator to rent the buffer from. + public FixedCapacityPooledMemoryStream(long length, MemoryAllocator allocator) + : this(RentBuffer(length, allocator)) => this.Length = length; + + private FixedCapacityPooledMemoryStream(IManagedByteBuffer buffer) + : base(buffer.Array) => this.buffer = buffer; + + /// + public override long Length { get; } + + /// + public override bool TryGetBuffer(out ArraySegment buffer) + { + if (this.isDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + buffer = new ArraySegment(this.buffer.Array, 0, this.buffer.Length()); + return true; + } + + /// + protected override void Dispose(bool disposing) + { + if (!this.isDisposed) + { + this.isDisposed = true; + + if (disposing) + { + this.buffer.Dispose(); + } + + base.Dispose(disposing); + } + } + + // In the extrememly unlikely event someone ever gives us a stream + // with length longer than int.MaxValue then we'll use something else. + private static IManagedByteBuffer RentBuffer(long length, MemoryAllocator allocator) + { + Guard.MustBeBetweenOrEqualTo(length, 0, int.MaxValue, nameof(length)); + return allocator.AllocateManagedByteBuffer((int)length); + } + } +} diff --git a/src/ImageSharp/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs index 593c760fcf..6a765956bf 100644 --- a/src/ImageSharp/IO/IFileSystem.cs +++ b/src/ImageSharp/IO/IFileSystem.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.IO Stream OpenRead(string path); /// - /// Creates or opens a file and returns it as a writeable stream as defined by the path. + /// Creates or opens a file and returns it as a writable stream as defined by the path. /// /// Path to the file to open. /// A stream representing the file to open. diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs index dc5901ff92..50a6293a63 100644 --- a/src/ImageSharp/IO/LocalFileSystem.cs +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.IO /// /// A wrapper around the local File apis. /// - internal class LocalFileSystem : IFileSystem + internal sealed class LocalFileSystem : IFileSystem { /// public Stream OpenRead(string path) => File.OpenRead(path); diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index ffdab25e24..da23fb47dd 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -1,20 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp { /// /// Adds static methods allowing the decoding of new images. /// - public static partial class Image + public abstract partial class Image { /// /// Creates an instance backed by an uninitialized memory buffer. @@ -22,21 +24,21 @@ namespace SixLabors.ImageSharp /// The image might be filled with memory garbage. /// /// The pixel type - /// The + /// The /// The width of the image /// The height of the image - /// The + /// The /// The result internal static Image CreateUninitialized( Configuration configuration, int width, int height, - ImageMetaData metadata) - where TPixel : struct, IPixel + ImageMetadata metadata) + where TPixel : unmanaged, IPixel { Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - return new Image(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata); + return new Image(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata); } /// @@ -47,22 +49,54 @@ namespace SixLabors.ImageSharp /// The mime type or null if none found. private static IImageFormat InternalDetectFormat(Stream stream, Configuration config) { - // This is probably a candidate for making into a public API in the future! - int maxHeaderSize = config.MaxHeaderSize; - if (maxHeaderSize <= 0) + // 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(config.MaxHeaderSize, stream.Length); + if (headerSize <= 0) { return null; } - using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize, AllocationOptions.Clean)) + using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(headerSize, AllocationOptions.Clean)) { long startPosition = stream.Position; - stream.Read(buffer.Array, 0, maxHeaderSize); + + // 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 = stream.Read(buffer.Array, n, headerSize - n); + n += i; + } + while (n < headerSize && i > 0); + stream.Position = startPosition; - return config.ImageFormatsManager.FormatDetectors.Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null); + + // 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. + return config.ImageFormatsManager.FormatDetectors + .Where(x => x.HeaderSize <= headerSize) + .Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null); } } + /// + /// 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 Task InternalDetectFormatAsync(Stream stream, Configuration config) + { + // We are going to cheat here because we know that by this point we have been wrapped in a + // seekable stream then we are free to use sync APIs this is potentially brittle and may + // need a better fix in the future. + return Task.FromResult(InternalDetectFormat(stream, config)); + } + /// /// By reading the header on the provided stream this calculates the images format. /// @@ -79,7 +113,23 @@ namespace SixLabors.ImageSharp : null; } -#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The image stream to read the header from. + /// The configuration. + /// The decoder and the image format or null if none found. + private static async Task<(IImageDecoder decoder, IImageFormat format)> DiscoverDecoderAsync(Stream stream, Configuration config) + { + IImageFormat format = await InternalDetectFormatAsync(stream, config).ConfigureAwait(false); + + IImageDecoder decoder = format != null + ? config.ImageFormatsManager.FindDecoder(format) + : null; + + return (decoder, format); + } + /// /// Decodes the image stream to the current image. /// @@ -89,9 +139,8 @@ namespace SixLabors.ImageSharp /// /// 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 + private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config) + where TPixel : unmanaged, IPixel { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder is null) @@ -103,18 +152,103 @@ namespace SixLabors.ImageSharp return (img, format); } + /// + /// Decodes the image stream to the current image. + /// + /// The stream. + /// the configuration. + /// The token to monitor for cancellation requests. + /// The pixel format. + /// A representing the asynchronous operation. + private static async Task<(Image Image, IImageFormat Format)> DecodeAsync( + Stream stream, + Configuration config, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config) + .ConfigureAwait(false); + if (decoder is null) + { + return (null, null); + } + + Image img = await decoder.DecodeAsync(config, stream, cancellationToken) + .ConfigureAwait(false); + return (img, format); + } + + private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config) + { + IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + if (decoder is null) + { + return (null, null); + } + + Image img = decoder.Decode(config, stream); + return (img, format); + } + + private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken) + { + (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); + if (decoder is null) + { + return (null, null); + } + + Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false); + return (img, format); + } + /// /// Reads the raw image information from the specified stream. /// /// The stream. /// the configuration. /// - /// The or null if suitable info detector not found. + /// The or null if a suitable info detector is not found. /// - private static IImageInfo InternalIdentity(Stream stream, Configuration config) + private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config) + { + IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + + if (!(decoder is IImageInfoDetector detector)) + { + return (null, null); + } + + IImageInfo info = detector?.Identify(config, stream); + return (info, format); + } + + /// + /// Reads the raw image information from the specified stream. + /// + /// The stream. + /// the configuration. + /// The token to monitor for cancellation requests. + /// + /// A representing the asynchronous operation with the + /// property of the returned type set to null if a suitable detector + /// is not found. + private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config, CancellationToken cancellationToken) { - var detector = DiscoverDecoder(stream, config, out IImageFormat _) as IImageInfoDetector; - return detector?.Identify(config, stream); + (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); + + if (!(decoder is IImageInfoDetector detector)) + { + return (null, null); + } + + if (detector is null) + { + return (null, format); + } + + IImageInfo info = await detector.IdentifyAsync(config, stream, cancellationToken).ConfigureAwait(false); + return (info, format); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 34927e6e2b..50950c192a 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; -using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -12,89 +11,101 @@ namespace SixLabors.ImageSharp /// /// Adds static methods allowing the creation of new image from a byte array. /// - public static partial class Image + public abstract partial class Image { /// /// 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 data is null. /// The format or null if none found. public static IImageFormat DetectFormat(byte[] data) - { - return DetectFormat(Configuration.Default, data); - } + => DetectFormat(Configuration.Default, data); /// /// By reading the header on the provided byte array this calculates the images format. /// - /// The configuration. + /// The configuration. /// The byte array containing encoded image data to read the header from. + /// The configuration is null. + /// The data is null. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, byte[] data) + public static IImageFormat DetectFormat(Configuration configuration, byte[] data) { - using (Stream stream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return DetectFormat(config, stream); + return DetectFormat(configuration, stream); } } /// - /// 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. + /// Reads the raw image information from the specified stream without fully decoding it. /// - /// 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); + /// The byte array containing encoded image data to read the header from. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(byte[] data) => Identify(data, out IImageFormat _); /// - /// Load a new instance of from the given encoded byte array. + /// Reads the raw image information from the specified stream without fully decoding it. /// - /// 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); + /// The byte array containing encoded image data to read the header from. + /// The format type of the decoded image. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(byte[] data, out IImageFormat format) => Identify(Configuration.Default, data, out format); /// - /// Load a new instance of from the given encoded byte array. + /// Reads the raw image information from the specified stream without fully decoding it. /// - /// 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); + /// The configuration. + /// The byte array containing encoded image data to read the header from. + /// The format type of the decoded image. + /// The configuration is null. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format) + { + Guard.NotNull(data, nameof(data)); - /// - /// 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); + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) + { + return Identify(configuration, stream, out format); + } + } /// /// Load a new instance of from the given encoded byte array. /// - /// The config for the decoder. /// The byte array containing image data. - /// The decoder. + /// The configuration is null. + /// The data is null. /// A new . - public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) => Load(config, data, decoder); + 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 pixel format. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(byte[] data) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data); /// @@ -103,41 +114,56 @@ namespace SixLabors.ImageSharp /// The byte array containing image data. /// The mime type of the decoded image. /// The pixel format. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(byte[] data, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data, out format); /// /// Load a new instance of from the given encoded byte array. /// - /// The configuration options. + /// The configuration options. /// The byte array containing encoded image data. /// The pixel format. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(Configuration config, byte[] data) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data) + where TPixel : unmanaged, IPixel { - using (var memoryStream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, memoryStream); + return Load(configuration, stream); } } /// /// Load a new instance of from the given encoded byte array. /// - /// The configuration options. + /// The configuration options. /// The byte array containing encoded image data. /// The of the decoded image. /// The pixel format. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(Configuration config, byte[] data, out IImageFormat format) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) + where TPixel : unmanaged, IPixel { - using (var memoryStream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, memoryStream, out format); + return Load(configuration, stream, out format); } } @@ -147,37 +173,48 @@ namespace SixLabors.ImageSharp /// The byte array containing encoded image data. /// The decoder. /// The pixel format. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(byte[] data, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var memoryStream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(memoryStream, decoder); + return Load(stream, decoder); } } /// /// Load a new instance of from the given encoded byte array. /// - /// The Configuration. + /// The Configuration. /// The byte array containing encoded image data. /// The decoder. /// The pixel format. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) + where TPixel : unmanaged, IPixel { - using (var memoryStream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, memoryStream, decoder); + return Load(configuration, stream, decoder); } } /// - /// By reading the header on the provided byte array this calculates the images format. + /// By reading the header on the provided byte span this calculates the images format. /// - /// The byte array containing encoded image data to read the header from. + /// The byte span containing encoded image data to read the header from. /// The format or null if none found. public static IImageFormat DetectFormat(ReadOnlySpan data) { @@ -185,20 +222,23 @@ namespace SixLabors.ImageSharp } /// - /// By reading the header on the provided byte array this calculates the images format. + /// By reading the header on the provided byte span this calculates the images format. /// - /// The configuration. - /// The byte array containing encoded image data to read the header from. + /// The configuration. + /// The byte span containing encoded image data to read the header from. + /// The configuration is null. /// The mime type or null if none found. - public static unsafe IImageFormat DetectFormat(Configuration config, ReadOnlySpan data) + public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan data) { - int maxHeaderSize = config.MaxHeaderSize; + Guard.NotNull(configuration, nameof(configuration)); + + int maxHeaderSize = configuration.MaxHeaderSize; if (maxHeaderSize <= 0) { return null; } - foreach (IImageFormatDetector detector in config.ImageFormatsManager.FormatDetectors) + foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors) { IImageFormat f = detector.DetectFormat(data); @@ -212,45 +252,61 @@ namespace SixLabors.ImageSharp } /// - /// Load a new instance of from the given encoded byte span. + /// 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); + /// The byte span containing encoded image data. + /// The pixel format. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static Image Load(ReadOnlySpan data) + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, data); /// - /// Load a new instance of from the given encoded byte span. + /// 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); + /// The byte span containing image data. + /// The mime type of the decoded image. + /// The pixel format. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static Image Load(ReadOnlySpan data, out IImageFormat format) + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, data, out format); /// /// Load a new instance of from the given encoded byte span. /// /// The byte span containing encoded image data. + /// The decoder. /// The pixel format. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(ReadOnlySpan data) - where TPixel : struct, IPixel - => Load(Configuration.Default, data); + public static Image Load(ReadOnlySpan data, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, data, decoder); /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing encoded image data. /// The pixel format. + /// The configuration is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static unsafe Image Load(Configuration config, ReadOnlySpan data) - where TPixel : struct, IPixel + public static unsafe Image Load(Configuration configuration, ReadOnlySpan data) + where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream); + return Load(configuration, stream); } } } @@ -258,22 +314,25 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte span. /// - /// The Configuration. + /// The Configuration. /// The byte span containing image data. /// The decoder. /// The pixel format. + /// The configuration is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } } @@ -281,24 +340,197 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing image data. /// The of the decoded image. /// The pixel format. + /// The configuration is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static unsafe Image Load( - Configuration config, + Configuration configuration, + ReadOnlySpan data, + out IImageFormat format) + where TPixel : unmanaged, IPixel + { + fixed (byte* ptr = &data.GetPinnableReference()) + { + using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) + { + return Load(configuration, stream, out format); + } + } + } + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing image data. + /// The detected format. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + 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 byte array containing encoded image data. + /// The decoder. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(byte[] data, IImageDecoder decoder) + => Load(Configuration.Default, data, decoder); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The configuration for the decoder. + /// The byte array containing encoded image data. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, byte[] data) + => Load(configuration, data, out _); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The configuration for the decoder. + /// The byte array containing image data. + /// The decoder. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) + { + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) + { + return Load(configuration, stream, decoder); + } + } + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The configuration for the decoder. + /// The byte array containing image data. + /// The mime type of the decoded image. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) + { + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) + { + return Load(configuration, stream, out format); + } + } + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing image data. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(ReadOnlySpan data) + => Load(Configuration.Default, data); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing image data. + /// The decoder. + /// The data is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(ReadOnlySpan data, IImageDecoder decoder) + => Load(Configuration.Default, data, decoder); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte span containing image data. + /// The detected format. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(ReadOnlySpan data, out IImageFormat format) + => Load(Configuration.Default, data, out format); + + /// + /// Decodes a new instance of from the given encoded byte span. + /// + /// The configuration options. + /// The byte span containing image data. + /// The . + public static Image Load(Configuration configuration, ReadOnlySpan data) + => Load(configuration, data, out _); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The Configuration. + /// The byte span containing image data. + /// The decoder. + /// The configuration is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static unsafe Image Load( + Configuration configuration, + ReadOnlySpan data, + IImageDecoder decoder) + { + fixed (byte* ptr = &data.GetPinnableReference()) + { + using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) + { + return Load(configuration, 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 configuration is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static unsafe Image Load( + Configuration configuration, ReadOnlySpan data, out IImageFormat format) - where TPixel : struct, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index b13cef4824..bf239c3e9f 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -1,8 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -11,7 +13,7 @@ namespace SixLabors.ImageSharp /// /// Adds static methods allowing the creation of new image from a given file. /// - public static partial class Image + public abstract partial class Image { /// /// By reading the header on the provided file this calculates the images mime type. @@ -19,37 +21,149 @@ namespace SixLabors.ImageSharp /// 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); - } + => DetectFormat(Configuration.Default, filePath); /// /// By reading the header on the provided file this calculates the images mime type. /// - /// The configuration. + /// The configuration. /// The image file to open and to read the header from. + /// The configuration is null. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, string filePath) + public static IImageFormat DetectFormat(Configuration configuration, string filePath) + { + Guard.NotNull(configuration, nameof(configuration)); + + using (Stream file = configuration.FileSystem.OpenRead(filePath)) + { + return DetectFormat(configuration, file); + } + } + + /// + /// 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 or null if suitable info detector not found. + /// + public static IImageInfo Identify(string filePath) + => Identify(filePath, out IImageFormat _); + + /// + /// 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 format type of the decoded image. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(string filePath, out IImageFormat format) + => Identify(Configuration.Default, filePath, out format); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The format type of the decoded image. + /// The configuration is null. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, string filePath, out IImageFormat format) { - config = config ?? Configuration.Default; - using (Stream file = config.FileSystem.OpenRead(filePath)) + Guard.NotNull(configuration, nameof(configuration)); + using (Stream file = configuration.FileSystem.OpenRead(filePath)) { - return DetectFormat(config, file); + return Identify(configuration, file, out format); } } /// - /// Create a new instance of the class from the given file. + /// 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 configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task IdentifyAsync(string filePath, CancellationToken cancellationToken = default) + => IdentifyAsync(Configuration.Default, filePath, cancellationToken); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static async Task IdentifyAsync( + Configuration configuration, + string filePath, + CancellationToken cancellationToken = default) + { + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken) + .ConfigureAwait(false); + return res.ImageInfo; + } + + /// + /// 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 configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + string filePath, + CancellationToken cancellationToken = default) + => IdentifyWithFormatAsync(Configuration.Default, filePath, cancellationToken); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Configuration configuration, + string filePath, + CancellationToken cancellationToken = default) + { + Guard.NotNull(configuration, nameof(configuration)); + using Stream stream = configuration.FileSystem.OpenRead(filePath); + return await IdentifyWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// 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); + /// The . + public static Image Load(string path) + => Load(Configuration.Default, path); /// - /// Create a new instance of the class from the given file. + /// Create a new instance of the class from the given file. /// /// The file path to the image. /// The mime type of the decoded image. @@ -57,121 +171,310 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path, out IImageFormat format) => Load(path, out format); + public static Image Load(string path, out IImageFormat format) + => Load(Configuration.Default, path, out format); /// - /// Create a new instance of the class from the given file. + /// Create a new instance of the class from the given file. /// - /// The config for the decoder. + /// The configuration 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); + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, string path) + => Load(configuration, path, out _); /// - /// Create a new instance of the class from the given file. + /// Create a new instance of the class from the given file. /// - /// The config for the decoder. + /// The configuration 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); + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task LoadAsync( + Configuration configuration, + string path, + CancellationToken cancellationToken = default) + { + using Stream stream = configuration.FileSystem.OpenRead(path); + (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + return img; + } /// - /// Create a new instance of the class from the given file. + /// Create a new instance of the class from the given file. /// - /// The Configuration. + /// 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); + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, string path, IImageDecoder decoder) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) + { + return Load(configuration, stream, decoder); + } + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(string path, CancellationToken cancellationToken = default) + => LoadAsync(Configuration.Default, path, cancellationToken); /// - /// Create a new instance of the class from the given file. + /// 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); + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(string path, IImageDecoder decoder) + => LoadAsync(Configuration.Default, path, decoder, default); + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The decoder. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(string path, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => LoadAsync(Configuration.Default, path, decoder, default); + + /// + /// Create a new instance of the class from the given file. + /// + /// The Configuration. + /// The file path to the image. + /// The decoder. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync( + Configuration configuration, + string path, + IImageDecoder decoder, + CancellationToken cancellationToken = default) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using Stream stream = configuration.FileSystem.OpenRead(path); + return LoadAsync(configuration, stream, decoder, cancellationToken); + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The Configuration. + /// The file path to the image. + /// The decoder. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync( + Configuration configuration, + string path, + IImageDecoder decoder, + CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using Stream stream = configuration.FileSystem.OpenRead(path); + return LoadAsync(configuration, stream, decoder, cancellationToken); + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(string path) + where TPixel : unmanaged, IPixel + => LoadAsync(Configuration.Default, path, default(CancellationToken)); + + /// + /// Create a new instance of the class from the given file. + /// + /// The configuration for the decoder. + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task> LoadAsync( + Configuration configuration, + string path, + CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + { + using Stream stream = configuration.FileSystem.OpenRead(path); + (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + return img; + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The decoder. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(string path, IImageDecoder decoder) + => Load(Configuration.Default, 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 path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . public static Image Load(string path) - where TPixel : struct, IPixel - { - return Load(Configuration.Default, path); - } + where TPixel : unmanaged, IPixel + => 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 path is null. + /// Image format not recognised. + /// Image contains invalid content. /// 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); - } + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, path, out format); /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// The configuration options. /// The file path to the image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Configuration config, string path) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path) + where TPixel : unmanaged, IPixel { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream); + return Load(configuration, stream); } } /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// 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 configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Configuration config, string path, out IImageFormat format) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path, out IImageFormat format) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) + { + return Load(configuration, stream, out format); + } + } + + /// + /// Create a new instance of the class from the given file. + /// The pixel type is selected by the decoder. + /// + /// The configuration options. + /// The file path to the image. + /// The mime type of the decoded image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static Image Load(Configuration configuration, string path, out IImageFormat format) { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } @@ -180,35 +483,38 @@ namespace SixLabors.ImageSharp /// /// The file path to the image. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . public static Image Load(string path, IImageDecoder decoder) - where TPixel : struct, IPixel - { - return Load(Configuration.Default, path, decoder); - } + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, path, decoder); /// /// Create a new instance of the class from the given file. /// - /// The Configuration. + /// The Configuration. /// The file path to the image. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Configuration config, string path, IImageDecoder decoder) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path, IImageDecoder decoder) + where TPixel : unmanaged, IPixel { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 3236e00072..ee148cd254 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -1,11 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -13,168 +16,670 @@ namespace SixLabors.ImageSharp /// /// Adds static methods allowing the creation of new image from a given stream. /// - public static partial class Image + public abstract partial class Image { /// - /// By reading the header on the provided stream this calculates the images mime type. + /// By reading the header on the provided stream this calculates the images format 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); + /// The stream is null. + /// The stream is not readable. + /// The format 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. + /// By reading the header on the provided stream this calculates the images format type. /// - /// The configuration. + /// 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)); + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// The format type or null if none found. + public static IImageFormat DetectFormat(Configuration configuration, Stream stream) + => WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration)); /// - /// By reading the header on the provided stream this reads the raw image information. + /// By reading the header on the provided stream this calculates the images format type. /// /// The image stream to read the header from. - /// Thrown if the stream is not readable. + /// The stream is null. + /// The stream is not readable. + /// A representing the asynchronous operation or null if none is found. + public static Task DetectFormatAsync(Stream stream) + => DetectFormatAsync(Configuration.Default, stream); + + /// + /// By reading the header on the provided stream this calculates the images format type. + /// + /// The configuration. + /// The image stream to read the header from. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// A representing the asynchronous operation. + public static Task DetectFormatAsync(Configuration configuration, Stream stream) + => WithSeekableStreamAsync( + configuration, + stream, + (s, _) => InternalDetectFormatAsync(s, configuration), + default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the header from. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if a suitable info detector is not found. + /// + public static IImageInfo Identify(Stream stream) + => Identify(stream, out IImageFormat _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the header from. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. + /// + public static Task IdentifyAsync(Stream stream) + => IdentifyAsync(Configuration.Default, stream); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the header from. + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if a suitable info detector is not found. + /// + public static IImageInfo Identify(Stream stream, out IImageFormat format) + => Identify(Configuration.Default, stream, out format); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if a suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, Stream stream) + => Identify(configuration, stream, out _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. + /// + public static async Task IdentifyAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) + { + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + return res.ImageInfo; + } + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The format type of the decoded image. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if a suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, Stream stream, out IImageFormat format) + { + (IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default)); + + format = data.Format; + return data.ImageInfo; + } + + /// + /// 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 configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. /// - /// The or null if suitable info detector not found. + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. /// - public static IImageInfo Identify(Stream stream) => Identify(Configuration.Default, stream); + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Stream stream, + CancellationToken cancellationToken = default) + => IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The configuration. /// The image stream to read the information from. - /// Thrown if the stream is not readable. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. /// - /// The or null if suitable info detector is not found. + /// The representing the asynchronous operation with the parameter type + /// property set to 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)); + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) + => WithSeekableStreamAsync( + configuration, + stream, + (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct), + cancellationToken); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Stream stream, out IImageFormat format) + => Load(Configuration.Default, stream, out format); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream) + => LoadWithFormatAsync(Configuration.Default, stream); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Stream stream) => Load(Configuration.Default, stream); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(Stream stream) => LoadAsync(Configuration.Default, stream); /// - /// Create a new instance of the class from the given stream. + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by 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(Stream stream, out IImageFormat format) => Load(stream, out format); + /// The decoder. + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Stream stream, IImageDecoder decoder) + => Load(Configuration.Default, stream, decoder); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The decoder. + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(Stream stream, IImageDecoder decoder) + => LoadAsync(Configuration.Default, stream, decoder); /// - /// Create a new instance of the class from the given stream. + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. /// + /// The configuration for the decoder. /// The stream containing image information. - /// Thrown if the stream is not readable. - /// A new .> - public static Image Load(Stream stream) => Load(stream); + /// The decoder. + /// The configuration is null. + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) + { + Guard.NotNull(decoder, nameof(decoder)); + return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); + } /// - /// Create a new instance of the class from the given stream. + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. /// + /// The configuration for the decoder. /// 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); + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync( + Configuration configuration, + Stream stream, + IImageDecoder decoder, + CancellationToken cancellationToken = default) + { + Guard.NotNull(decoder, nameof(decoder)); + return WithSeekableStreamAsync( + configuration, + stream, + (s, ct) => decoder.DecodeAsync(configuration, s, ct), + cancellationToken); + } /// - /// Create a new instance of the class from the given stream. + /// Decode a new instance of the class from the given stream. /// - /// The config for the decoder. + /// The configuration 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); + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _); /// - /// Create a new instance of the class from the given stream. + /// Decode a new instance of the class from the given stream. /// - /// The config for the decoder. + /// The configuration 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); + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + { + (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + return fmt.Image; + } /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// Thrown if the stream is not readable. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A new . public static Image Load(Stream stream) - where TPixel : struct, IPixel - => Load(null, stream); + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, stream); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(Stream stream) + where TPixel : unmanaged, IPixel + => LoadAsync(Configuration.Default, 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 format type of the decoded image. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A new . public static Image Load(Stream stream, out IImageFormat format) - where TPixel : struct, IPixel - => Load(null, stream, out format); + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, stream, out format); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream) + where TPixel : unmanaged, IPixel + => await LoadWithFormatAsync(Configuration.Default, stream).ConfigureAwait(false); /// /// 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 stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A new . public static Image Load(Stream stream, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, 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 token to monitor for cancellation requests. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + => WithSeekableStreamAsync( + Configuration.Default, + stream, + (s, ct) => decoder.DecodeAsync(Configuration.Default, s, ct), + cancellationToken); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The Configuration. + /// The stream containing image information. + /// The decoder. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new . + public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The Configuration. + /// The stream containing image information. + /// The decoder. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// 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)); + /// A representing the asynchronous operation. + public static Task> LoadAsync( + Configuration configuration, + Stream stream, + IImageDecoder decoder, + CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + => WithSeekableStreamAsync( + configuration, + stream, + (s, ct) => decoder.DecodeAsync(configuration, s, ct), + cancellationToken); /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The configuration options. /// The stream containing image information. - /// Thrown if the stream is not readable. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. - /// A new .> - public static Image Load(Configuration config, Stream stream) - where TPixel : struct, IPixel - => Load(config, stream, out IImageFormat _); + /// A new . + public static Image Load(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + => Load(configuration, stream, out IImageFormat _); /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The configuration options. /// The stream containing image information. - /// the mime type of the decoded image. - /// Thrown if the stream is not readable. + /// The format type of the decoded image. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. - /// A new .> - public static Image Load(Configuration config, Stream stream, out IImageFormat format) - where TPixel : struct, IPixel + /// A representing the asynchronous operation. + public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) + where TPixel : unmanaged, IPixel + { + (Image Image, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); + + format = data.Format; + + if (data.Image != null) + { + return data.Image; + } + + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); + + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + { + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + } + + throw new UnknownImageFormatException(sb.ToString()); + } + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) { - config = config ?? Configuration.Default; - (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); + (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( + configuration, + stream, + async (s, ct) => await DecodeAsync(s, configuration, ct).ConfigureAwait(false), + cancellationToken) + .ConfigureAwait(false); + + if (data.Image != null) + { + return data; + } + + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); + + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + { + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + } + + throw new UnknownImageFormatException(sb.ToString()); + } + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + { + (Image Image, IImageFormat Format) data = + await WithSeekableStreamAsync( + configuration, + stream, + (s, ct) => DecodeAsync(s, configuration, ct), + cancellationToken) + .ConfigureAwait(false); + + if (data.Image != null) + { + return data; + } + + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); + + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + { + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + } + + throw new UnknownImageFormatException(sb.ToString()); + } + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task> LoadAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + { + (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + return img; + } + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The configuration options. + /// The stream containing image information. + /// The format type of the decoded image. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) + { + (Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); format = data.format; @@ -186,16 +691,30 @@ namespace SixLabors.ImageSharp var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) { - sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } - throw new NotSupportedException(sb.ToString()); + throw new UnknownImageFormatException(sb.ToString()); } - private static T WithSeekableStream(Configuration config, Stream stream, Func action) + /// + /// Performs the given action against the stream ensuring that it is seekable. + /// + /// The type of object returned from the action. + /// The configuration. + /// The input stream. + /// The action to perform. + /// The . + private static T WithSeekableStream( + Configuration configuration, + Stream stream, + Func action) { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(stream, nameof(stream)); + if (!stream.CanRead) { throw new NotSupportedException("Cannot read from the stream."); @@ -203,7 +722,7 @@ namespace SixLabors.ImageSharp if (stream.CanSeek) { - if (config.ReadOrigin == ReadOrigin.Begin) + if (configuration.ReadOrigin == ReadOrigin.Begin) { stream.Position = 0; } @@ -212,13 +731,55 @@ namespace SixLabors.ImageSharp } // We want to be able to load images from things like HttpContext.Request.Body - using (var memoryStream = new MemoryStream()) + using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length); + stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize); + memoryStream.Position = 0; + + 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 configuration. + /// The input stream. + /// The action to perform. + /// The cancellation token. + /// The . + private static async Task WithSeekableStreamAsync( + Configuration configuration, + Stream stream, + Func> action, + CancellationToken cancellationToken) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(stream, nameof(stream)); + + if (!stream.CanRead) { - stream.CopyTo(memoryStream); - memoryStream.Position = 0; + throw new NotSupportedException("Cannot read from the stream."); + } - return action(memoryStream); + // To make sure we don't trigger anything with aspnetcore then we just need to make sure we are + // seekable and we make the copy using CopyToAsync if the stream is seekable then we arn't using + // one of the aspnetcore wrapped streams that error on sync api calls and we can use it without + // having to further wrap + if (stream.CanSeek) + { + if (configuration.ReadOrigin == ReadOrigin.Begin) + { + stream.Position = 0; + } + + return await action(stream, cancellationToken).ConfigureAwait(false); } + + using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length); + await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); + memoryStream.Position = 0; + + return await action(memoryStream, cancellationToken).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index 282f980865..4ea72ca2ca 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp /// /// Adds static methods allowing the creation of new image from raw pixel data. /// - public static partial class Image + public abstract partial class Image { /// /// Create a new instance of the class from the raw data. @@ -20,9 +20,10 @@ namespace SixLabors.ImageSharp /// 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(TPixel[] data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -32,9 +33,10 @@ namespace SixLabors.ImageSharp /// 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 : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -44,9 +46,10 @@ namespace SixLabors.ImageSharp /// 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(byte[] data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -56,72 +59,81 @@ namespace SixLabors.ImageSharp /// 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 : struct, IPixel + where TPixel : unmanaged, 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 configuration for the decoder. /// The byte array 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 config, byte[] data, int width, int height) - where TPixel : struct, IPixel - => LoadPixelData(config, MemoryMarshal.Cast(new ReadOnlySpan(data)), width, height); + public static Image LoadPixelData(Configuration configuration, byte[] data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, 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 configuration for the decoder. /// The byte array 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 config, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel - => LoadPixelData(config, MemoryMarshal.Cast(data), width, height); + 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 raw data. /// - /// The config for the decoder. + /// The configuration 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. + /// The configuration is null. + /// The data length is incorrect. /// 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); - } + public static Image LoadPixelData(Configuration configuration, TPixel[] data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, new ReadOnlySpan(data), width, height); /// /// Create a new instance of the class from the raw data. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The 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 config, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel { + Guard.NotNull(configuration, nameof(configuration)); + int count = width * height; Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - var image = new Image(config, width, height); - - data.Slice(0, count).CopyTo(image.Frames.RootFrame.GetPixelSpan()); + var image = new Image(configuration, width, height); + data = data.Slice(0, count); + data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); return image; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index c2935bed93..2d3c29ed42 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -1,42 +1,46 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp { /// /// Adds static methods allowing wrapping an existing memory area as an image. /// - public static partial class Image + public abstract partial class Image { /// /// 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 + /// 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 config, + Configuration configuration, Memory pixelMemory, int width, int height, - ImageMetaData metaData) - where TPixel : struct, IPixel + ImageMetadata metadata) + where TPixel : unmanaged, IPixel { - var memorySource = new MemorySource(pixelMemory); - return new Image(config, memorySource, width, height, metaData); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + var memorySource = MemoryGroup.Wrap(pixelMemory); + return new Image(configuration, memorySource, width, height, metadata); } /// @@ -44,39 +48,36 @@ namespace SixLabors.ImageSharp /// 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 + /// 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 config, + Configuration configuration, Memory pixelMemory, int width, int height) - where TPixel : struct, IPixel - { - return WrapMemory(config, pixelMemory, width, height, new ImageMetaData()); - } + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, 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 + /// 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); - } + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pixelMemory, width, height); /// /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, @@ -86,22 +87,27 @@ namespace SixLabors.ImageSharp /// It will be disposed together with the result image. /// /// The pixel type - /// The + /// The /// The that is being transferred to the image - /// The width of the memory image - /// The height of the memory image - /// The + /// 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 config, + Configuration configuration, IMemoryOwner pixelMemoryOwner, int width, int height, - ImageMetaData metaData) - where TPixel : struct, IPixel + ImageMetadata metadata) + where TPixel : unmanaged, IPixel { - var memorySource = new MemorySource(pixelMemoryOwner, false); - return new Image(config, memorySource, width, height, metaData); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + var memorySource = MemoryGroup.Wrap(pixelMemoryOwner); + return new Image(configuration, memorySource, width, height, metadata); } /// @@ -111,21 +117,20 @@ namespace SixLabors.ImageSharp /// 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 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 config, + Configuration configuration, IMemoryOwner pixelMemoryOwner, int width, int height) - where TPixel : struct, IPixel - { - return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetaData()); - } + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata()); /// /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, @@ -135,17 +140,15 @@ namespace SixLabors.ImageSharp /// 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 + /// 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); - } + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs new file mode 100644 index 0000000000..fbb3ec2069 --- /dev/null +++ b/src/ImageSharp/Image.cs @@ -0,0 +1,189 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +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 : IImage, IConfigurationProvider + { + private Size size; + private readonly Configuration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The configuration which allows altering default behaviour or extending the library. + /// + /// The . + /// The . + /// The . + protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size) + { + this.configuration = configuration ?? Configuration.Default; + this.PixelType = pixelType; + this.size = size; + this.Metadata = metadata ?? new ImageMetadata(); + } + + /// + /// Initializes a new instance of the class. + /// + internal Image( + Configuration configuration, + PixelTypeInfo pixelType, + ImageMetadata metadata, + int width, + int height) + : this(configuration, pixelType, metadata, new Size(width, height)) + { + } + + /// + /// Gets the implementing the public property. + /// + protected abstract ImageFrameCollection NonGenericFrameCollection { get; } + + /// + public PixelTypeInfo PixelType { get; } + + /// + public int Width => this.size.Width; + + /// + public int Height => this.size.Height; + + /// + public ImageMetadata Metadata { get; } + + /// + /// Gets the frames of the image as (non-generic) . + /// + public ImageFrameCollection Frames => this.NonGenericFrameCollection; + + /// + Configuration IConfigurationProvider.Configuration => this.configuration; + + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 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.GetConfiguration()); + + /// + /// 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; + + /// + /// Update the size of the image after mutation. + /// + /// The . + protected void UpdateSize(Size size) => this.size = size; + + /// + /// 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 abstract void EnsureNotDisposed(); + + /// + /// 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); + + 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 dfdbbd89b8..b2ba19e84e 100644 --- a/src/ImageSharp/ImageExtensions.Internal.cs +++ b/src/ImageSharp/ImageExtensions.Internal.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp { @@ -24,7 +23,7 @@ namespace SixLabors.ImageSharp /// The /// internal static Buffer2D GetRootFramePixelBuffer(this Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.Frames.RootFrame.PixelBuffer; } diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index cbf93275c8..d40c5c271b 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -1,104 +1,112 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// - /// Extension methods over Image{TPixel} + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// 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 - { - Guard.NotNullOrWhiteSpace(filePath, nameof(filePath)); - - string ext = Path.GetExtension(filePath); - IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); - if (format is null) - { - var sb = new StringBuilder(); - sb.AppendLine($"Can't find a format that is associated with the file extension '{ext}'. Registered formats with there extensions include:"); - foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) - { - sb.AppendLine($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}"); - } + /// The file path to save the image to. + /// The path is null. + public static void Save(this Image source, string path) + => source.Save(path, source.DetectEncoder(path)); - throw new NotSupportedException(sb.ToString()); - } - - IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// The source image. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// The path is null. + /// A representing the asynchronous operation. + public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default) + => source.SaveAsync(path, source.DetectEncoder(path), cancellationToken); - if (encoder is null) + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// 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.GetConfiguration().FileSystem.Create(path)) { - var sb = new StringBuilder(); - sb.AppendLine($"Can't find encoder for file 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()); + source.Save(fs, encoder); } - - source.Save(filePath, encoder); } /// /// 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 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 + /// 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) { + Guard.NotNull(path, nameof(path)); Guard.NotNull(encoder, nameof(encoder)); - using (Stream fs = source.GetConfiguration().FileSystem.Create(filePath)) - { - source.Save(fs, encoder); - } + using Stream fs = source.GetConfiguration().FileSystem.Create(path); + await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false); } /// /// 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 + /// 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) + { + throw new NotSupportedException("Cannot write to the stream."); + } + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); if (encoder is null) { var sb = new StringBuilder(); - sb.AppendLine("Can't find encoder for provided mime type. Available encoded:"); + sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { - sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } throw new NotSupportedException(sb.ToString()); @@ -109,21 +117,28 @@ namespace SixLabors.ImageSharp /// /// Returns a Base64 encoded string from the given image. + /// The result is prepended with a Data URI + /// + /// + /// For example: + /// + /// + /// /// - /// - /// The pixel format. /// The source image /// The format. + /// The format is null. /// The - public static string ToBase64String(this Image source, IImageFormat format) - where TPixel : struct, IPixel + public static string ToBase64String(this Image source, IImageFormat format) { - using (var stream = new MemoryStream()) - { - source.Save(stream, format); - stream.Flush(); - return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; - } + Guard.NotNull(format, nameof(format)); + + using var stream = new MemoryStream(); + source.Save(stream, format); + + // Always available. + stream.TryGetBuffer(out ArraySegment buffer); + return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(buffer.Array, 0, (int)stream.Length)}"; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 33dbe31df7..39fd50e193 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -1,17 +1,18 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// - /// Adds static methods allowing the creation of new image from raw pixel data. + /// Contains methods for loading raw pixel data. /// - internal static class ImageFrame + public partial class ImageFrame { /// /// Create a new instance of the class from the given byte array in format. @@ -22,8 +23,8 @@ namespace SixLabors.ImageSharp /// 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 + internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); /// @@ -35,17 +36,18 @@ namespace SixLabors.ImageSharp /// 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 + internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel { int count = width * height; Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); var image = new ImageFrame(configuration, width, height); - data.Slice(0, count).CopyTo(image.GetPixelSpan()); + data = data.Slice(0, count); + data.CopyTo(image.PixelBuffer.FastMemoryGroup); return image; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs new file mode 100644 index 0000000000..fa25d76423 --- /dev/null +++ b/src/ImageSharp/ImageFrame.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 + { + private readonly Configuration configuration; + + /// + /// 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) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + this.configuration = configuration ?? Configuration.Default; + this.Width = width; + this.Height = height; + this.Metadata = metadata; + } + + /// + /// Gets the width. + /// + public int Width { get; private set; } + + /// + /// Gets the height. + /// + public int Height { get; private set; } + + /// + /// Gets the metadata of the frame. + /// + public ImageFrameMetadata Metadata { get; } + + /// + Configuration IConfigurationProvider.Configuration => this.configuration; + + /// + /// 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); + + /// + 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. + /// + internal void UpdateSize(Size size) + { + this.Width = size.Width; + this.Height = size.Height; + } + } +} diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index bbe05ce435..62ecc71f55 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -1,152 +1,72 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections; using System.Collections.Generic; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// - /// Encapsulates a collection of instances that make up an . + /// Encapsulates a pixel-agnostic collection of instances + /// that make up an . /// - /// The type of the pixel. - public sealed class ImageFrameCollection : IEnumerable> - where TPixel : struct, IPixel + public abstract class ImageFrameCollection : IEnumerable { - 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.GetConfiguration(), width, height, backgroundColor)); - } - - internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource) - { - this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - - // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.GetConfiguration(), 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 int Count => this.frames.Count; + public abstract int Count { get; } /// /// Gets the root frame. /// - public ImageFrame RootFrame => this.frames.Count > 0 ? this.frames[0] : null; + public ImageFrame RootFrame => this.NonGenericRootFrame; + + /// + /// Gets the root frame. (Implements .) + /// + protected abstract ImageFrame NonGenericRootFrame { get; } /// - /// Gets the at the specified index. + /// Gets the at the specified index. /// /// - /// The . + /// The . /// /// The index. - /// The at the specified index. - public ImageFrame this[int index] => this.frames[index]; + /// The at the specified index. + public ImageFrame this[int index] => this.NonGenericGetFrame(index); /// - /// Determines the index of a specific in the . + /// Determines the index of a specific in the . /// - /// The to locate 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); + public abstract int IndexOf(ImageFrame frame); /// - /// Clones and inserts the into the at the specified . + /// 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 . + /// 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; - } + /// The cloned . + public ImageFrame InsertFrame(int index, ImageFrame source) => 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.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); - 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(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 ImageFrame AddFrame(ImageFrame source) => 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 void RemoveFrame(int index) - { - 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 abstract void RemoveFrame(int index); /// /// Determines whether the contains the . @@ -155,24 +75,14 @@ namespace SixLabors.ImageSharp /// /// true if the contains the specified frame; otherwise, false. /// - public bool Contains(ImageFrame frame) => this.frames.Contains(frame); + 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 void MoveFrame(int sourceIndex, int destinationIndex) - { - if (sourceIndex == destinationIndex) - { - return; - } - - ImageFrame frameAtIndex = this.frames[sourceIndex]; - this.frames.RemoveAt(sourceIndex); - this.frames.Insert(destinationIndex, frameAtIndex); - } + 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 @@ -181,19 +91,7 @@ namespace SixLabors.ImageSharp /// 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]; - - if (this.Count == 1 && this.frames.Contains(frame)) - { - throw new InvalidOperationException("Cannot remove last frame."); - } - - this.frames.Remove(frame); - - return new Image(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { frame }); - } + public Image ExportFrame(int index) => this.NonGenericExportFrame(index); /// /// Creates an with only the frame at the specified index @@ -201,12 +99,7 @@ namespace SixLabors.ImageSharp /// /// The zero-based index of the frame to clone. /// The new with the specified frame. - public Image CloneFrame(int index) - { - ImageFrame frame = this[index]; - ImageFrame clonedFrame = frame.Clone(); - return new Image(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { clonedFrame }); - } + public Image CloneFrame(int index) => this.NonGenericCloneFrame(index); /// /// Creates a new and appends it to the end of the collection. @@ -214,7 +107,7 @@ namespace SixLabors.ImageSharp /// /// The new . /// - public ImageFrame CreateFrame() => this.CreateFrame(default); + public ImageFrame CreateFrame() => this.NonGenericCreateFrame(); /// /// Creates a new and appends it to the end of the collection. @@ -223,44 +116,67 @@ namespace SixLabors.ImageSharp /// /// 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; - } + public ImageFrame CreateFrame(Color backgroundColor) => this.NonGenericCreateFrame(backgroundColor); - /// - IEnumerator> IEnumerable>.GetEnumerator() => this.frames.GetEnumerator(); + /// + public IEnumerator GetEnumerator() => this.NonGenericGetEnumerator(); /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - private void ValidateFrame(ImageFrame frame) - { - Guard.NotNull(frame, nameof(frame)); + /// + /// 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); + + /// + /// Implements . + /// + /// The index. + /// The new image. + protected abstract Image NonGenericExportFrame(int index); - 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 index. + /// The new image. + protected abstract Image NonGenericCloneFrame(int index); - internal void Dispose() - { - foreach (ImageFrame f in this.frames) - { - f.Dispose(); - } + /// + /// Implements . + /// + /// The new frame. + protected abstract ImageFrame NonGenericCreateFrame(); - this.frames.Clear(); - } + /// + /// Implements . + /// + /// The background color. + /// The new frame. + protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs new file mode 100644 index 0000000000..36c3ee481f --- /dev/null +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -0,0 +1,358 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections; +using System.Collections.Generic; + +using SixLabors.ImageSharp.Advanced; +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.GetConfiguration(), 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.GetConfiguration(), 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 => this.frames.Count > 0 ? this.frames[0] : null; + + /// + 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] => this.frames[index]; + + /// + public override int IndexOf(ImageFrame frame) + { + return frame is ImageFrame specific ? this.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.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; + } + + /// + /// 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; + } + + /// + /// 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) + { + var frame = ImageFrame.LoadPixelData( + this.parent.GetConfiguration(), + 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) + { + 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) => + frame is ImageFrame specific && this.Contains(specific); + + /// + /// 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 override void MoveFrame(int sourceIndex, int destinationIndex) + { + 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) + { + 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.GetConfiguration(), 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) + { + ImageFrame frame = this[index]; + ImageFrame clonedFrame = frame.Clone(); + return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame }); + } + + /// + /// Creates a new and appends it to the end of the collection. + /// + /// + /// The new . + /// + public new ImageFrame CreateFrame() + { + var frame = new ImageFrame( + this.parent.GetConfiguration(), + 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) + { + var frame = new ImageFrame( + this.parent.GetConfiguration(), + this.RootFrame.Width, + this.RootFrame.Height, + backgroundColor); + this.frames.Add(frame); + return frame; + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() => this.frames.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).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)); + } + } + } + + internal void Dispose() + { + foreach (ImageFrame f in this.frames) + { + f.Dispose(); + } + + this.frames.Clear(); + } + + private ImageFrame CopyNonCompatibleFrame(ImageFrame source) + { + var result = new ImageFrame( + this.parent.GetConfiguration(), + 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 f69ae37574..a336c9c591 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -1,25 +1,25 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; -using System.Threading.Tasks; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; -using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp { /// - /// Represents a single frame in a animation. + /// 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 : IPixelSource, IDisposable - where TPixel : struct, IPixel + public sealed class ImageFrame : ImageFrame, IPixelSource + where TPixel : unmanaged, IPixel { private bool isDisposed; @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp /// 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(configuration, width, height, new ImageFrameMetadata()) { } @@ -39,9 +39,9 @@ namespace SixLabors.ImageSharp /// /// The configuration which allows altering default behaviour or extending the library. /// The of the frame. - /// The meta data. - internal ImageFrame(Configuration configuration, Size size, ImageFrameMetaData metaData) - : this(configuration, size.Width, size.Height, metaData) + /// The metadata. + internal ImageFrame(Configuration configuration, Size size, ImageFrameMetadata metadata) + : this(configuration, size.Width, size.Height, metadata) { } @@ -51,10 +51,14 @@ namespace SixLabors.ImageSharp /// 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 meta data. - internal ImageFrame(Configuration configuration, int width, int height, ImageFrameMetaData metaData) - : this(configuration, width, height, default(TPixel), metaData) + /// 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.GetConfiguration().MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); } /// @@ -65,7 +69,7 @@ namespace SixLabors.ImageSharp /// 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()) + : this(configuration, width, height, backgroundColor, new ImageFrameMetadata()) { } @@ -76,18 +80,15 @@ namespace SixLabors.ImageSharp /// The width of the image in pixels. /// The height of the image in pixels. /// The color to clear the image with. - /// The meta data. - internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetaData metaData) + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetadata metadata) + : base(configuration, width, height, metadata) { - 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); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height); + this.Clear(backgroundColor); } /// @@ -97,8 +98,8 @@ namespace SixLabors.ImageSharp /// 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()) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource) + : this(configuration, width, height, memorySource, new ImageFrameMetadata()) { } @@ -109,18 +110,14 @@ namespace SixLabors.ImageSharp /// The width of the image in pixels. /// The height of the image in pixels. /// The memory source. - /// The meta data. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetaData metaData) + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) + : base(configuration, width, height, metadata) { - 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; } /// @@ -129,27 +126,15 @@ namespace SixLabors.ImageSharp /// 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.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 = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); + source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); } - /// - /// Gets the to use for buffer allocations. - /// - public MemoryAllocator MemoryAllocator { get; } - - /// - /// Gets the instance associated with this . - /// - internal Configuration Configuration { get; } - /// /// Gets the image pixels. Not private as Buffer2D requires an array in its constructor. /// @@ -158,47 +143,63 @@ namespace SixLabors.ImageSharp /// 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 meta data 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. + /// Thrown when the provided (x,y) coordinates are outside the image boundary. public TPixel this[int x, int y] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.PixelBuffer[x, y]; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.VerifyCoords(x, y); + return this.PixelBuffer.GetElementUnsafe(x, y); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.PixelBuffer[x, y] = value; + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.VerifyCoords(x, y); + this.PixelBuffer.GetElementUnsafe(x, y) = value; + } } /// - /// Gets the size of the frame. + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. /// - /// The - public Size Size() => new Size(this.Width, this.Height); + /// The row. + /// The + /// Thrown when row index is out of range. + public Span GetPixelRowSpan(int rowIndex) + { + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); + + return this.PixelBuffer.GetRowSpan(rowIndex); + } /// - /// Gets the bounds of the frame. + /// 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. /// - /// The - public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); + /// The . + /// The . + public bool TryGetSinglePixelSpan(out Span span) + { + IMemoryGroup mg = this.GetPixelMemoryGroup(); + if (mg.Count > 1) + { + span = default; + return false; + } + + span = mg.Single().Span; + return true; + } /// /// Gets a reference to the pixel at the specified position. @@ -220,7 +221,7 @@ namespace SixLabors.ImageSharp throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } - this.GetPixelSpan().CopyTo(target.GetSpan()); + this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup); } /// @@ -232,25 +233,44 @@ namespace SixLabors.ImageSharp Guard.NotNull(pixelSource, nameof(pixelSource)); Buffer2D.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); + this.UpdateSize(this.PixelBuffer.Size()); } - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - internal void Dispose() + /// + protected override void Dispose(bool disposing) { if (this.isDisposed) { return; } - this.PixelBuffer?.Dispose(); - this.PixelBuffer = null; + if (disposing) + { + this.PixelBuffer?.Dispose(); + this.PixelBuffer = null; + } - // Note disposing is done. this.isDisposed = true; } + internal override void CopyPixelsTo(MemoryGroup destination) + { + if (typeof(TPixel) == typeof(TDestinationPixel)) + { + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => + { + Span d1 = MemoryMarshal.Cast(d); + s.CopyTo(d1); + }); + return; + } + + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => + { + PixelOperations.Instance.To(this.GetConfiguration(), s, d); + }); + } + /// public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})"; @@ -258,7 +278,7 @@ namespace SixLabors.ImageSharp /// Clones the current instance. /// /// The - internal ImageFrame Clone() => this.Clone(this.Configuration); + internal ImageFrame Clone() => this.Clone(this.GetConfiguration()); /// /// Clones the current instance. @@ -273,7 +293,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The internal ImageFrame CloneAs() - where TPixel2 : struct, IPixel => this.CloneAs(this.Configuration); + where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); /// /// Returns a copy of the image frame in the given pixel format. @@ -282,27 +302,20 @@ namespace SixLabors.ImageSharp /// The configuration providing initialization code which allows extending the library. /// The internal ImageFrame CloneAs(Configuration configuration) - where TPixel2 : struct, IPixel + where TPixel2 : unmanaged, IPixel { if (typeof(TPixel2) == typeof(TPixel)) { return this.Clone(configuration) as ImageFrame; } - var target = new ImageFrame(configuration, this.Width, this.Height, this.MetaData.DeepClone()); + var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); + var operation = new RowIntervalOperation(this, target, configuration); - ParallelHelper.IterateRows( - this.Bounds(), + ParallelRowIterator.IterateRowIntervals( 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); - } - }); + this.Bounds(), + in operation); return target; } @@ -310,23 +323,73 @@ namespace SixLabors.ImageSharp /// /// Clears the bitmap. /// - /// The parallel options. /// The value to initialize the bitmap with. - internal void Clear(ParallelOptions parallelOptions, TPixel value) + internal void Clear(TPixel value) { - Span span = this.GetPixelSpan(); + MemoryGroup group = this.PixelBuffer.FastMemoryGroup; if (value.Equals(default)) { - span.Clear(); + group.Clear(); } else { - span.Fill(value); + group.Fill(value); } } - /// - void IDisposable.Dispose() => this.Dispose(); + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if (x < 0 || x >= this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); + } + + if (y < 0 || y >= this.Height) + { + ThrowArgumentOutOfRangeException(nameof(y)); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string paramName) + { + throw new ArgumentOutOfRangeException(paramName); + } + + /// + /// A implementing the clone logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TPixel2 : unmanaged, IPixel + { + private readonly ImageFrame source; + private readonly ImageFrame target; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + ImageFrame source, + ImageFrame target, + Configuration configuration) + { + this.source = source; + this.target = target; + this.configuration = configuration; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.target.GetPixelRowSpan(y); + PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); + } + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageInfo.cs b/src/ImageSharp/ImageInfo.cs index 6f894cb599..3128e63bfe 100644 --- a/src/ImageSharp/ImageInfo.cs +++ b/src/ImageSharp/ImageInfo.cs @@ -1,8 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp { @@ -17,13 +17,13 @@ namespace SixLabors.ImageSharp /// 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) + /// The images metadata. + public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata metadata) { this.PixelType = pixelType; this.Width = width; this.Height = height; - this.MetaData = metaData; + this.Metadata = metadata; } /// @@ -36,6 +36,6 @@ namespace SixLabors.ImageSharp public int Height { get; } /// - public ImageMetaData MetaData { get; } + public ImageMetadata Metadata { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/ImageInfoExtensions.cs b/src/ImageSharp/ImageInfoExtensions.cs index dca5502d0f..7af166c9ca 100644 --- a/src/ImageSharp/ImageInfoExtensions.cs +++ b/src/ImageSharp/ImageInfoExtensions.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp { /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 6ab609d5c0..c3d9618c8c 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -1,125 +1,44 @@ - + + + + SixLabors.ImageSharp SixLabors.ImageSharp - 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 $(packageversion) 0.0.1 - netstandard1.3;netstandard2.0;netcoreapp2.1;net472 + + netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;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 - - - - $(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS + SixLabors.ImageSharp - + + - - - - - + + - - - - - + + + + - - ..\..\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 - + + + + + @@ -158,15 +77,30 @@ True Bgra32.PixelOperations.Generated.tt - + + True + True + Bgra5551.PixelOperations.Generated.tt + + True True - Gray8.PixelOperations.Generated.tt + L8.PixelOperations.Generated.tt - + True True - Gray16.PixelOperations.Generated.tt + L16.PixelOperations.Generated.tt + + + True + True + La16.PixelOperations.Generated.tt + + + True + True + La32.PixelOperations.Generated.tt True @@ -198,9 +132,95 @@ True PorterDuffFunctions.Generated.tt + + True + True + ImageExtensions.Save.tt + - + + 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 + 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 index a7337240a5..6896e069c2 100644 --- a/src/ImageSharp/ImageSharp.csproj.DotSettings +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -1,10 +1,15 @@  + True True True + True + True True True True True True + True + True True True \ No newline at end of file diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 178194b21e..255193c8ea 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -1,27 +1,29 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +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 : IImage, IConfigurable - where TPixel : struct, IPixel + public sealed class Image : Image + where TPixel : unmanaged, IPixel { - private readonly Configuration configuration; + private bool isDisposed; /// /// Initializes a new instance of the class @@ -31,7 +33,7 @@ namespace SixLabors.ImageSharp /// 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()) + : this(configuration, width, height, new ImageMetadata()) { } @@ -44,7 +46,19 @@ namespace SixLabors.ImageSharp /// 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()) + : 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()) { } @@ -67,29 +81,30 @@ namespace SixLabors.ImageSharp /// 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) + internal Image(Configuration configuration, int width, int height, ImageMetadata metadata) + : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - 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 + /// wrapping an external . /// /// The configuration providing initialization code which allows extending the library. - /// The memory source. + /// 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) + internal Image( + Configuration configuration, + MemoryGroup memoryGroup, + int width, + int height, + ImageMetadata metadata) + : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - this.configuration = configuration; - this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); - this.MetaData = metadata; - this.Frames = new ImageFrameCollection(this, width, height, memorySource); + this.Frames = new ImageFrameCollection(this, width, height, memoryGroup); } /// @@ -101,11 +116,14 @@ namespace SixLabors.ImageSharp /// 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) + internal Image( + Configuration configuration, + int width, + int height, + TPixel backgroundColor, + ImageMetadata metadata) + : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - 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); } @@ -116,36 +134,19 @@ namespace SixLabors.ImageSharp /// 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) + internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable> frames) + : base(configuration, PixelTypeInfo.Create(), metadata, ValidateFramesAndGetSize(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; } + /// + protected override ImageFrameCollection NonGenericFrameCollection => this.Frames; /// - /// Gets the frames. + /// Gets the collection of image frames. /// - public ImageFrameCollection Frames { get; } + public new ImageFrameCollection Frames { get; } /// /// Gets the root frame. @@ -158,32 +159,63 @@ namespace SixLabors.ImageSharp /// 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] { - get => this.PixelSource.PixelBuffer[x, y]; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.VerifyCoords(x, y); + return this.PixelSource.PixelBuffer.GetElementUnsafe(x, y); + } + + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.VerifyCoords(x, y); + this.PixelSource.PixelBuffer.GetElementUnsafe(x, y) = value; + } + } + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// The row. + /// The + /// Thrown when row index is out of range. + public Span GetPixelRowSpan(int rowIndex) + { + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); - set => this.PixelSource.PixelBuffer[x, y] = value; + return this.PixelSource.PixelBuffer.GetRowSpan(rowIndex); } /// - /// Saves the image to the given stream using the given image encoder. + /// 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. /// - /// 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) + /// The . + /// The . + public bool TryGetSinglePixelSpan(out Span span) { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); + IMemoryGroup mg = this.GetPixelMemoryGroup(); + if (mg.Count > 1) + { + span = default; + return false; + } - encoder.Encode(this, stream); + span = mg.Single().Span; + 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); + public Image Clone() => this.Clone(this.GetConfiguration()); /// /// Clones the current image with the given configuration. @@ -192,37 +224,80 @@ namespace SixLabors.ImageSharp /// Returns a new with all the same pixel data as the original. public Image Clone(Configuration configuration) { - IEnumerable> clonedFrames = this.Frames.Select(x => x.Clone(configuration)); - return new Image(configuration, this.MetaData.DeepClone(), clonedFrames); - } + 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); + var clonedFrames = new ImageFrame[this.Frames.Count]; + for (int i = 0; i < clonedFrames.Length; i++) + { + clonedFrames[i] = this.Frames[i].Clone(configuration); + } + + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); + } /// /// 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 + /// The . + public override Image CloneAs(Configuration configuration) + { + this.EnsureNotDisposed(); + + var clonedFrames = new ImageFrame[this.Frames.Count]; + for (int i = 0; i < clonedFrames.Length; i++) + { + clonedFrames[i] = this.Frames[i].CloneAs(configuration); + } + + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); + } + + /// + protected override void Dispose(bool disposing) { - IEnumerable> clonedFrames = this.Frames.Select(x => x.CloneAs(configuration)); - return new Image(configuration, this.MetaData.DeepClone(), clonedFrames); + if (this.isDisposed) + { + return; + } + + if (disposing) + { + this.Frames.Dispose(); + } + + this.isDisposed = true; } /// - public void Dispose() => this.Frames.Dispose(); + internal override void EnsureNotDisposed() + { + if (this.isDisposed) + { + throw new ObjectDisposedException("Trying to execute an operation on a disposed image."); + } + } /// 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 pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. /// @@ -235,6 +310,49 @@ namespace SixLabors.ImageSharp { this.Frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.Frames[i]); } + + this.UpdateSize(pixelSource.Size()); + } + + private static Size ValidateFramesAndGetSize(IEnumerable> frames) + { + Guard.NotNull(frames, nameof(frames)); + + ImageFrame rootFrame = frames.FirstOrDefault(); + + if (rootFrame == null) + { + 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(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if (x < 0 || x >= this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); + } + + if (y < 0 || y >= this.Height) + { + ThrowArgumentOutOfRangeException(nameof(y)); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + 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 new file mode 100644 index 0000000000..7668d7600a --- /dev/null +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 Buffer2D pixelBuffer; + 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 frame width. + /// The frame height. + /// The color palette. + internal 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 independant of any palette source. + this.paletteOwner = configuration.MemoryAllocator.Allocate(palette.Length); + palette.Span.CopyTo(this.paletteOwner.GetSpan()); + this.Palette = this.paletteOwner.Memory.Slice(0, palette.Length); + } + + /// + /// Gets the configuration which allows altering default behaviour 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. + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPixelRowSpan(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.GetRowSpan(rowIndex); + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.pixelBuffer.Dispose(); + this.paletteOwner.Dispose(); + this.pixelBuffer = null; + this.paletteOwner = null; + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs new file mode 100644 index 0000000000..3c865f3578 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Options for allocating buffers. + /// + public enum AllocationOptions + { + /// + /// Indicates that the buffer should just be allocated. + /// + None, + + /// + /// Indicates that the allocated buffer should be cleaned following allocation. + /// + Clean + } +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs new file mode 100644 index 0000000000..a7a51f77dd --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// 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.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Contains and . + /// + public partial class ArrayPoolMemoryAllocator + { + /// + /// The buffer implementation of . + /// + private class Buffer : ManagedBufferBase + where T : struct + { + /// + /// The length of the buffer. + /// + private readonly int length; + + /// + /// A weak reference to the source pool. + /// + /// + /// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed + /// after a call to , regardless of having buffer instances still being in use. + /// + private WeakReference> sourcePoolReference; + + public Buffer(byte[] data, int length, ArrayPool sourcePool) + { + this.Data = data; + this.length = length; + this.sourcePoolReference = new WeakReference>(sourcePool); + } + + /// + /// Gets the buffer as a byte array. + /// + protected byte[] Data { get; private set; } + + /// + public override Span GetSpan() + { + if (this.Data is null) + { + ThrowObjectDisposedException(); + } + + return MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); + } + + /// + protected override void Dispose(bool disposing) + { + if (!disposing || this.Data is null || this.sourcePoolReference is null) + { + return; + } + + if (this.sourcePoolReference.TryGetTarget(out ArrayPool pool)) + { + pool.Return(this.Data); + } + + this.sourcePoolReference = null; + this.Data = null; + } + + protected override object GetPinnableObject() => this.Data; + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); + } + } + + /// + /// The implementation of . + /// + private sealed class ManagedByteBuffer : Buffer, IManagedByteBuffer + { + public ManagedByteBuffer(byte[] data, int length, ArrayPool sourcePool) + : base(data, length, sourcePool) + { + } + + /// + public byte[] Array => this.Data; + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs new file mode 100644 index 0000000000..8aa1b90634 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Contains common factory methods and configuration constants. + /// + public partial class ArrayPoolMemoryAllocator + { + /// + /// The default value for: maximum size of pooled arrays in bytes. + /// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data. + /// + internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024; + + /// + /// The value for: The threshold to pool arrays in which has less buckets for memory safety. + /// + private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024; + + /// + /// The default bucket count for . + /// + private const int DefaultLargePoolBucketCount = 6; + + /// + /// The default bucket count for . + /// + private const int DefaultNormalPoolBucketCount = 16; + + // TODO: This value should be determined by benchmarking + private const int DefaultBufferCapacityInBytes = int.MaxValue / 4; + + /// + /// This is the default. Should be good for most use cases. + /// + /// The memory manager. + public static ArrayPoolMemoryAllocator CreateDefault() + { + return new ArrayPoolMemoryAllocator( + DefaultMaxPooledBufferSizeInBytes, + DefaultBufferSelectorThresholdInBytes, + DefaultLargePoolBucketCount, + DefaultNormalPoolBucketCount, + DefaultBufferCapacityInBytes); + } + + /// + /// For environments with very limited memory capabilities, only small buffers like image rows are pooled. + /// + /// The memory manager. + public static ArrayPoolMemoryAllocator CreateWithMinimalPooling() + { + return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24); + } + + /// + /// For environments with limited memory capabilities, only small array requests are pooled, which can result in reduced throughput. + /// + /// The memory manager. + public static ArrayPoolMemoryAllocator CreateWithModeratePooling() + { + return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24); + } + + /// + /// For environments where memory capabilities are not an issue, the maximum amount of array requests are pooled which results in optimal throughput. + /// + /// The memory manager. + public static ArrayPoolMemoryAllocator CreateWithAggressivePooling() + { + return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs new file mode 100644 index 0000000000..8814bbe1f5 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -0,0 +1,189 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements by allocating memory from . + /// + public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator + { + private readonly int maxArraysPerBucketNormalPool; + + private readonly int maxArraysPerBucketLargePool; + + /// + /// The for small-to-medium buffers which is not kept clean. + /// + private ArrayPool normalArrayPool; + + /// + /// The for huge buffers, which is not kept clean. + /// + private ArrayPool largeArrayPool; + + /// + /// Initializes a new instance of the class. + /// + public ArrayPoolMemoryAllocator() + : this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes) + : this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// Arrays over this threshold will be pooled in which has less buckets for memory safety. + public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes) + : this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// The threshold to pool arrays in which has less buckets for memory safety. + /// Max arrays per bucket for the large array pool. + /// Max arrays per bucket for the normal array pool. + public ArrayPoolMemoryAllocator( + int maxPoolSizeInBytes, + int poolSelectorThresholdInBytes, + int maxArraysPerBucketLargePool, + int maxArraysPerBucketNormalPool) + : this( + maxPoolSizeInBytes, + poolSelectorThresholdInBytes, + maxArraysPerBucketLargePool, + maxArraysPerBucketNormalPool, + DefaultBufferCapacityInBytes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// The threshold to pool arrays in which has less buckets for memory safety. + /// Max arrays per bucket for the large array pool. + /// Max arrays per bucket for the normal array pool. + /// The length of the largest contiguous buffer that can be handled by this allocator instance. + public ArrayPoolMemoryAllocator( + int maxPoolSizeInBytes, + int poolSelectorThresholdInBytes, + int maxArraysPerBucketLargePool, + int maxArraysPerBucketNormalPool, + int bufferCapacityInBytes) + { + Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); + Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); + + this.MaxPoolSizeInBytes = maxPoolSizeInBytes; + this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; + this.BufferCapacityInBytes = bufferCapacityInBytes; + this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool; + this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool; + + this.InitArrayPools(); + } + + /// + /// Gets the maximum size of pooled arrays in bytes. + /// + public int MaxPoolSizeInBytes { get; } + + /// + /// Gets the threshold to pool arrays in which has less buckets for memory safety. + /// + public int PoolSelectorThresholdInBytes { get; } + + /// + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// + public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests + + /// + public override void ReleaseRetainedResources() + { + this.InitArrayPools(); + } + + /// + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; + + /// + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + int itemSizeBytes = Unsafe.SizeOf(); + int bufferSizeInBytes = length * itemSizeBytes; + if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes) + { + ThrowInvalidAllocationException(length); + } + + ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); + byte[] byteArray = pool.Rent(bufferSizeInBytes); + + var buffer = new Buffer(byteArray, length, pool); + if (options == AllocationOptions.Clean) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + + /// + public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + + ArrayPool pool = this.GetArrayPool(length); + byte[] byteArray = pool.Rent(length); + + var buffer = new ManagedByteBuffer(byteArray, length, pool); + if (options == AllocationOptions.Clean) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + + private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) + { + return maxPoolSizeInBytes / 4; + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowInvalidAllocationException(int length) => + throw new InvalidMemoryOperationException( + $"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator."); + + private ArrayPool GetArrayPool(int bufferSizeInBytes) + { + return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool; + } + + private void InitArrayPools() + { + this.largeArrayPool = ArrayPool.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool); + this.normalArrayPool = ArrayPool.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs b/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs new file mode 100644 index 0000000000..8088a2c47c --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s. + /// + public interface IManagedByteBuffer : IMemoryOwner + { + /// + /// Gets the managed array backing this buffer instance. + /// + byte[] Array { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs new file mode 100644 index 0000000000..3a3c695b2c --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs new file mode 100644 index 0000000000..499a9228c1 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Provides an based on . + /// + internal sealed class BasicByteBuffer : BasicArrayBuffer, IManagedByteBuffer + { + /// + /// Initializes a new instance of the class. + /// + /// The byte array. + internal BasicByteBuffer(byte[] array) + : base(array) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs new file mode 100644 index 0000000000..3f54e335e8 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +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 = (void*)this.pinHandle.AddrOfPinnedObject(); + return new MemoryHandle(ptr, this.pinHandle); + } + + /// + public override void Unpin() + { + if (this.pinHandle.IsAllocated) + { + this.pinHandle.Free(); + } + } + + /// + /// Gets the object that should be pinned. + /// + /// The pinnable . + protected abstract object GetPinnableObject(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs new file mode 100644 index 0000000000..ff376a6186 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Memory managers are used to allocate memory for image processing operations. + /// + public abstract class MemoryAllocator + { + /// + /// 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(); + + /// + /// 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; + + /// + /// Allocates an . + /// + /// The requested buffer length. + /// The allocation options. + /// The . + /// When length is zero or negative. + /// When length is over the capacity of the allocator. + public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None); + + /// + /// Releases all retained resources not being in use. + /// Eg: by resetting array pools and letting GC to free the arrays. + /// + public virtual void ReleaseRetainedResources() + { + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs new file mode 100644 index 0000000000..84494f6856 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +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) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + + return new BasicArrayBuffer(new T[length]); + } + + /// + public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + + return new BasicByteBuffer(new byte[length]); + } + } +} diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 17ab6e2522..9fce9a4f4e 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -1,80 +1,70 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; - -using SixLabors.Primitives; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory { /// /// Defines extension methods for . /// - internal static class Buffer2DExtensions + public static class Buffer2DExtensions { /// - /// Gets a to the backing buffer of . + /// Gets the backing . /// - internal static Span GetSpan(this Buffer2D buffer) + /// The buffer. + /// The element type. + /// The MemoryGroup. + public static IMemoryGroup GetMemoryGroup(this Buffer2D buffer) where T : struct { - return buffer.MemorySource.GetSpan(); + Guard.NotNull(buffer, nameof(buffer)); + return buffer.FastMemoryGroup.View; } /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// TODO: Does not work with multi-buffer groups, should be specific to Resize. + /// Copy columns of inplace, + /// from positions starting at to positions at . /// - /// 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) + internal static unsafe void CopyColumns( + this Buffer2D buffer, + int sourceIndex, + int destIndex, + int columnCount) where T : struct { - return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x); - } + DebugGuard.NotNull(buffer, nameof(buffer)); + DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex)); + DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex)); + CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount); - /// - /// 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); - } + int elementSize = Unsafe.SizeOf(); + int width = buffer.Width * elementSize; + int sOffset = sourceIndex * elementSize; + int dOffset = destIndex * elementSize; + long count = columnCount * elementSize; - /// - /// 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); - } + Span span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span); - /// - /// 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); + fixed (byte* ptr = span) + { + byte* basePtr = ptr; + for (int y = 0; y < buffer.Height; y++) + { + byte* sPtr = basePtr + sOffset; + byte* dPtr = basePtr + dOffset; + + Buffer.MemoryCopy(sPtr, dPtr, count, count); + + basePtr += width; + } + } } /// @@ -83,44 +73,63 @@ namespace SixLabors.ImageSharp.Memory /// The element type /// The /// The - public static Rectangle FullRectangle(this Buffer2D buffer) + internal static Rectangle FullRectangle(this Buffer2D buffer) where T : struct { return new Rectangle(0, 0, buffer.Width, buffer.Height); } /// - /// Return a to the subarea represented by 'rectangle' + /// Return a to the subregion 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)); + /// The rectangle subregion + /// The + internal static Buffer2DRegion GetRegion(this Buffer2D buffer, Rectangle rectangle) + where T : unmanaged => + new Buffer2DRegion(buffer, rectangle); - 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)); + internal static Buffer2DRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) + where T : unmanaged => + new Buffer2DRegion(buffer, new Rectangle(x, y, width, height)); /// - /// Return a to the whole area of 'buffer' + /// 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); + /// The + internal static Buffer2DRegion GetRegion(this Buffer2D buffer) + where T : unmanaged => + new Buffer2DRegion(buffer); /// - /// Gets a span for all the pixels in defined by + /// Returns the size of the buffer. /// - public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) + /// The element type + /// The + /// The of the buffer + internal static Size Size(this Buffer2D buffer) + where T : struct + { + return new Size(buffer.Width, buffer.Height); + } + + [Conditional("DEBUG")] + private static void CheckColumnRegionsDoNotOverlap( + Buffer2D buffer, + int sourceIndex, + int destIndex, + int columnCount) where T : struct { - return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); + 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 new file mode 100644 index 0000000000..8c59889442 --- /dev/null +++ b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. +using System; +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 GetRowSpan(int y) + { + int yy = this.Rectangle.Y + y; + int xx = this.Rectangle.X; + int width = this.Rectangle.Width; + + return this.Buffer.GetRowSpan(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) + { + var rectangle = new Rectangle(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.GetRowSpan(y)[x]; + } + + 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.GetRowSpan(y); + row.Clear(); + } + } + } +} diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 41a560cdb6..38ca89e59b 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; - -using SixLabors.Primitives; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory { @@ -12,23 +12,31 @@ namespace SixLabors.ImageSharp.Memory /// Represents a buffer of value type objects /// interpreted as a 2D region of x elements. /// + /// + /// Before RC1, this class might be target of API changes, use it on your own risk! + /// /// The value type. - internal sealed class Buffer2D : IDisposable + public sealed class Buffer2D : IDisposable where T : struct { - private MemorySource memorySource; + private Memory cachedMemory = default; /// /// 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) + /// The to wrap. + /// The number of elements in a row. + /// The number of rows. + internal Buffer2D(MemoryGroup memoryGroup, int width, int height) { - this.memorySource = memorySource; + this.FastMemoryGroup = memoryGroup; this.Width = width; this.Height = height; + + if (memoryGroup.Count == 1) + { + this.cachedMemory = memoryGroup[0]; + } } /// @@ -42,13 +50,20 @@ namespace SixLabors.ImageSharp.Memory public int Height { get; private set; } /// - /// Gets the backing + /// Gets the backing . /// - public MemorySource MemorySource => this.memorySource; + /// The MemoryGroup. + public IMemoryGroup MemoryGroup => this.FastMemoryGroup.View; - public Memory Memory => this.MemorySource.Memory; - - public Span Span => this.Memory.Span; + /// + /// 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; } /// /// Gets a reference to the element at the specified position. @@ -56,15 +71,18 @@ namespace SixLabors.ImageSharp.Memory /// 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(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] 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]; + 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.GetRowSpan(y)[x]; } } @@ -73,20 +91,131 @@ namespace SixLabors.ImageSharp.Memory /// public void Dispose() { - this.MemorySource.Dispose(); + this.FastMemoryGroup.Dispose(); + this.cachedMemory = default; + } + + /// + /// 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 GetRowSpan(int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + + return this.cachedMemory.Length > 0 + ? this.cachedMemory.Span.Slice(y * this.Width, this.Width) + : this.GetRowMemorySlow(y).Span; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal ref T GetElementUnsafe(int x, int y) + { + if (this.cachedMemory.Length > 0) + { + Span span = this.cachedMemory.Span; + ref T start = ref MemoryMarshal.GetReference(span); + return ref Unsafe.Add(ref start, (y * this.Width) + x); + } + + return ref this.GetElementSlow(x, y); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// This method is intended for internal use only, since it does not use the indirection provided by + /// . + /// + /// The y (row) coordinate. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetFastRowMemory(int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + return this.cachedMemory.Length > 0 + ? this.cachedMemory.Slice(y * this.Width, this.Width) + : this.GetRowMemorySlow(y); + } + + /// + /// 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.GetBoundedSlice(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 GetSingleSpan() + { + // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup + return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.GetSingleSpanSlow(); + } + + /// + /// 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 GetSingleMemory() + { + // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup + return this.cachedMemory.Length != 0 ? this.cachedMemory : this.GetSingleMemorySlow(); } /// /// 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) + internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) + { + bool swap = MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); + SwapOwnData(destination, source, swap); + } + + [MethodImpl(InliningOptions.ColdPath)] + private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); + + [MethodImpl(InliningOptions.ColdPath)] + private Memory GetSingleMemorySlow() => this.FastMemoryGroup.Single(); + + [MethodImpl(InliningOptions.ColdPath)] + private Span GetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; + + [MethodImpl(InliningOptions.ColdPath)] + private ref T GetElementSlow(int x, int y) { - MemorySource.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource); - SwapDimensionData(destination, source); + Span span = this.GetRowMemorySlow(y).Span; + return ref span[x]; } - private static void SwapDimensionData(Buffer2D a, Buffer2D b) + private static void SwapOwnData(Buffer2D a, Buffer2D b, bool swapCachedMemory) { Size aSize = a.Size(); Size bSize = b.Size(); @@ -96,6 +225,13 @@ namespace SixLabors.ImageSharp.Memory a.Width = bSize.Width; a.Height = bSize.Height; + + if (swapCachedMemory) + { + Memory aCached = a.cachedMemory; + a.cachedMemory = b.cachedMemory; + b.cachedMemory = aCached; + } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs deleted file mode 100644 index f71a281390..0000000000 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ /dev/null @@ -1,164 +0,0 @@ -// 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/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs new file mode 100644 index 0000000000..e00775cb36 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +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 new file mode 100644 index 0000000000..950cecf350 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 new file mode 100644 index 0000000000..da42b30ad8 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -0,0 +1,238 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal static class MemoryGroupExtensions + { + internal static void Fill(this IMemoryGroup group, T value) + where T : struct + { + foreach (Memory memory in group) + { + memory.Span.Fill(value); + } + } + + 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. + /// Otherwise is thrown. + /// + internal static Memory GetBoundedSlice(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)(start / group.BufferLength); + + if (bufferIdx < 0) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + + if (bufferIdx >= group.Count) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + + int bufferStart = (int)(start % group.BufferLength); + 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)); + + var cur = new MemoryGroupCursor(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.Slice(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)); + + var cur = new MemoryGroupCursor(target); + + while (!source.IsEmpty) + { + int fwd = Math.Min(cur.LookAhead(), source.Length); + source.Slice(0, fwd).CopyTo(cur.GetSpan(fwd)); + cur.Forward(fwd); + source = source.Slice(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; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(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; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(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/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs new file mode 100644 index 0000000000..2fd424d328 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -0,0 +1,145 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +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; + } + } + + 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; + } + + 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 new file mode 100644 index 0000000000..cc2a2f17c9 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +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 new file mode 100644 index 0000000000..35290c109e --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +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; + + public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) + : base(bufferLength, totalLength) + { + this.memoryOwners = memoryOwners; + this.Swappable = swappable; + this.View = new MemoryGroupView(this); + } + + 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; + } + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override MemoryGroupEnumerator GetEnumerator() + { + return new MemoryGroupEnumerator(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(); + + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + memoryOwner.Dispose(); + } + + this.memoryOwners = null; + this.IsValid = false; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void EnsureNotDisposed() + { + if (this.memoryOwners is null) + { + ThrowObjectDisposedException(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException(nameof(MemoryGroup)); + } + + internal static void SwapContents(Owned a, Owned b) + { + a.EnsureNotDisposed(); + b.EnsureNotDisposed(); + + IMemoryOwner[] tempOwners = a.memoryOwners; + long tempTotalLength = a.TotalLength; + int tempBufferLength = a.BufferLength; + + a.memoryOwners = b.memoryOwners; + a.TotalLength = b.TotalLength; + a.BufferLength = b.BufferLength; + + b.memoryOwners = tempOwners; + b.TotalLength = tempTotalLength; + b.BufferLength = tempBufferLength; + + a.View.Invalidate(); + b.View.Invalidate(); + a.View = new MemoryGroupView(a); + b.View = new MemoryGroupView(b); + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs new file mode 100644 index 0000000000..451a8f7e39 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -0,0 +1,200 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +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 MemoryGroup(int bufferLength, long totalLength) + { + this.BufferLength = bufferLength; + this.TotalLength = totalLength; + } + + /// + public abstract int Count { get; } + + /// + public int BufferLength { get; private set; } + + /// + public long TotalLength { get; private set; } + + /// + public bool IsValid { get; private set; } = true; + + public MemoryGroupView View { get; private set; } + + /// + 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 totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + { + Guard.NotNull(allocator, nameof(allocator)); + Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment)); + + int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; + + if (bufferAlignment > blockCapacityInElements) + { + throw new InvalidMemoryOperationException( + $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}."); + } + + if (totalLength == 0) + { + var buffers0 = new IMemoryOwner[1] { allocator.Allocate(0, options) }; + return new Owned(buffers0, 0, 0, true); + } + + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment; + int bufferLength = numberOfAlignedSegments * bufferAlignment; + if (totalLength > 0 && totalLength < bufferLength) + { + bufferLength = (int)totalLength; + } + + int sizeOfLastBuffer = (int)(totalLength % bufferLength); + long bufferCount = totalLength / bufferLength; + + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferLength; + } + else + { + bufferCount++; + } + + var buffers = new IMemoryOwner[bufferCount]; + for (int i = 0; i < buffers.Length - 1; i++) + { + buffers[i] = allocator.Allocate(bufferLength, options); + } + + if (bufferCount > 0) + { + buffers[buffers.Length - 1] = allocator.Allocate(sizeOfLastBuffer, options); + } + + return new Owned(buffers, bufferLength, totalLength, 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[source.Length - 1].Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 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[source.Length - 1].Memory.Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Memory.Length : 0; + + return new Owned(source, bufferLength, totalLength, false); + } + + /// + /// Swaps the contents of 'target' with 'source' if the buffers are allocated (1), + /// copies the contents of 'source' to 'target' otherwise (2). + /// Groups should be of same TotalLength in case 2. + /// + public static bool SwapOrCopyContent(MemoryGroup target, MemoryGroup source) + { + if (source is Owned ownedSrc && ownedSrc.Swappable && + target is Owned ownedTarget && ownedTarget.Swappable) + { + Owned.SwapContents(ownedTarget, ownedSrc); + return true; + } + else + { + if (target.TotalLength != source.TotalLength) + { + throw new InvalidMemoryOperationException( + "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); + } + + source.CopyTo(target); + return false; + } + } + } +} diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs new file mode 100644 index 0000000000..92b1d8d359 --- /dev/null +++ b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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() + { + } + } +} diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index b596351b5f..9a56390d89 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -1,18 +1,27 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Buffers; - -using SixLabors.Memory; -using SixLabors.Primitives; +using System.IO; +using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Memory { /// /// Extension methods for . /// - internal static class MemoryAllocatorExtensions + public static class MemoryAllocatorExtensions { + /// + /// 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, @@ -20,12 +29,20 @@ namespace SixLabors.ImageSharp.Memory AllocationOptions options = AllocationOptions.None) where T : struct { - IMemoryOwner buffer = memoryAllocator.Allocate(width * height, options); - var memorySource = new MemorySource(buffer, true); - - return new Buffer2D(memorySource, width, height); + long groupLength = (long)width * height; + MemoryGroup memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + return new Buffer2D(memoryGroup, width, height); } + /// + /// 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, @@ -33,15 +50,31 @@ namespace SixLabors.ImageSharp.Memory where T : struct => Allocate2D(memoryAllocator, size.Width, size.Height, 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 for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) + /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea). /// - /// The + /// The . /// Pixel count in the row - /// The pixel size in bytes, eg. 3 for RGB - /// The padding - /// A - public static IManagedByteBuffer AllocatePaddedPixelRowBuffer( + /// The pixel size in bytes, eg. 3 for RGB. + /// The padding. + /// A . + internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer( this MemoryAllocator memoryAllocator, int width, int pixelSizeInBytes, @@ -50,5 +83,25 @@ namespace SixLabors.ImageSharp.Memory int length = (width * pixelSizeInBytes) + paddingInBytes; return memoryAllocator.AllocateManagedByteBuffer(length); } + + /// + /// Allocates a . + /// + /// 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. + internal static MemoryGroup AllocateGroup( + this MemoryAllocator memoryAllocator, + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + where T : struct + => MemoryGroup.Allocate(memoryAllocator, totalLength, bufferAlignment, options); + + internal static MemoryStream AllocateFixedCapacityMemoryStream(this MemoryAllocator allocator, long length) => + new FixedCapacityPooledMemoryStream(length, allocator); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs index 9b68f52c4d..98fd40e65b 100644 --- a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs +++ b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,7 +13,6 @@ namespace SixLabors.ImageSharp.Memory /// internal static class MemoryOwnerExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetSpan(this IMemoryOwner buffer) => buffer.Memory.Span; @@ -22,7 +21,7 @@ namespace SixLabors.ImageSharp.Memory => buffer.GetSpan().Length; /// - /// Gets a to an offseted position inside the buffer. + /// Gets a to an offsetted position inside the buffer. /// /// The buffer /// The start @@ -60,4 +59,4 @@ namespace SixLabors.ImageSharp.Memory 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 deleted file mode 100644 index f0b0ab0281..0000000000 --- a/src/ImageSharp/Memory/MemorySource.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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 501ed5e69c..437760f0ea 100644 --- a/src/ImageSharp/Memory/RowInterval.cs +++ b/src/ImageSharp/Memory/RowInterval.cs @@ -1,45 +1,88 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; +using System; namespace SixLabors.ImageSharp.Memory { /// /// Represents an interval of rows in a and/or /// - internal readonly struct RowInterval + /// + /// Before RC1, this class might be target of API changes, use it on your own risk! + /// + public readonly struct RowInterval : IEquatable { /// /// Initializes a new instance of the struct. /// + /// The inclusive minimum row. + /// The exclusive maximum row. public RowInterval(int min, int max) { - DebugGuard.MustBeLessThan(min, max, nameof(min)); + Guard.MustBeLessThan(min, max, nameof(min)); this.Min = min; this.Max = max; } /// - /// Gets the INCLUSIVE minimum + /// Gets the inclusive minimum row. /// public int Min { get; } /// - /// Gets the EXCLUSIVE maximum + /// Gets the exclusive maximum row. /// public int Max { get; } /// - /// Gets the difference ( - ) + /// 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); + } + + /// + /// 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 override string ToString() + public bool Equals(RowInterval other) { - return $"RowInterval [{this.Min}->{this.Max}["; + return this.Min == other.Min && this.Max == other.Max; } + + /// + public override bool Equals(object obj) + { + return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Min, this.Max); + + /// + public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]"; + + internal RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max); + + internal RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs new file mode 100644 index 0000000000..722dde19a4 --- /dev/null +++ b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); +} diff --git a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs new file mode 100644 index 0000000000..3a2356f181 --- /dev/null +++ b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal delegate void TransformItemsInplaceDelegate(Span data); +} diff --git a/src/ImageSharp/MetaData/FrameDecodingMode.cs b/src/ImageSharp/MetaData/FrameDecodingMode.cs deleted file mode 100644 index 2863fbf8f9..0000000000 --- a/src/ImageSharp/MetaData/FrameDecodingMode.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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 deleted file mode 100644 index f1f884be68..0000000000 --- a/src/ImageSharp/MetaData/ImageFrameMetaData.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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 deleted file mode 100644 index 73549d98aa..0000000000 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ /dev/null @@ -1,187 +0,0 @@ -// 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 meta data. - /// - internal void SyncProfiles() => this.ExifProfile?.Sync(this); - } -} diff --git a/src/ImageSharp/MetaData/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs deleted file mode 100644 index 24a3686de2..0000000000 --- a/src/ImageSharp/MetaData/ImageProperty.cs +++ /dev/null @@ -1,127 +0,0 @@ -// 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() - { - return $"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 deleted file mode 100644 index ce848004f7..0000000000 --- a/src/ImageSharp/MetaData/PixelResolutionUnit.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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 deleted file mode 100644 index fb2a893d1c..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index 5bd38b195d..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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 deleted file mode 100644 index b1b42ad433..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 deleted file mode 100644 index 37ceaf10f6..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ /dev/null @@ -1,304 +0,0 @@ -// 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 meta data. - /// - /// The meta data. - 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 deleted file mode 100644 index 8ec9eea275..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ /dev/null @@ -1,572 +0,0 @@ -// 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.IO; -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 Endianness endianness = Endianness.BigEndian; - 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 - if (this.ReadUInt16() == 0x4949) - { - this.endianness = Endianness.LittleEndian; - } - - 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 - { - 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.endianness == Endianness.BigEndian - ? 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.endianness == Endianness.BigEndian - ? BinaryPrimitives.ReadUInt32BigEndian(buffer) - : BinaryPrimitives.ReadUInt32LittleEndian(buffer); - } - - private ushort ConvertToShort(ReadOnlySpan buffer) - { - if (buffer.Length < 2) - { - return default; - } - - return this.endianness == Endianness.BigEndian - ? BinaryPrimitives.ReadUInt16BigEndian(buffer) - : BinaryPrimitives.ReadUInt16LittleEndian(buffer); - } - - private float ConvertToSingle(ReadOnlySpan buffer) - { - if (buffer.Length < 4) - { - return default; - } - - int intValue = this.endianness == Endianness.BigEndian - ? 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.endianness == Endianness.BigEndian - ? 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.endianness == Endianness.BigEndian - ? BinaryPrimitives.ReadInt16BigEndian(buffer) - : BinaryPrimitives.ReadInt16LittleEndian(buffer); - } - - private class EnumHelper - where TEnum : struct - { - 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 deleted file mode 100644 index 625f95b63d..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs +++ /dev/null @@ -1,1544 +0,0 @@ -// 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 deleted file mode 100644 index 286fdbe57f..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs +++ /dev/null @@ -1,59 +0,0 @@ -// 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 deleted file mode 100644 index e999d00b86..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs +++ /dev/null @@ -1,281 +0,0 @@ -// 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 deleted file mode 100644 index 409c55253a..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs +++ /dev/null @@ -1,721 +0,0 @@ -// 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 deleted file mode 100644 index 9079526d5a..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs +++ /dev/null @@ -1,376 +0,0 @@ -// 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 deleted file mode 100644 index b6e27b70c5..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/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 deleted file mode 100644 index 516887bcd6..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 deleted file mode 100644 index d168c5c285..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs +++ /dev/null @@ -1,93 +0,0 @@ -// 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 deleted file mode 100644 index 7076e51447..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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 deleted file mode 100644 index efc2ca5377..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs +++ /dev/null @@ -1,168 +0,0 @@ -// 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 deleted file mode 100644 index de08485ac8..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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 deleted file mode 100644 index d9badf5a80..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 deleted file mode 100644 index b27083dc49..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs +++ /dev/null @@ -1,219 +0,0 @@ -// 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 deleted file mode 100644 index f9d56b0fff..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs +++ /dev/null @@ -1,171 +0,0 @@ -// 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 deleted file mode 100644 index 495d73a784..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 deleted file mode 100644 index 2b39b1d6c6..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs +++ /dev/null @@ -1,85 +0,0 @@ -// 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 deleted file mode 100644 index 7dc8cf98aa..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs +++ /dev/null @@ -1,181 +0,0 @@ -// 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 deleted file mode 100644 index bb85a5ca3e..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs +++ /dev/null @@ -1,173 +0,0 @@ -// 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 deleted file mode 100644 index c572b7f210..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ /dev/null @@ -1,905 +0,0 @@ -// 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 deleted file mode 100644 index 91a28fd743..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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 deleted file mode 100644 index d5f5d5a9b6..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs +++ /dev/null @@ -1,177 +0,0 @@ -// 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 deleted file mode 100644 index 38a3dd457d..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs +++ /dev/null @@ -1,126 +0,0 @@ -// 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 deleted file mode 100644 index 79b9132192..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs +++ /dev/null @@ -1,159 +0,0 @@ -// 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 deleted file mode 100644 index 48c6734aec..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs +++ /dev/null @@ -1,78 +0,0 @@ -// 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 deleted file mode 100644 index 1a3c2c0ac5..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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 deleted file mode 100644 index 404285b500..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs +++ /dev/null @@ -1,246 +0,0 @@ -// 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 deleted file mode 100644 index 51ea2d4e4a..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs +++ /dev/null @@ -1,1029 +0,0 @@ -// 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 deleted file mode 100644 index 21b7b6421b..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs +++ /dev/null @@ -1,247 +0,0 @@ -// 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 deleted file mode 100644 index fe0c9b8b59..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 deleted file mode 100644 index 0913cda418..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs +++ /dev/null @@ -1,136 +0,0 @@ -// 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 deleted file mode 100644 index bec71e0dbc..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 deleted file mode 100644 index cfd2671e94..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs +++ /dev/null @@ -1,60 +0,0 @@ -// 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 deleted file mode 100644 index e9b8cc26b8..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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 deleted file mode 100644 index b13edb53f4..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs +++ /dev/null @@ -1,84 +0,0 @@ -// 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 deleted file mode 100644 index 88cca866dc..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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 deleted file mode 100644 index 2a375b0d1c..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 deleted file mode 100644 index fe6575c41b..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 deleted file mode 100644 index fe5d309228..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 deleted file mode 100644 index 374b4ad93b..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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 deleted file mode 100644 index 1563078f72..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 deleted file mode 100644 index 8f0427db27..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 deleted file mode 100644 index 1e9ec18e89..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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 deleted file mode 100644 index 61d34dca10..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs +++ /dev/null @@ -1,362 +0,0 @@ -// 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 deleted file mode 100644 index 7cb9c00f39..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 deleted file mode 100644 index 839ab8c6b1..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 deleted file mode 100644 index f1d73c6268..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 deleted file mode 100644 index 4773ab6c2a..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs +++ /dev/null @@ -1,176 +0,0 @@ -// 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 deleted file mode 100644 index fe0c406576..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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 deleted file mode 100644 index a393c258b2..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 deleted file mode 100644 index ad0db4df93..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs +++ /dev/null @@ -1,271 +0,0 @@ -// 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 deleted file mode 100644 index f690670419..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 deleted file mode 100644 index 5d75a6df9d..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ /dev/null @@ -1,219 +0,0 @@ -// 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 deleted file mode 100644 index 189b40275a..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs +++ /dev/null @@ -1,101 +0,0 @@ -// 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 deleted file mode 100644 index 9f9d373ae1..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs +++ /dev/null @@ -1,150 +0,0 @@ -// 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 deleted file mode 100644 index 4c6bb07858..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 deleted file mode 100644 index 91a3bba549..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs +++ /dev/null @@ -1,91 +0,0 @@ -// 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 deleted file mode 100644 index 09931a1f91..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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 deleted file mode 100644 index 6aba186326..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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 deleted file mode 100644 index 7585fc2128..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 deleted file mode 100644 index 7e0e1eda82..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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 deleted file mode 100644 index e6170f7680..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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 deleted file mode 100644 index db2d56cc3d..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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 deleted file mode 100644 index e9a812d8ca..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs +++ /dev/null @@ -1,174 +0,0 @@ -// 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 deleted file mode 100644 index b5f8fd5c49..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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 deleted file mode 100644 index 9f2b4c90ad..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs +++ /dev/null @@ -1,78 +0,0 @@ -// 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 deleted file mode 100644 index 4d393dfb33..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs +++ /dev/null @@ -1,133 +0,0 @@ -// 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 deleted file mode 100644 index 0d34d3ceba..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs +++ /dev/null @@ -1,121 +0,0 @@ -// 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 deleted file mode 100644 index 0b8367303d..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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 deleted file mode 100644 index 104d243099..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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 deleted file mode 100644 index 0a114ea1f9..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 deleted file mode 100644 index 415f6941ec..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs +++ /dev/null @@ -1,177 +0,0 @@ -// 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 deleted file mode 100644 index 04a49e316d..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs +++ /dev/null @@ -1,180 +0,0 @@ -// 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 deleted file mode 100644 index f7c0946320..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ /dev/null @@ -1,310 +0,0 @@ -// 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 deleted file mode 100644 index 27572acd0b..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs +++ /dev/null @@ -1,310 +0,0 @@ -// 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 deleted file mode 100644 index 9247ca593f..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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 deleted file mode 100644 index 1a06eb5880..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs +++ /dev/null @@ -1,71 +0,0 @@ -// 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 deleted file mode 100644 index f13fdb17fb..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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 deleted file mode 100644 index 957b8d5c3f..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs +++ /dev/null @@ -1,167 +0,0 @@ -// 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 deleted file mode 100644 index 9ec497d3be..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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 deleted file mode 100644 index ff69115267..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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 deleted file mode 100644 index c6cc0903c5..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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 deleted file mode 100644 index 494aa2888b..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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 deleted file mode 100644 index a073291ad1..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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 deleted file mode 100644 index 287f0efb0c..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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 deleted file mode 100644 index 8e6f4bc196..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs +++ /dev/null @@ -1,178 +0,0 @@ -// 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 deleted file mode 100644 index ab3b3fb6fb..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 deleted file mode 100644 index 464cbb9e75..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 deleted file mode 100644 index 1e7a7f8a90..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 deleted file mode 100644 index affdc8720b..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 deleted file mode 100644 index 36d48d6135..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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 deleted file mode 100644 index b2f2677575..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 deleted file mode 100644 index 510930e397..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 deleted file mode 100644 index a0089e7359..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 deleted file mode 100644 index bd636d4f2c..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 deleted file mode 100644 index c1c14d8cbb..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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 deleted file mode 100644 index 3f7cad3afe..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs +++ /dev/null @@ -1,190 +0,0 @@ -// 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 deleted file mode 100644 index 8f273dd603..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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() - { - return $"{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 00ededca4d..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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() - { - return $"{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 deleted file mode 100644 index c46d6884b6..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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 deleted file mode 100644 index b7cb5bc495..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs +++ /dev/null @@ -1,102 +0,0 @@ -// 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 deleted file mode 100644 index 745312f563..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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 deleted file mode 100644 index 7e7c527ead..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 deleted file mode 100644 index f64d5409ae..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs +++ /dev/null @@ -1,122 +0,0 @@ -// 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) - { - return 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() - { - return $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}"; - } - - private static string ToHex(uint value) - { - return 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 deleted file mode 100644 index ae451c5db8..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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 deleted file mode 100644 index 8cae869925..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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 deleted file mode 100644 index e8885a66bf..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs +++ /dev/null @@ -1,97 +0,0 @@ -// 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() - { - return $"{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 deleted file mode 100644 index d93e068c52..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs +++ /dev/null @@ -1,95 +0,0 @@ -// 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) - { - return 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() - { - return HashCode.Combine(this.Signature, this.Offset, this.DataSize); - } - - /// - public override string ToString() - { - return $"{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 2486cc80a9..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccVersion.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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/FrameDecodingMode.cs b/src/ImageSharp/Metadata/FrameDecodingMode.cs new file mode 100644 index 0000000000..e03af18bdc --- /dev/null +++ b/src/ImageSharp/Metadata/FrameDecodingMode.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// 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..2021f1249b --- /dev/null +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// 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..425fd9b47f --- /dev/null +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -0,0 +1,161 @@ +// Copyright (c) Six Labors. +// 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; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; + +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()); + } + + this.ExifProfile = other.ExifProfile?.DeepClone(); + this.IccProfile = other.IccProfile?.DeepClone(); + this.IptcProfile = other.IptcProfile?.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 or sets the iptc profile. + /// + public IptcProfile IptcProfile { get; set; } + + /// + /// 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); + + /// + /// Synchronizes the profiles with the current metadata. + /// + internal void SyncProfiles() => this.ExifProfile?.Sync(this); + } +} diff --git a/src/ImageSharp/Metadata/PixelResolutionUnit.cs b/src/ImageSharp/Metadata/PixelResolutionUnit.cs new file mode 100644 index 0000000000..84d15ed4ce --- /dev/null +++ b/src/ImageSharp/Metadata/PixelResolutionUnit.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// 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/DC-008-Translation-2019-E.pdf b/src/ImageSharp/Metadata/Profiles/Exif/DC-008-Translation-2019-E.pdf new file mode 100644 index 0000000000..9be0c8402b Binary files /dev/null and b/src/ImageSharp/Metadata/Profiles/Exif/DC-008-Translation-2019-E.pdf differ diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs new file mode 100644 index 0000000000..0c81f14dd4 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifConstants + { + public static ReadOnlySpan LittleEndianByteOrderMarker => new byte[] + { + (byte)'I', + (byte)'I', + 0x2A, + 0x00, + }; + + public static ReadOnlySpan BigEndianByteOrderMarker => new byte[] + { + (byte)'M', + (byte)'M', + 0x00, + 0x2A + }; + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs new file mode 100644 index 0000000000..13e67554c5 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// 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. + /// + /// 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 + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs new file mode 100644 index 0000000000..4f75999bbf --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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: + return 4; + case ExifDataType.DoubleFloat: + case ExifDataType.Rational: + case ExifDataType.SignedRational: + return 8; + default: + throw new NotSupportedException(dataType.ToString()); + } + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs new file mode 100644 index 0000000000..dc12f3819b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// 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 + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs new file mode 100644 index 0000000000..55af45fb46 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -0,0 +1,304 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using SixLabors.ImageSharp.PixelFormats; + +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. + /// 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) + : (IReadOnlyList)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. + /// + 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 : unmanaged, 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 value with the specified tag. + /// The data type of the tag. + public IExifValue GetValue(ExifTag tag) + { + IExifValue value = this.GetValueInternal(tag); + return value is null ? null : (IExifValue)value; + } + + /// + /// 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. + /// 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 Array.Empty(); + } + + var writer = new ExifWriter(this.values, this.Parts); + return writer.GetData(); + } + + /// + public ExifProfile DeepClone() => new ExifProfile(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. + 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); + if (newExifValue is null) + { + throw new NotSupportedException(); + } + + 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); + } + + private void SyncResolution(ExifTag tag, double resolution) + { + IExifValue 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; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs new file mode 100644 index 0000000000..749c691865 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -0,0 +1,517 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Runtime.CompilerServices; +using System.Text; + +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)ExifDataTypes.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 (IExifValue val in values) + { + if (val == value) + { + duplicate = true; + break; + } + } + + if (duplicate) + { + continue; + } + + if (value == ExifTag.SubIFDOffset) + { + this.exifOffset = ((ExifLong)value).Value; + } + else if (value == ExifTag.GPSIFDOffset) + { + this.gpsOffset = ((ExifLong)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) + { + exifValue = default; + + // 2 | 2 | 4 | 4 + // tag | type | count | value offset + if (this.RemainingLength < 12) + { + return false; + } + + var tag = (ExifTagValue)this.ReadUInt16(); + ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); + + // Ensure that the data type is valid + if (dataType == ExifDataType.Unknown) + { + return false; + } + + 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 * ExifDataTypes.GetSize(dataType); + + this.TryReadSpan(4, out ReadOnlySpan offsetBuffer); + + object value; + 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(new UnkownExifTag(tag)); + return false; + } + + this.position = (int)newIndex; + + if (this.RemainingLength < size) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + + this.position = oldIndex; + 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 = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); + + if (exifValue is null) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return false; + } + + if (!exifValue.TrySetValue(value)) + { + return false; + } + + return true; + } + + private void AddInvalidTag(ExifTag tag) + => (this.invalidTags ?? (this.invalidTags = new List())).Add(tag); + + 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 void GetThumbnail(uint offset) + { + var values = new List(); + this.AddValues(values, offset); + + foreach (ExifValue value in values) + { + if (value == ExifTag.JPEGInterchangeFormat) + { + this.ThumbnailOffset = ((ExifLong)value).Value; + } + else if (value == ExifTag.JPEGInterchangeFormatLength) + { + this.ThumbnailLength = ((ExifLong)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); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs new file mode 100644 index 0000000000..b8a3d4f23d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// 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 + { + /// + /// 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 . + /// + public static string GetDescription(ExifTag tag, object value) + { + var tagValue = (ExifTagValue)(ushort)tag; + FieldInfo field = tagValue.GetType().GetTypeInfo().GetDeclaredField(tagValue.ToString()); + + if (field is null) + { + return null; + } + + foreach (CustomAttributeData customAttribute in field.CustomAttributes) + { + object attributeValue = customAttribute.ConstructorArguments[0].Value; + + if (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..3f73035666 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs @@ -0,0 +1,275 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs new file mode 100644 index 0000000000..a240c13925 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -0,0 +1,423 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; + +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; + uint length; + + IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset); + IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset); + + if (this.ifdValues.Count == 0 && this.exifValues.Count == 0 && this.gpsValues.Count == 0) + { + return Array.Empty(); + } + + uint ifdLength = this.GetLength(this.ifdValues) + 4U; + uint exifLength = this.GetLength(this.exifValues); + uint gpsLength = this.GetLength(this.gpsValues); + + length = ifdLength + exifLength + gpsLength; + + if (length == 4U) + { + return Array.Empty(); + } + + // 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; + + length += 4 + 2; + + var 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; + uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; + + exifOffset?.TrySetValue(ifdOffset + ifdLength); + gpsOffset?.TrySetValue(ifdOffset + ifdLength + exifLength); + + i = WriteUInt32(ifdOffset, result, i); + i = this.WriteHeaders(this.ifdValues, result, i); + i = WriteUInt32(thumbnailOffset, 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); + i = this.WriteData(startIndex, this.gpsValues, result, i); + } + + WriteUInt16(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 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); + ifdValues.Add(result); + + return result; + } + else if (index != -1) + { + ifdValues.RemoveAt(index); + } + + return null; + } + + private List GetPartValues(ExifParts part) + { + var result = new List(); + + 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) + { + string stringValue = (string)value; + return stringValue.Length > 0; + } + + if (value is Array arrayValue) + { + return arrayValue.Length > 0; + } + + return true; + } + + private uint GetLength(IList values) + { + if (values.Count == 0) + { + return 0; + } + + uint length = 2; + + foreach (IExifValue value in values) + { + uint valueLength = GetLength(value); + + length += 2 + 2 + 4 + 4; + + if (valueLength > 4) + { + length += valueLength; + } + } + + return length; + } + + private static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType); + + private static uint GetNumberOfComponents(IExifValue exifValue) + { + object value = exifValue.GetValue(); + + if (exifValue.DataType == ExifDataType.Ascii) + { + return (uint)Encoding.UTF8.GetBytes((string)value).Length + 1; + } + + if (value is Array arrayValue) + { + return (uint)arrayValue.Length; + } + + return 1; + } + + private int WriteArray(IExifValue value, Span destination, int offset) + { + if (value.DataType == ExifDataType.Ascii) + { + return this.WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset); + } + + int newOffset = offset; + foreach (object obj in (Array)value.GetValue()) + { + newOffset = this.WriteValue(value.DataType, obj, destination, newOffset); + } + + return newOffset; + } + + private int WriteData(uint startIndex, List values, Span destination, int offset) + { + if (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 = this.WriteValue(value, destination, newOffset); + } + } + + return newOffset; + } + + private int WriteHeaders(List values, Span destination, int offset) + { + this.dataOffsets = new List(); + + 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 + { + 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: + offset = Write(Encoding.UTF8.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.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(IExifValue value, Span destination, int offset) + { + if (value.IsArray && value.DataType != ExifDataType.Ascii) + { + return this.WriteArray(value, destination, offset); + } + + return this.WriteValue(value.DataType, value.GetValue(), destination, offset); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/README.md b/src/ImageSharp/Metadata/Profiles/Exif/README.md new file mode 100644 index 0000000000..7901527e1a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/README.md @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..110abafddd --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the FaxProfile exif tag. + /// + public static ExifTag FaxProfile { get; } = new ExifTag(ExifTagValue.FaxProfile); + + /// + /// Gets the ModeNumber exif tag. + /// + public static ExifTag ModeNumber { get; } = new ExifTag(ExifTagValue.ModeNumber); + + /// + /// Gets the GPSAltitudeRef exif tag. + /// + public static ExifTag GPSAltitudeRef { get; } = new ExifTag(ExifTagValue.GPSAltitudeRef); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs new file mode 100644 index 0000000000..e20867b43e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the ClipPath exif tag. + /// + public static ExifTag ClipPath => new ExifTag(ExifTagValue.ClipPath); + + /// + /// Gets the VersionYear exif tag. + /// + public static ExifTag VersionYear => new ExifTag(ExifTagValue.VersionYear); + + /// + /// Gets the XMP exif tag. + /// + public static ExifTag XMP => new ExifTag(ExifTagValue.XMP); + + /// + /// Gets the CFAPattern2 exif tag. + /// + public static ExifTag CFAPattern2 => new ExifTag(ExifTagValue.CFAPattern2); + + /// + /// Gets the TIFFEPStandardID exif tag. + /// + public static ExifTag TIFFEPStandardID => new ExifTag(ExifTagValue.TIFFEPStandardID); + + /// + /// Gets the XPTitle exif tag. + /// + public static ExifTag XPTitle => new ExifTag(ExifTagValue.XPTitle); + + /// + /// Gets the XPComment exif tag. + /// + public static ExifTag XPComment => new ExifTag(ExifTagValue.XPComment); + + /// + /// Gets the XPAuthor exif tag. + /// + public static ExifTag XPAuthor => new ExifTag(ExifTagValue.XPAuthor); + + /// + /// Gets the XPKeywords exif tag. + /// + public static ExifTag XPKeywords => new ExifTag(ExifTagValue.XPKeywords); + + /// + /// Gets the XPSubject exif tag. + /// + public static ExifTag XPSubject => new ExifTag(ExifTagValue.XPSubject); + + /// + /// Gets the GPSVersionID exif tag. + /// + public static ExifTag GPSVersionID => new ExifTag(ExifTagValue.GPSVersionID); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs new file mode 100644 index 0000000000..2d8a638d0c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the PixelScale exif tag. + /// + public static ExifTag PixelScale { get; } = new ExifTag(ExifTagValue.PixelScale); + + /// + /// Gets the IntergraphMatrix exif tag. + /// + public static ExifTag IntergraphMatrix { get; } = new ExifTag(ExifTagValue.IntergraphMatrix); + + /// + /// Gets the ModelTiePoint exif tag. + /// + public static ExifTag ModelTiePoint { get; } = new ExifTag(ExifTagValue.ModelTiePoint); + + /// + /// Gets the ModelTransform exif tag. + /// + public static ExifTag ModelTransform { get; } = new ExifTag(ExifTagValue.ModelTransform); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs new file mode 100644 index 0000000000..8aae081608 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the SubfileType exif tag. + /// + public static ExifTag SubfileType { get; } = new ExifTag(ExifTagValue.SubfileType); + + /// + /// Gets the SubIFDOffset exif tag. + /// + public static ExifTag SubIFDOffset { get; } = new ExifTag(ExifTagValue.SubIFDOffset); + + /// + /// Gets the GPSIFDOffset exif tag. + /// + public static ExifTag GPSIFDOffset { get; } = new ExifTag(ExifTagValue.GPSIFDOffset); + + /// + /// Gets the T4Options exif tag. + /// + public static ExifTag T4Options { get; } = new ExifTag(ExifTagValue.T4Options); + + /// + /// Gets the T6Options exif tag. + /// + public static ExifTag T6Options { get; } = new ExifTag(ExifTagValue.T6Options); + + /// + /// Gets the XClipPathUnits exif tag. + /// + public static ExifTag XClipPathUnits { get; } = new ExifTag(ExifTagValue.XClipPathUnits); + + /// + /// Gets the YClipPathUnits exif tag. + /// + public static ExifTag YClipPathUnits { get; } = new ExifTag(ExifTagValue.YClipPathUnits); + + /// + /// Gets the ProfileType exif tag. + /// + public static ExifTag ProfileType { get; } = new ExifTag(ExifTagValue.ProfileType); + + /// + /// Gets the CodingMethods exif tag. + /// + public static ExifTag CodingMethods { get; } = new ExifTag(ExifTagValue.CodingMethods); + + /// + /// Gets the T82ptions exif tag. + /// + public static ExifTag T82ptions { get; } = new ExifTag(ExifTagValue.T82ptions); + + /// + /// Gets the JPEGInterchangeFormat exif tag. + /// + public static ExifTag JPEGInterchangeFormat { get; } = new ExifTag(ExifTagValue.JPEGInterchangeFormat); + + /// + /// Gets the JPEGInterchangeFormatLength exif tag. + /// + public static ExifTag JPEGInterchangeFormatLength { get; } = new ExifTag(ExifTagValue.JPEGInterchangeFormatLength); + + /// + /// Gets the MDFileTag exif tag. + /// + public static ExifTag MDFileTag { get; } = new ExifTag(ExifTagValue.MDFileTag); + + /// + /// Gets the StandardOutputSensitivity exif tag. + /// + public static ExifTag StandardOutputSensitivity { get; } = new ExifTag(ExifTagValue.StandardOutputSensitivity); + + /// + /// Gets the RecommendedExposureIndex exif tag. + /// + public static ExifTag RecommendedExposureIndex { get; } = new ExifTag(ExifTagValue.RecommendedExposureIndex); + + /// + /// Gets the ISOSpeed exif tag. + /// + public static ExifTag ISOSpeed { get; } = new ExifTag(ExifTagValue.ISOSpeed); + + /// + /// Gets the ISOSpeedLatitudeyyy exif tag. + /// + public static ExifTag ISOSpeedLatitudeyyy { get; } = new ExifTag(ExifTagValue.ISOSpeedLatitudeyyy); + + /// + /// Gets the ISOSpeedLatitudezzz exif tag. + /// + public static ExifTag ISOSpeedLatitudezzz { get; } = new ExifTag(ExifTagValue.ISOSpeedLatitudezzz); + + /// + /// Gets the FaxRecvParams exif tag. + /// + public static ExifTag FaxRecvParams { get; } = new ExifTag(ExifTagValue.FaxRecvParams); + + /// + /// Gets the FaxRecvTime exif tag. + /// + public static ExifTag FaxRecvTime { get; } = new ExifTag(ExifTagValue.FaxRecvTime); + + /// + /// Gets the ImageNumber exif tag. + /// + public static ExifTag ImageNumber { get; } = new ExifTag(ExifTagValue.ImageNumber); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs new file mode 100644 index 0000000000..ac4b0a1bf8 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the FreeOffsets exif tag. + /// + public static ExifTag FreeOffsets { get; } = new ExifTag(ExifTagValue.FreeOffsets); + + /// + /// Gets the FreeByteCounts exif tag. + /// + public static ExifTag FreeByteCounts { get; } = new ExifTag(ExifTagValue.FreeByteCounts); + + /// + /// Gets the ColorResponseUnit exif tag. + /// + public static ExifTag ColorResponseUnit { get; } = new ExifTag(ExifTagValue.ColorResponseUnit); + + /// + /// Gets the TileOffsets exif tag. + /// + public static ExifTag TileOffsets { get; } = new ExifTag(ExifTagValue.TileOffsets); + + /// + /// Gets the SMinSampleValue exif tag. + /// + public static ExifTag SMinSampleValue { get; } = new ExifTag(ExifTagValue.SMinSampleValue); + + /// + /// Gets the SMaxSampleValue exif tag. + /// + public static ExifTag SMaxSampleValue { get; } = new ExifTag(ExifTagValue.SMaxSampleValue); + + /// + /// Gets the JPEGQTables exif tag. + /// + public static ExifTag JPEGQTables { get; } = new ExifTag(ExifTagValue.JPEGQTables); + + /// + /// Gets the JPEGDCTables exif tag. + /// + public static ExifTag JPEGDCTables { get; } = new ExifTag(ExifTagValue.JPEGDCTables); + + /// + /// Gets the JPEGACTables exif tag. + /// + public static ExifTag JPEGACTables { get; } = new ExifTag(ExifTagValue.JPEGACTables); + + /// + /// Gets the StripRowCounts exif tag. + /// + public static ExifTag StripRowCounts { get; } = new ExifTag(ExifTagValue.StripRowCounts); + + /// + /// Gets the IntergraphRegisters exif tag. + /// + public static ExifTag IntergraphRegisters { get; } = new ExifTag(ExifTagValue.IntergraphRegisters); + + /// + /// Gets the TimeZoneOffset exif tag. + /// + public static ExifTag TimeZoneOffset { get; } = new ExifTag(ExifTagValue.TimeZoneOffset); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs new file mode 100644 index 0000000000..7e73b75aad --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the ImageWidth exif tag. + /// + public static ExifTag ImageWidth { get; } = new ExifTag(ExifTagValue.ImageWidth); + + /// + /// Gets the ImageLength exif tag. + /// + public static ExifTag ImageLength { get; } = new ExifTag(ExifTagValue.ImageLength); + + /// + /// Gets the TileWidth exif tag. + /// + public static ExifTag TileWidth { get; } = new ExifTag(ExifTagValue.TileWidth); + + /// + /// Gets the TileLength exif tag. + /// + public static ExifTag TileLength { get; } = new ExifTag(ExifTagValue.TileLength); + + /// + /// Gets the BadFaxLines exif tag. + /// + public static ExifTag BadFaxLines { get; } = new ExifTag(ExifTagValue.BadFaxLines); + + /// + /// Gets the ConsecutiveBadFaxLines exif tag. + /// + public static ExifTag ConsecutiveBadFaxLines { get; } = new ExifTag(ExifTagValue.ConsecutiveBadFaxLines); + + /// + /// Gets the PixelXDimension exif tag. + /// + public static ExifTag PixelXDimension { get; } = new ExifTag(ExifTagValue.PixelXDimension); + + /// + /// Gets the PixelYDimension exif tag. + /// + public static ExifTag PixelYDimension { get; } = new ExifTag(ExifTagValue.PixelYDimension); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs new file mode 100644 index 0000000000..e9440119ef --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the StripOffsets exif tag. + /// + public static ExifTag StripOffsets { get; } = new ExifTag(ExifTagValue.StripOffsets); + + /// + /// Gets the TileByteCounts exif tag. + /// + public static ExifTag TileByteCounts { get; } = new ExifTag(ExifTagValue.TileByteCounts); + + /// + /// Gets the ImageLayer exif tag. + /// + public static ExifTag ImageLayer { get; } = new ExifTag(ExifTagValue.ImageLayer); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs new file mode 100644 index 0000000000..fb16a0cc34 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the XPosition exif tag. + /// + public static ExifTag XPosition { get; } = new ExifTag(ExifTagValue.XPosition); + + /// + /// Gets the YPosition exif tag. + /// + public static ExifTag YPosition { get; } = new ExifTag(ExifTagValue.YPosition); + + /// + /// Gets the XResolution exif tag. + /// + public static ExifTag XResolution { get; } = new ExifTag(ExifTagValue.XResolution); + + /// + /// Gets the YResolution exif tag. + /// + public static ExifTag YResolution { get; } = new ExifTag(ExifTagValue.YResolution); + + /// + /// Gets the BatteryLevel exif tag. + /// + public static ExifTag BatteryLevel { get; } = new ExifTag(ExifTagValue.BatteryLevel); + + /// + /// Gets the ExposureTime exif tag. + /// + public static ExifTag ExposureTime { get; } = new ExifTag(ExifTagValue.ExposureTime); + + /// + /// Gets the FNumber exif tag. + /// + public static ExifTag FNumber { get; } = new ExifTag(ExifTagValue.FNumber); + + /// + /// Gets the MDScalePixel exif tag. + /// + public static ExifTag MDScalePixel { get; } = new ExifTag(ExifTagValue.MDScalePixel); + + /// + /// Gets the CompressedBitsPerPixel exif tag. + /// + public static ExifTag CompressedBitsPerPixel { get; } = new ExifTag(ExifTagValue.CompressedBitsPerPixel); + + /// + /// Gets the ApertureValue exif tag. + /// + public static ExifTag ApertureValue { get; } = new ExifTag(ExifTagValue.ApertureValue); + + /// + /// Gets the MaxApertureValue exif tag. + /// + public static ExifTag MaxApertureValue { get; } = new ExifTag(ExifTagValue.MaxApertureValue); + + /// + /// Gets the SubjectDistance exif tag. + /// + public static ExifTag SubjectDistance { get; } = new ExifTag(ExifTagValue.SubjectDistance); + + /// + /// Gets the FocalLength exif tag. + /// + public static ExifTag FocalLength { get; } = new ExifTag(ExifTagValue.FocalLength); + + /// + /// Gets the FlashEnergy2 exif tag. + /// + public static ExifTag FlashEnergy2 { get; } = new ExifTag(ExifTagValue.FlashEnergy2); + + /// + /// Gets the FocalPlaneXResolution2 exif tag. + /// + public static ExifTag FocalPlaneXResolution2 { get; } = new ExifTag(ExifTagValue.FocalPlaneXResolution2); + + /// + /// Gets the FocalPlaneYResolution2 exif tag. + /// + public static ExifTag FocalPlaneYResolution2 { get; } = new ExifTag(ExifTagValue.FocalPlaneYResolution2); + + /// + /// Gets the ExposureIndex2 exif tag. + /// + public static ExifTag ExposureIndex2 { get; } = new ExifTag(ExifTagValue.ExposureIndex2); + + /// + /// Gets the Humidity exif tag. + /// + public static ExifTag Humidity { get; } = new ExifTag(ExifTagValue.Humidity); + + /// + /// Gets the Pressure exif tag. + /// + public static ExifTag Pressure { get; } = new ExifTag(ExifTagValue.Pressure); + + /// + /// Gets the Acceleration exif tag. + /// + public static ExifTag Acceleration { get; } = new ExifTag(ExifTagValue.Acceleration); + + /// + /// Gets the FlashEnergy exif tag. + /// + public static ExifTag FlashEnergy { get; } = new ExifTag(ExifTagValue.FlashEnergy); + + /// + /// Gets the FocalPlaneXResolution exif tag. + /// + public static ExifTag FocalPlaneXResolution { get; } = new ExifTag(ExifTagValue.FocalPlaneXResolution); + + /// + /// Gets the FocalPlaneYResolution exif tag. + /// + public static ExifTag FocalPlaneYResolution { get; } = new ExifTag(ExifTagValue.FocalPlaneYResolution); + + /// + /// Gets the ExposureIndex exif tag. + /// + public static ExifTag ExposureIndex { get; } = new ExifTag(ExifTagValue.ExposureIndex); + + /// + /// Gets the DigitalZoomRatio exif tag. + /// + public static ExifTag DigitalZoomRatio { get; } = new ExifTag(ExifTagValue.DigitalZoomRatio); + + /// + /// Gets the GPSAltitude exif tag. + /// + public static ExifTag GPSAltitude { get; } = new ExifTag(ExifTagValue.GPSAltitude); + + /// + /// Gets the GPSDOP exif tag. + /// + public static ExifTag GPSDOP { get; } = new ExifTag(ExifTagValue.GPSDOP); + + /// + /// Gets the GPSSpeed exif tag. + /// + public static ExifTag GPSSpeed { get; } = new ExifTag(ExifTagValue.GPSSpeed); + + /// + /// Gets the GPSTrack exif tag. + /// + public static ExifTag GPSTrack { get; } = new ExifTag(ExifTagValue.GPSTrack); + + /// + /// Gets the GPSImgDirection exif tag. + /// + public static ExifTag GPSImgDirection { get; } = new ExifTag(ExifTagValue.GPSImgDirection); + + /// + /// Gets the GPSDestBearing exif tag. + /// + public static ExifTag GPSDestBearing { get; } = new ExifTag(ExifTagValue.GPSDestBearing); + + /// + /// Gets the GPSDestDistance exif tag. + /// + public static ExifTag GPSDestDistance { get; } = new ExifTag(ExifTagValue.GPSDestDistance); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs new file mode 100644 index 0000000000..dfca0e2777 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the WhitePoint exif tag. + /// + public static ExifTag WhitePoint { get; } = new ExifTag(ExifTagValue.WhitePoint); + + /// + /// Gets the PrimaryChromaticities exif tag. + /// + public static ExifTag PrimaryChromaticities { get; } = new ExifTag(ExifTagValue.PrimaryChromaticities); + + /// + /// Gets the YCbCrCoefficients exif tag. + /// + public static ExifTag YCbCrCoefficients { get; } = new ExifTag(ExifTagValue.YCbCrCoefficients); + + /// + /// Gets the ReferenceBlackWhite exif tag. + /// + public static ExifTag ReferenceBlackWhite { get; } = new ExifTag(ExifTagValue.ReferenceBlackWhite); + + /// + /// Gets the GPSLatitude exif tag. + /// + public static ExifTag GPSLatitude { get; } = new ExifTag(ExifTagValue.GPSLatitude); + + /// + /// Gets the GPSLongitude exif tag. + /// + public static ExifTag GPSLongitude { get; } = new ExifTag(ExifTagValue.GPSLongitude); + + /// + /// Gets the GPSTimestamp exif tag. + /// + public static ExifTag GPSTimestamp { get; } = new ExifTag(ExifTagValue.GPSTimestamp); + + /// + /// Gets the GPSDestLatitude exif tag. + /// + public static ExifTag GPSDestLatitude { get; } = new ExifTag(ExifTagValue.GPSDestLatitude); + + /// + /// Gets the GPSDestLongitude exif tag. + /// + public static ExifTag GPSDestLongitude { get; } = new ExifTag(ExifTagValue.GPSDestLongitude); + + /// + /// Gets the LensSpecification exif tag. + /// + public static ExifTag LensSpecification { get; } = new ExifTag(ExifTagValue.LensSpecification); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs new file mode 100644 index 0000000000..f520455311 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs @@ -0,0 +1,239 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the OldSubfileType exif tag. + /// + public static ExifTag OldSubfileType { get; } = new ExifTag(ExifTagValue.OldSubfileType); + + /// + /// Gets the Compression exif tag. + /// + public static ExifTag Compression { get; } = new ExifTag(ExifTagValue.Compression); + + /// + /// Gets the PhotometricInterpretation exif tag. + /// + public static ExifTag PhotometricInterpretation { get; } = new ExifTag(ExifTagValue.PhotometricInterpretation); + + /// + /// Gets the Thresholding exif tag. + /// + public static ExifTag Thresholding { get; } = new ExifTag(ExifTagValue.Thresholding); + + /// + /// Gets the CellWidth exif tag. + /// + public static ExifTag CellWidth { get; } = new ExifTag(ExifTagValue.CellWidth); + + /// + /// Gets the CellLength exif tag. + /// + public static ExifTag CellLength { get; } = new ExifTag(ExifTagValue.CellLength); + + /// + /// Gets the FillOrder exif tag. + /// + public static ExifTag FillOrder { get; } = new ExifTag(ExifTagValue.FillOrder); + + /// + /// Gets the Orientation exif tag. + /// + public static ExifTag Orientation { get; } = new ExifTag(ExifTagValue.Orientation); + + /// + /// Gets the SamplesPerPixel exif tag. + /// + public static ExifTag SamplesPerPixel { get; } = new ExifTag(ExifTagValue.SamplesPerPixel); + + /// + /// Gets the PlanarConfiguration exif tag. + /// + public static ExifTag PlanarConfiguration { get; } = new ExifTag(ExifTagValue.PlanarConfiguration); + + /// + /// Gets the GrayResponseUnit exif tag. + /// + public static ExifTag GrayResponseUnit { get; } = new ExifTag(ExifTagValue.GrayResponseUnit); + + /// + /// Gets the ResolutionUnit exif tag. + /// + public static ExifTag ResolutionUnit { get; } = new ExifTag(ExifTagValue.ResolutionUnit); + + /// + /// Gets the CleanFaxData exif tag. + /// + public static ExifTag CleanFaxData { get; } = new ExifTag(ExifTagValue.CleanFaxData); + + /// + /// Gets the InkSet exif tag. + /// + public static ExifTag InkSet { get; } = new ExifTag(ExifTagValue.InkSet); + + /// + /// Gets the NumberOfInks exif tag. + /// + public static ExifTag NumberOfInks { get; } = new ExifTag(ExifTagValue.NumberOfInks); + + /// + /// Gets the DotRange exif tag. + /// + public static ExifTag DotRange { get; } = new ExifTag(ExifTagValue.DotRange); + + /// + /// Gets the Indexed exif tag. + /// + public static ExifTag Indexed { get; } = new ExifTag(ExifTagValue.Indexed); + + /// + /// Gets the OPIProxy exif tag. + /// + public static ExifTag OPIProxy { get; } = new ExifTag(ExifTagValue.OPIProxy); + + /// + /// Gets the JPEGProc exif tag. + /// + public static ExifTag JPEGProc { get; } = new ExifTag(ExifTagValue.JPEGProc); + + /// + /// Gets the JPEGRestartInterval exif tag. + /// + public static ExifTag JPEGRestartInterval { get; } = new ExifTag(ExifTagValue.JPEGRestartInterval); + + /// + /// Gets the YCbCrPositioning exif tag. + /// + public static ExifTag YCbCrPositioning { get; } = new ExifTag(ExifTagValue.YCbCrPositioning); + + /// + /// Gets the Rating exif tag. + /// + public static ExifTag Rating { get; } = new ExifTag(ExifTagValue.Rating); + + /// + /// Gets the RatingPercent exif tag. + /// + public static ExifTag RatingPercent { get; } = new ExifTag(ExifTagValue.RatingPercent); + + /// + /// Gets the ExposureProgram exif tag. + /// + public static ExifTag ExposureProgram { get; } = new ExifTag(ExifTagValue.ExposureProgram); + + /// + /// Gets the Interlace exif tag. + /// + public static ExifTag Interlace { get; } = new ExifTag(ExifTagValue.Interlace); + + /// + /// Gets the SelfTimerMode exif tag. + /// + public static ExifTag SelfTimerMode { get; } = new ExifTag(ExifTagValue.SelfTimerMode); + + /// + /// Gets the SensitivityType exif tag. + /// + public static ExifTag SensitivityType { get; } = new ExifTag(ExifTagValue.SensitivityType); + + /// + /// Gets the MeteringMode exif tag. + /// + public static ExifTag MeteringMode { get; } = new ExifTag(ExifTagValue.MeteringMode); + + /// + /// Gets the LightSource exif tag. + /// + public static ExifTag LightSource { get; } = new ExifTag(ExifTagValue.LightSource); + + /// + /// Gets the FocalPlaneResolutionUnit2 exif tag. + /// + public static ExifTag FocalPlaneResolutionUnit2 { get; } = new ExifTag(ExifTagValue.FocalPlaneResolutionUnit2); + + /// + /// Gets the SensingMethod2 exif tag. + /// + public static ExifTag SensingMethod2 { get; } = new ExifTag(ExifTagValue.SensingMethod2); + + /// + /// Gets the Flash exif tag. + /// + public static ExifTag Flash { get; } = new ExifTag(ExifTagValue.Flash); + + /// + /// Gets the ColorSpace exif tag. + /// + public static ExifTag ColorSpace { get; } = new ExifTag(ExifTagValue.ColorSpace); + + /// + /// Gets the FocalPlaneResolutionUnit exif tag. + /// + public static ExifTag FocalPlaneResolutionUnit { get; } = new ExifTag(ExifTagValue.FocalPlaneResolutionUnit); + + /// + /// Gets the SensingMethod exif tag. + /// + public static ExifTag SensingMethod { get; } = new ExifTag(ExifTagValue.SensingMethod); + + /// + /// Gets the CustomRendered exif tag. + /// + public static ExifTag CustomRendered { get; } = new ExifTag(ExifTagValue.CustomRendered); + + /// + /// Gets the ExposureMode exif tag. + /// + public static ExifTag ExposureMode { get; } = new ExifTag(ExifTagValue.ExposureMode); + + /// + /// Gets the WhiteBalance exif tag. + /// + public static ExifTag WhiteBalance { get; } = new ExifTag(ExifTagValue.WhiteBalance); + + /// + /// Gets the FocalLengthIn35mmFilm exif tag. + /// + public static ExifTag FocalLengthIn35mmFilm { get; } = new ExifTag(ExifTagValue.FocalLengthIn35mmFilm); + + /// + /// Gets the SceneCaptureType exif tag. + /// + public static ExifTag SceneCaptureType { get; } = new ExifTag(ExifTagValue.SceneCaptureType); + + /// + /// Gets the GainControl exif tag. + /// + public static ExifTag GainControl { get; } = new ExifTag(ExifTagValue.GainControl); + + /// + /// Gets the Contrast exif tag. + /// + public static ExifTag Contrast { get; } = new ExifTag(ExifTagValue.Contrast); + + /// + /// Gets the Saturation exif tag. + /// + public static ExifTag Saturation { get; } = new ExifTag(ExifTagValue.Saturation); + + /// + /// Gets the Sharpness exif tag. + /// + public static ExifTag Sharpness { get; } = new ExifTag(ExifTagValue.Sharpness); + + /// + /// Gets the SubjectDistanceRange exif tag. + /// + public static ExifTag SubjectDistanceRange { get; } = new ExifTag(ExifTagValue.SubjectDistanceRange); + + /// + /// Gets the GPSDifferential exif tag. + /// + public static ExifTag GPSDifferential { get; } = new ExifTag(ExifTagValue.GPSDifferential); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs new file mode 100644 index 0000000000..ce0bb36f0d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the BitsPerSample exif tag. + /// + public static ExifTag BitsPerSample { get; } = new ExifTag(ExifTagValue.BitsPerSample); + + /// + /// Gets the MinSampleValue exif tag. + /// + public static ExifTag MinSampleValue { get; } = new ExifTag(ExifTagValue.MinSampleValue); + + /// + /// Gets the MaxSampleValue exif tag. + /// + public static ExifTag MaxSampleValue { get; } = new ExifTag(ExifTagValue.MaxSampleValue); + + /// + /// Gets the GrayResponseCurve exif tag. + /// + public static ExifTag GrayResponseCurve { get; } = new ExifTag(ExifTagValue.GrayResponseCurve); + + /// + /// Gets the ColorMap exif tag. + /// + public static ExifTag ColorMap { get; } = new ExifTag(ExifTagValue.ColorMap); + + /// + /// Gets the ExtraSamples exif tag. + /// + public static ExifTag ExtraSamples { get; } = new ExifTag(ExifTagValue.ExtraSamples); + + /// + /// Gets the PageNumber exif tag. + /// + public static ExifTag PageNumber { get; } = new ExifTag(ExifTagValue.PageNumber); + + /// + /// Gets the TransferFunction exif tag. + /// + public static ExifTag TransferFunction { get; } = new ExifTag(ExifTagValue.TransferFunction); + + /// + /// Gets the Predictor exif tag. + /// + public static ExifTag Predictor { get; } = new ExifTag(ExifTagValue.Predictor); + + /// + /// Gets the HalftoneHints exif tag. + /// + public static ExifTag HalftoneHints { get; } = new ExifTag(ExifTagValue.HalftoneHints); + + /// + /// Gets the SampleFormat exif tag. + /// + public static ExifTag SampleFormat { get; } = new ExifTag(ExifTagValue.SampleFormat); + + /// + /// Gets the TransferRange exif tag. + /// + public static ExifTag TransferRange { get; } = new ExifTag(ExifTagValue.TransferRange); + + /// + /// Gets the DefaultImageColor exif tag. + /// + public static ExifTag DefaultImageColor { get; } = new ExifTag(ExifTagValue.DefaultImageColor); + + /// + /// Gets the JPEGLosslessPredictors exif tag. + /// + public static ExifTag JPEGLosslessPredictors { get; } = new ExifTag(ExifTagValue.JPEGLosslessPredictors); + + /// + /// Gets the JPEGPointTransforms exif tag. + /// + public static ExifTag JPEGPointTransforms { get; } = new ExifTag(ExifTagValue.JPEGPointTransforms); + + /// + /// Gets the YCbCrSubsampling exif tag. + /// + public static ExifTag YCbCrSubsampling { get; } = new ExifTag(ExifTagValue.YCbCrSubsampling); + + /// + /// Gets the CFARepeatPatternDim exif tag. + /// + public static ExifTag CFARepeatPatternDim { get; } = new ExifTag(ExifTagValue.CFARepeatPatternDim); + + /// + /// Gets the IntergraphPacketData exif tag. + /// + public static ExifTag IntergraphPacketData { get; } = new ExifTag(ExifTagValue.IntergraphPacketData); + + /// + /// Gets the ISOSpeedRatings exif tag. + /// + public static ExifTag ISOSpeedRatings { get; } = new ExifTag(ExifTagValue.ISOSpeedRatings); + + /// + /// Gets the SubjectArea exif tag. + /// + public static ExifTag SubjectArea { get; } = new ExifTag(ExifTagValue.SubjectArea); + + /// + /// Gets the SubjectLocation exif tag. + /// + public static ExifTag SubjectLocation { get; } = new ExifTag(ExifTagValue.SubjectLocation); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs new file mode 100644 index 0000000000..46f495786f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the ShutterSpeedValue exif tag. + /// + public static ExifTag ShutterSpeedValue { get; } = new ExifTag(ExifTagValue.ShutterSpeedValue); + + /// + /// Gets the BrightnessValue exif tag. + /// + public static ExifTag BrightnessValue { get; } = new ExifTag(ExifTagValue.BrightnessValue); + + /// + /// Gets the ExposureBiasValue exif tag. + /// + public static ExifTag ExposureBiasValue { get; } = new ExifTag(ExifTagValue.ExposureBiasValue); + + /// + /// Gets the AmbientTemperature exif tag. + /// + public static ExifTag AmbientTemperature { get; } = new ExifTag(ExifTagValue.AmbientTemperature); + + /// + /// Gets the WaterDepth exif tag. + /// + public static ExifTag WaterDepth { get; } = new ExifTag(ExifTagValue.WaterDepth); + + /// + /// Gets the CameraElevationAngle exif tag. + /// + public static ExifTag CameraElevationAngle { get; } = new ExifTag(ExifTagValue.CameraElevationAngle); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs new file mode 100644 index 0000000000..4c4a6f2758 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the Decode exif tag. + /// + public static ExifTag Decode { get; } = new ExifTag(ExifTagValue.Decode); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs new file mode 100644 index 0000000000..3fc353211d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs @@ -0,0 +1,279 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the ImageDescription exif tag. + /// + public static ExifTag ImageDescription { get; } = new ExifTag(ExifTagValue.ImageDescription); + + /// + /// Gets the Make exif tag. + /// + public static ExifTag Make { get; } = new ExifTag(ExifTagValue.Make); + + /// + /// Gets the Model exif tag. + /// + public static ExifTag Model { get; } = new ExifTag(ExifTagValue.Model); + + /// + /// Gets the Software exif tag. + /// + public static ExifTag Software { get; } = new ExifTag(ExifTagValue.Software); + + /// + /// Gets the DateTime exif tag. + /// + public static ExifTag DateTime { get; } = new ExifTag(ExifTagValue.DateTime); + + /// + /// Gets the Artist exif tag. + /// + public static ExifTag Artist { get; } = new ExifTag(ExifTagValue.Artist); + + /// + /// Gets the HostComputer exif tag. + /// + public static ExifTag HostComputer { get; } = new ExifTag(ExifTagValue.HostComputer); + + /// + /// Gets the Copyright exif tag. + /// + public static ExifTag Copyright { get; } = new ExifTag(ExifTagValue.Copyright); + + /// + /// Gets the DocumentName exif tag. + /// + public static ExifTag DocumentName { get; } = new ExifTag(ExifTagValue.DocumentName); + + /// + /// Gets the PageName exif tag. + /// + public static ExifTag PageName { get; } = new ExifTag(ExifTagValue.PageName); + + /// + /// Gets the InkNames exif tag. + /// + public static ExifTag InkNames { get; } = new ExifTag(ExifTagValue.InkNames); + + /// + /// Gets the TargetPrinter exif tag. + /// + public static ExifTag TargetPrinter { get; } = new ExifTag(ExifTagValue.TargetPrinter); + + /// + /// Gets the ImageID exif tag. + /// + public static ExifTag ImageID { get; } = new ExifTag(ExifTagValue.ImageID); + + /// + /// Gets the MDLabName exif tag. + /// + public static ExifTag MDLabName { get; } = new ExifTag(ExifTagValue.MDLabName); + + /// + /// Gets the MDSampleInfo exif tag. + /// + public static ExifTag MDSampleInfo { get; } = new ExifTag(ExifTagValue.MDSampleInfo); + + /// + /// Gets the MDPrepDate exif tag. + /// + public static ExifTag MDPrepDate { get; } = new ExifTag(ExifTagValue.MDPrepDate); + + /// + /// Gets the MDPrepTime exif tag. + /// + public static ExifTag MDPrepTime { get; } = new ExifTag(ExifTagValue.MDPrepTime); + + /// + /// Gets the MDFileUnits exif tag. + /// + public static ExifTag MDFileUnits => new ExifTag(ExifTagValue.MDFileUnits); + + /// + /// Gets the SEMInfo exif tag. + /// + public static ExifTag SEMInfo { get; } = new ExifTag(ExifTagValue.SEMInfo); + + /// + /// Gets the SpectralSensitivity exif tag. + /// + public static ExifTag SpectralSensitivity { get; } = new ExifTag(ExifTagValue.SpectralSensitivity); + + /// + /// Gets the DateTimeOriginal exif tag. + /// + public static ExifTag DateTimeOriginal { get; } = new ExifTag(ExifTagValue.DateTimeOriginal); + + /// + /// Gets the DateTimeDigitized exif tag. + /// + public static ExifTag DateTimeDigitized { get; } = new ExifTag(ExifTagValue.DateTimeDigitized); + + /// + /// Gets the SubsecTime exif tag. + /// + public static ExifTag SubsecTime { get; } = new ExifTag(ExifTagValue.SubsecTime); + + /// + /// Gets the SubsecTimeOriginal exif tag. + /// + public static ExifTag SubsecTimeOriginal { get; } = new ExifTag(ExifTagValue.SubsecTimeOriginal); + + /// + /// Gets the SubsecTimeDigitized exif tag. + /// + public static ExifTag SubsecTimeDigitized { get; } = new ExifTag(ExifTagValue.SubsecTimeDigitized); + + /// + /// Gets the RelatedSoundFile exif tag. + /// + public static ExifTag RelatedSoundFile { get; } = new ExifTag(ExifTagValue.RelatedSoundFile); + + /// + /// Gets the FaxSubaddress exif tag. + /// + public static ExifTag FaxSubaddress { get; } = new ExifTag(ExifTagValue.FaxSubaddress); + + /// + /// Gets the OffsetTime exif tag. + /// + public static ExifTag OffsetTime { get; } = new ExifTag(ExifTagValue.OffsetTime); + + /// + /// Gets the OffsetTimeOriginal exif tag. + /// + public static ExifTag OffsetTimeOriginal { get; } = new ExifTag(ExifTagValue.OffsetTimeOriginal); + + /// + /// Gets the OffsetTimeDigitized exif tag. + /// + public static ExifTag OffsetTimeDigitized { get; } = new ExifTag(ExifTagValue.OffsetTimeDigitized); + + /// + /// Gets the SecurityClassification exif tag. + /// + public static ExifTag SecurityClassification { get; } = new ExifTag(ExifTagValue.SecurityClassification); + + /// + /// Gets the ImageHistory exif tag. + /// + public static ExifTag ImageHistory { get; } = new ExifTag(ExifTagValue.ImageHistory); + + /// + /// Gets the ImageUniqueID exif tag. + /// + public static ExifTag ImageUniqueID { get; } = new ExifTag(ExifTagValue.ImageUniqueID); + + /// + /// Gets the OwnerName exif tag. + /// + public static ExifTag OwnerName { get; } = new ExifTag(ExifTagValue.OwnerName); + + /// + /// Gets the SerialNumber exif tag. + /// + public static ExifTag SerialNumber { get; } = new ExifTag(ExifTagValue.SerialNumber); + + /// + /// Gets the LensMake exif tag. + /// + public static ExifTag LensMake { get; } = new ExifTag(ExifTagValue.LensMake); + + /// + /// Gets the LensModel exif tag. + /// + public static ExifTag LensModel { get; } = new ExifTag(ExifTagValue.LensModel); + + /// + /// Gets the LensSerialNumber exif tag. + /// + public static ExifTag LensSerialNumber { get; } = new ExifTag(ExifTagValue.LensSerialNumber); + + /// + /// Gets the GDALMetadata exif tag. + /// + public static ExifTag GDALMetadata { get; } = new ExifTag(ExifTagValue.GDALMetadata); + + /// + /// Gets the GDALNoData exif tag. + /// + public static ExifTag GDALNoData { get; } = new ExifTag(ExifTagValue.GDALNoData); + + /// + /// Gets the GPSLatitudeRef exif tag. + /// + public static ExifTag GPSLatitudeRef { get; } = new ExifTag(ExifTagValue.GPSLatitudeRef); + + /// + /// Gets the GPSLongitudeRef exif tag. + /// + public static ExifTag GPSLongitudeRef { get; } = new ExifTag(ExifTagValue.GPSLongitudeRef); + + /// + /// Gets the GPSSatellites exif tag. + /// + public static ExifTag GPSSatellites { get; } = new ExifTag(ExifTagValue.GPSSatellites); + + /// + /// Gets the GPSStatus exif tag. + /// + public static ExifTag GPSStatus { get; } = new ExifTag(ExifTagValue.GPSStatus); + + /// + /// Gets the GPSMeasureMode exif tag. + /// + public static ExifTag GPSMeasureMode { get; } = new ExifTag(ExifTagValue.GPSMeasureMode); + + /// + /// Gets the GPSSpeedRef exif tag. + /// + public static ExifTag GPSSpeedRef { get; } = new ExifTag(ExifTagValue.GPSSpeedRef); + + /// + /// Gets the GPSTrackRef exif tag. + /// + public static ExifTag GPSTrackRef { get; } = new ExifTag(ExifTagValue.GPSTrackRef); + + /// + /// Gets the GPSImgDirectionRef exif tag. + /// + public static ExifTag GPSImgDirectionRef { get; } = new ExifTag(ExifTagValue.GPSImgDirectionRef); + + /// + /// Gets the GPSMapDatum exif tag. + /// + public static ExifTag GPSMapDatum { get; } = new ExifTag(ExifTagValue.GPSMapDatum); + + /// + /// Gets the GPSDestLatitudeRef exif tag. + /// + public static ExifTag GPSDestLatitudeRef { get; } = new ExifTag(ExifTagValue.GPSDestLatitudeRef); + + /// + /// Gets the GPSDestLongitudeRef exif tag. + /// + public static ExifTag GPSDestLongitudeRef { get; } = new ExifTag(ExifTagValue.GPSDestLongitudeRef); + + /// + /// Gets the GPSDestBearingRef exif tag. + /// + public static ExifTag GPSDestBearingRef { get; } = new ExifTag(ExifTagValue.GPSDestBearingRef); + + /// + /// Gets the GPSDestDistanceRef exif tag. + /// + public static ExifTag GPSDestDistanceRef { get; } = new ExifTag(ExifTagValue.GPSDestDistanceRef); + + /// + /// Gets the GPSDateStamp exif tag. + /// + public static ExifTag GPSDateStamp { get; } = new ExifTag(ExifTagValue.GPSDateStamp); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs new file mode 100644 index 0000000000..1d9af6adce --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the JPEGTables exif tag. + /// + public static ExifTag JPEGTables { get; } = new ExifTag(ExifTagValue.JPEGTables); + + /// + /// Gets the OECF exif tag. + /// + public static ExifTag OECF { get; } = new ExifTag(ExifTagValue.OECF); + + /// + /// Gets the ExifVersion exif tag. + /// + public static ExifTag ExifVersion { get; } = new ExifTag(ExifTagValue.ExifVersion); + + /// + /// Gets the ComponentsConfiguration exif tag. + /// + public static ExifTag ComponentsConfiguration { get; } = new ExifTag(ExifTagValue.ComponentsConfiguration); + + /// + /// Gets the MakerNote exif tag. + /// + public static ExifTag MakerNote { get; } = new ExifTag(ExifTagValue.MakerNote); + + /// + /// Gets the UserComment exif tag. + /// + public static ExifTag UserComment { get; } = new ExifTag(ExifTagValue.UserComment); + + /// + /// Gets the FlashpixVersion exif tag. + /// + public static ExifTag FlashpixVersion { get; } = new ExifTag(ExifTagValue.FlashpixVersion); + + /// + /// Gets the SpatialFrequencyResponse exif tag. + /// + public static ExifTag SpatialFrequencyResponse { get; } = new ExifTag(ExifTagValue.SpatialFrequencyResponse); + + /// + /// Gets the SpatialFrequencyResponse2 exif tag. + /// + public static ExifTag SpatialFrequencyResponse2 { get; } = new ExifTag(ExifTagValue.SpatialFrequencyResponse2); + + /// + /// Gets the Noise exif tag. + /// + public static ExifTag Noise { get; } = new ExifTag(ExifTagValue.Noise); + + /// + /// Gets the CFAPattern exif tag. + /// + public static ExifTag CFAPattern { get; } = new ExifTag(ExifTagValue.CFAPattern); + + /// + /// Gets the DeviceSettingDescription exif tag. + /// + public static ExifTag DeviceSettingDescription { get; } = new ExifTag(ExifTagValue.DeviceSettingDescription); + + /// + /// Gets the ImageSourceData exif tag. + /// + public static ExifTag ImageSourceData { get; } = new ExifTag(ExifTagValue.ImageSourceData); + + /// + /// Gets the GPSProcessingMethod exif tag. + /// + public static ExifTag GPSProcessingMethod { get; } = new ExifTag(ExifTagValue.GPSProcessingMethod); + + /// + /// Gets the GPSAreaInformation exif tag. + /// + public static ExifTag GPSAreaInformation { get; } = new ExifTag(ExifTagValue.GPSAreaInformation); + + /// + /// Gets the FileSource exif tag. + /// + public static ExifTag FileSource { get; } = new ExifTag(ExifTagValue.FileSource); + + /// + /// Gets the ImageDescription exif tag. + /// + public static ExifTag SceneType { get; } = new ExifTag(ExifTagValue.SceneType); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs new file mode 100644 index 0000000000..81a7463305 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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) => Equals(left, right); + + /// + /// 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) => !Equals(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 new file mode 100644 index 0000000000..e07a32598d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs @@ -0,0 +1,1543 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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, + + /// + /// 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, + + /// + /// 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, + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs new file mode 100644 index 0000000000..13c94e16d0 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..1e6d7abdbe --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/ExifArrayValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs new file mode 100644 index 0000000000..37dd64686a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 = new TValueType[] { (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 new file mode 100644 index 0000000000..375bc4ffff --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 >= byte.MinValue && intValue <= 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 new file mode 100644 index 0000000000..72be91ec54 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 >= byte.MinValue && intValue <= byte.MaxValue) + { + this.Value = new byte[] { (byte)intValue }; + } + + return true; + } + + return false; + } + + public override IExifValue DeepClone() => new ExifByteArray(this); + + private bool TrySetSignedIntArray(int[] intArrayValue) + { + if (Array.FindIndex(intArrayValue, x => x < byte.MinValue || x > byte.MaxValue) > -1) + { + return false; + } + + var 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 new file mode 100644 index 0000000000..553b4d3915 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..3afefc2949 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/ExifFloat.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs new file mode 100644 index 0000000000..0c4aba895f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..eaafff953e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..49dc8ebc26 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/ExifLongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs new file mode 100644 index 0000000000..206b020ce5 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..9e206b23d1 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..28002f0b75 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 null) + { + return ExifDataType.Short; + } + + for (int i = 0; i < this.Value.Length; i++) + { + if (this.Value[i] > ushort.MaxValue) + { + return ExifDataType.Long; + } + } + + return ExifDataType.Short; + } + } + + public override IExifValue DeepClone() => new ExifNumberArray(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs new file mode 100644 index 0000000000..c1f4c3800c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..227362444f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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[] { 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; + } + + var 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 new file mode 100644 index 0000000000..c0acd7790e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 >= ushort.MinValue && intValue <= 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 new file mode 100644 index 0000000000..a2f40d50c9 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 >= ushort.MinValue && signedInt <= ushort.MaxValue) + { + this.Value = new ushort[] { (ushort)signedInt }; + } + + return true; + } + + if (value is short signedShort) + { + if (signedShort >= ushort.MinValue) + { + this.Value = new ushort[] { (ushort)signedShort }; + } + + return true; + } + + return false; + } + + public override IExifValue DeepClone() => new ExifShortArray(this); + + private bool TrySetSignedIntArray(int[] signed) + { + if (Array.FindIndex(signed, x => x < ushort.MinValue || x > ushort.MaxValue) > -1) + { + return false; + } + + var 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; + } + + var 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 new file mode 100644 index 0000000000..37fef08c16 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 >= sbyte.MinValue && intValue <= 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 new file mode 100644 index 0000000000..5b562e7d97 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..d5a8e7c235 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/ExifSignedLongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs new file mode 100644 index 0000000000..e26584e8b4 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..5f80db4ea0 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..fbf4903c44 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..42332a9a30 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 >= short.MinValue && intValue <= 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 new file mode 100644 index 0000000000..9e5fd65e64 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedShortArray : ExifArrayValue + { + 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 >= short.MinValue && intValue <= short.MaxValue) + { + this.Value = new short[] { (short)intValue }; + } + + return true; + } + + return false; + } + + public override IExifValue DeepClone() => new ExifSignedShortArray(this); + + private bool TrySetSignedArray(int[] intArray) + { + if (Array.FindIndex(intArray, x => x < short.MinValue || x > short.MaxValue) > -1) + { + return false; + } + + var 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 new file mode 100644 index 0000000000..0514c2e8c2 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/ExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs new file mode 100644 index 0000000000..3143339198 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + 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. + var 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 new file mode 100644 index 0000000000..2d8aa92601 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -0,0 +1,307 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static partial 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, uint numberOfComponents) + { + bool isArray = numberOfComponents != 1; + + switch (dataType) + { + case ExifDataType.Byte: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); + case ExifDataType.DoubleFloat: return isArray ? (ExifValue)new ExifDoubleArray(tag) : new ExifDouble(tag); + case ExifDataType.SingleFloat: return isArray ? (ExifValue)new ExifFloatArray(tag) : new ExifFloat(tag); + case ExifDataType.Long: return isArray ? (ExifValue)new ExifLongArray(tag) : new ExifLong(tag); + case ExifDataType.Rational: return isArray ? (ExifValue)new ExifRationalArray(tag) : new ExifRational(tag); + case ExifDataType.Short: return isArray ? (ExifValue)new ExifShortArray(tag) : new ExifShort(tag); + case ExifDataType.SignedByte: return isArray ? (ExifValue)new ExifSignedByteArray(tag) : new ExifSignedByte(tag); + case ExifDataType.SignedLong: return isArray ? (ExifValue)new ExifSignedLongArray(tag) : new ExifSignedLong(tag); + case ExifDataType.SignedRational: return isArray ? (ExifValue)new ExifSignedRationalArray(tag) : new ExifSignedRational(tag); + case ExifDataType.SignedShort: return isArray ? (ExifValue)new ExifSignedShortArray(tag) : new ExifSignedShort(tag); + case ExifDataType.Ascii: return new ExifString(tag); + case ExifDataType.Undefined: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); + default: return null; + } + } + + private static object CreateValue(ExifTagValue tag) + { + switch (tag) + { + case ExifTagValue.FaxProfile: return new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte); + case ExifTagValue.ModeNumber: return new ExifByte(ExifTag.ModeNumber, ExifDataType.Byte); + case ExifTagValue.GPSAltitudeRef: return new ExifByte(ExifTag.GPSAltitudeRef, ExifDataType.Byte); + + case ExifTagValue.ClipPath: return new ExifByteArray(ExifTag.ClipPath, ExifDataType.Byte); + case ExifTagValue.VersionYear: return new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte); + case ExifTagValue.XMP: return new ExifByteArray(ExifTag.XMP, ExifDataType.Byte); + case ExifTagValue.CFAPattern2: return new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte); + case ExifTagValue.TIFFEPStandardID: return new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte); + case ExifTagValue.XPTitle: return new ExifByteArray(ExifTag.XPTitle, ExifDataType.Byte); + case ExifTagValue.XPComment: return new ExifByteArray(ExifTag.XPComment, ExifDataType.Byte); + case ExifTagValue.XPAuthor: return new ExifByteArray(ExifTag.XPAuthor, ExifDataType.Byte); + case ExifTagValue.XPKeywords: return new ExifByteArray(ExifTag.XPKeywords, ExifDataType.Byte); + case ExifTagValue.XPSubject: return new ExifByteArray(ExifTag.XPSubject, ExifDataType.Byte); + case ExifTagValue.GPSVersionID: return new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte); + + case ExifTagValue.PixelScale: return new ExifDoubleArray(ExifTag.PixelScale); + case ExifTagValue.IntergraphMatrix: return new ExifDoubleArray(ExifTag.IntergraphMatrix); + case ExifTagValue.ModelTiePoint: return new ExifDoubleArray(ExifTag.ModelTiePoint); + case ExifTagValue.ModelTransform: return new ExifDoubleArray(ExifTag.ModelTransform); + + case ExifTagValue.SubfileType: return new ExifLong(ExifTag.SubfileType); + case ExifTagValue.SubIFDOffset: return new ExifLong(ExifTag.SubIFDOffset); + case ExifTagValue.GPSIFDOffset: return new ExifLong(ExifTag.GPSIFDOffset); + case ExifTagValue.T4Options: return new ExifLong(ExifTag.T4Options); + case ExifTagValue.T6Options: return new ExifLong(ExifTag.T6Options); + case ExifTagValue.XClipPathUnits: return new ExifLong(ExifTag.XClipPathUnits); + case ExifTagValue.YClipPathUnits: return new ExifLong(ExifTag.YClipPathUnits); + case ExifTagValue.ProfileType: return new ExifLong(ExifTag.ProfileType); + case ExifTagValue.CodingMethods: return new ExifLong(ExifTag.CodingMethods); + case ExifTagValue.T82ptions: return new ExifLong(ExifTag.T82ptions); + case ExifTagValue.JPEGInterchangeFormat: return new ExifLong(ExifTag.JPEGInterchangeFormat); + case ExifTagValue.JPEGInterchangeFormatLength: return new ExifLong(ExifTag.JPEGInterchangeFormatLength); + case ExifTagValue.MDFileTag: return new ExifLong(ExifTag.MDFileTag); + case ExifTagValue.StandardOutputSensitivity: return new ExifLong(ExifTag.StandardOutputSensitivity); + case ExifTagValue.RecommendedExposureIndex: return new ExifLong(ExifTag.RecommendedExposureIndex); + case ExifTagValue.ISOSpeed: return new ExifLong(ExifTag.ISOSpeed); + case ExifTagValue.ISOSpeedLatitudeyyy: return new ExifLong(ExifTag.ISOSpeedLatitudeyyy); + case ExifTagValue.ISOSpeedLatitudezzz: return new ExifLong(ExifTag.ISOSpeedLatitudezzz); + case ExifTagValue.FaxRecvParams: return new ExifLong(ExifTag.FaxRecvParams); + case ExifTagValue.FaxRecvTime: return new ExifLong(ExifTag.FaxRecvTime); + case ExifTagValue.ImageNumber: return new ExifLong(ExifTag.ImageNumber); + + case ExifTagValue.FreeOffsets: return new ExifLongArray(ExifTag.FreeOffsets); + case ExifTagValue.FreeByteCounts: return new ExifLongArray(ExifTag.FreeByteCounts); + case ExifTagValue.ColorResponseUnit: return new ExifLongArray(ExifTag.ColorResponseUnit); + case ExifTagValue.TileOffsets: return new ExifLongArray(ExifTag.TileOffsets); + case ExifTagValue.SMinSampleValue: return new ExifLongArray(ExifTag.SMinSampleValue); + case ExifTagValue.SMaxSampleValue: return new ExifLongArray(ExifTag.SMaxSampleValue); + case ExifTagValue.JPEGQTables: return new ExifLongArray(ExifTag.JPEGQTables); + case ExifTagValue.JPEGDCTables: return new ExifLongArray(ExifTag.JPEGDCTables); + case ExifTagValue.JPEGACTables: return new ExifLongArray(ExifTag.JPEGACTables); + case ExifTagValue.StripRowCounts: return new ExifLongArray(ExifTag.StripRowCounts); + case ExifTagValue.IntergraphRegisters: return new ExifLongArray(ExifTag.IntergraphRegisters); + case ExifTagValue.TimeZoneOffset: return new ExifLongArray(ExifTag.TimeZoneOffset); + + case ExifTagValue.ImageWidth: return new ExifNumber(ExifTag.ImageWidth); + case ExifTagValue.ImageLength: return new ExifNumber(ExifTag.ImageLength); + case ExifTagValue.TileWidth: return new ExifNumber(ExifTag.TileWidth); + case ExifTagValue.TileLength: return new ExifNumber(ExifTag.TileLength); + case ExifTagValue.BadFaxLines: return new ExifNumber(ExifTag.BadFaxLines); + case ExifTagValue.ConsecutiveBadFaxLines: return new ExifNumber(ExifTag.ConsecutiveBadFaxLines); + case ExifTagValue.PixelXDimension: return new ExifNumber(ExifTag.PixelXDimension); + case ExifTagValue.PixelYDimension: return new ExifNumber(ExifTag.PixelYDimension); + + case ExifTagValue.StripOffsets: return new ExifNumberArray(ExifTag.StripOffsets); + case ExifTagValue.TileByteCounts: return new ExifNumberArray(ExifTag.TileByteCounts); + case ExifTagValue.ImageLayer: return new ExifNumberArray(ExifTag.ImageLayer); + + case ExifTagValue.XPosition: return new ExifRational(ExifTag.XPosition); + case ExifTagValue.YPosition: return new ExifRational(ExifTag.YPosition); + case ExifTagValue.XResolution: return new ExifRational(ExifTag.XResolution); + case ExifTagValue.YResolution: return new ExifRational(ExifTag.YResolution); + case ExifTagValue.BatteryLevel: return new ExifRational(ExifTag.BatteryLevel); + case ExifTagValue.ExposureTime: return new ExifRational(ExifTag.ExposureTime); + case ExifTagValue.FNumber: return new ExifRational(ExifTag.FNumber); + case ExifTagValue.MDScalePixel: return new ExifRational(ExifTag.MDScalePixel); + case ExifTagValue.CompressedBitsPerPixel: return new ExifRational(ExifTag.CompressedBitsPerPixel); + case ExifTagValue.ApertureValue: return new ExifRational(ExifTag.ApertureValue); + case ExifTagValue.MaxApertureValue: return new ExifRational(ExifTag.MaxApertureValue); + case ExifTagValue.SubjectDistance: return new ExifRational(ExifTag.SubjectDistance); + case ExifTagValue.FocalLength: return new ExifRational(ExifTag.FocalLength); + case ExifTagValue.FlashEnergy2: return new ExifRational(ExifTag.FlashEnergy2); + case ExifTagValue.FocalPlaneXResolution2: return new ExifRational(ExifTag.FocalPlaneXResolution2); + case ExifTagValue.FocalPlaneYResolution2: return new ExifRational(ExifTag.FocalPlaneYResolution2); + case ExifTagValue.ExposureIndex2: return new ExifRational(ExifTag.ExposureIndex2); + case ExifTagValue.Humidity: return new ExifRational(ExifTag.Humidity); + case ExifTagValue.Pressure: return new ExifRational(ExifTag.Pressure); + case ExifTagValue.Acceleration: return new ExifRational(ExifTag.Acceleration); + case ExifTagValue.FlashEnergy: return new ExifRational(ExifTag.FlashEnergy); + case ExifTagValue.FocalPlaneXResolution: return new ExifRational(ExifTag.FocalPlaneXResolution); + case ExifTagValue.FocalPlaneYResolution: return new ExifRational(ExifTag.FocalPlaneYResolution); + case ExifTagValue.ExposureIndex: return new ExifRational(ExifTag.ExposureIndex); + case ExifTagValue.DigitalZoomRatio: return new ExifRational(ExifTag.DigitalZoomRatio); + case ExifTagValue.GPSAltitude: return new ExifRational(ExifTag.GPSAltitude); + case ExifTagValue.GPSDOP: return new ExifRational(ExifTag.GPSDOP); + case ExifTagValue.GPSSpeed: return new ExifRational(ExifTag.GPSSpeed); + case ExifTagValue.GPSTrack: return new ExifRational(ExifTag.GPSTrack); + case ExifTagValue.GPSImgDirection: return new ExifRational(ExifTag.GPSImgDirection); + case ExifTagValue.GPSDestBearing: return new ExifRational(ExifTag.GPSDestBearing); + case ExifTagValue.GPSDestDistance: return new ExifRational(ExifTag.GPSDestDistance); + + case ExifTagValue.WhitePoint: return new ExifRationalArray(ExifTag.WhitePoint); + case ExifTagValue.PrimaryChromaticities: return new ExifRationalArray(ExifTag.PrimaryChromaticities); + case ExifTagValue.YCbCrCoefficients: return new ExifRationalArray(ExifTag.YCbCrCoefficients); + case ExifTagValue.ReferenceBlackWhite: return new ExifRationalArray(ExifTag.ReferenceBlackWhite); + case ExifTagValue.GPSLatitude: return new ExifRationalArray(ExifTag.GPSLatitude); + case ExifTagValue.GPSLongitude: return new ExifRationalArray(ExifTag.GPSLongitude); + case ExifTagValue.GPSTimestamp: return new ExifRationalArray(ExifTag.GPSTimestamp); + case ExifTagValue.GPSDestLatitude: return new ExifRationalArray(ExifTag.GPSDestLatitude); + case ExifTagValue.GPSDestLongitude: return new ExifRationalArray(ExifTag.GPSDestLongitude); + case ExifTagValue.LensSpecification: return new ExifRationalArray(ExifTag.LensSpecification); + + case ExifTagValue.OldSubfileType: return new ExifShort(ExifTag.OldSubfileType); + case ExifTagValue.Compression: return new ExifShort(ExifTag.Compression); + case ExifTagValue.PhotometricInterpretation: return new ExifShort(ExifTag.PhotometricInterpretation); + case ExifTagValue.Thresholding: return new ExifShort(ExifTag.Thresholding); + case ExifTagValue.CellWidth: return new ExifShort(ExifTag.CellWidth); + case ExifTagValue.CellLength: return new ExifShort(ExifTag.CellLength); + case ExifTagValue.FillOrder: return new ExifShort(ExifTag.FillOrder); + case ExifTagValue.Orientation: return new ExifShort(ExifTag.Orientation); + case ExifTagValue.SamplesPerPixel: return new ExifShort(ExifTag.SamplesPerPixel); + case ExifTagValue.PlanarConfiguration: return new ExifShort(ExifTag.PlanarConfiguration); + case ExifTagValue.GrayResponseUnit: return new ExifShort(ExifTag.GrayResponseUnit); + case ExifTagValue.ResolutionUnit: return new ExifShort(ExifTag.ResolutionUnit); + case ExifTagValue.CleanFaxData: return new ExifShort(ExifTag.CleanFaxData); + case ExifTagValue.InkSet: return new ExifShort(ExifTag.InkSet); + case ExifTagValue.NumberOfInks: return new ExifShort(ExifTag.NumberOfInks); + case ExifTagValue.DotRange: return new ExifShort(ExifTag.DotRange); + case ExifTagValue.Indexed: return new ExifShort(ExifTag.Indexed); + case ExifTagValue.OPIProxy: return new ExifShort(ExifTag.OPIProxy); + case ExifTagValue.JPEGProc: return new ExifShort(ExifTag.JPEGProc); + case ExifTagValue.JPEGRestartInterval: return new ExifShort(ExifTag.JPEGRestartInterval); + case ExifTagValue.YCbCrPositioning: return new ExifShort(ExifTag.YCbCrPositioning); + case ExifTagValue.Rating: return new ExifShort(ExifTag.Rating); + case ExifTagValue.RatingPercent: return new ExifShort(ExifTag.RatingPercent); + case ExifTagValue.ExposureProgram: return new ExifShort(ExifTag.ExposureProgram); + case ExifTagValue.Interlace: return new ExifShort(ExifTag.Interlace); + case ExifTagValue.SelfTimerMode: return new ExifShort(ExifTag.SelfTimerMode); + case ExifTagValue.SensitivityType: return new ExifShort(ExifTag.SensitivityType); + case ExifTagValue.MeteringMode: return new ExifShort(ExifTag.MeteringMode); + case ExifTagValue.LightSource: return new ExifShort(ExifTag.LightSource); + case ExifTagValue.FocalPlaneResolutionUnit2: return new ExifShort(ExifTag.FocalPlaneResolutionUnit2); + case ExifTagValue.SensingMethod2: return new ExifShort(ExifTag.SensingMethod2); + case ExifTagValue.Flash: return new ExifShort(ExifTag.Flash); + case ExifTagValue.ColorSpace: return new ExifShort(ExifTag.ColorSpace); + case ExifTagValue.FocalPlaneResolutionUnit: return new ExifShort(ExifTag.FocalPlaneResolutionUnit); + case ExifTagValue.SensingMethod: return new ExifShort(ExifTag.SensingMethod); + case ExifTagValue.CustomRendered: return new ExifShort(ExifTag.CustomRendered); + case ExifTagValue.ExposureMode: return new ExifShort(ExifTag.ExposureMode); + case ExifTagValue.WhiteBalance: return new ExifShort(ExifTag.WhiteBalance); + case ExifTagValue.FocalLengthIn35mmFilm: return new ExifShort(ExifTag.FocalLengthIn35mmFilm); + case ExifTagValue.SceneCaptureType: return new ExifShort(ExifTag.SceneCaptureType); + case ExifTagValue.GainControl: return new ExifShort(ExifTag.GainControl); + case ExifTagValue.Contrast: return new ExifShort(ExifTag.Contrast); + case ExifTagValue.Saturation: return new ExifShort(ExifTag.Saturation); + case ExifTagValue.Sharpness: return new ExifShort(ExifTag.Sharpness); + case ExifTagValue.SubjectDistanceRange: return new ExifShort(ExifTag.SubjectDistanceRange); + case ExifTagValue.GPSDifferential: return new ExifShort(ExifTag.GPSDifferential); + + case ExifTagValue.BitsPerSample: return new ExifShortArray(ExifTag.BitsPerSample); + case ExifTagValue.MinSampleValue: return new ExifShortArray(ExifTag.MinSampleValue); + case ExifTagValue.MaxSampleValue: return new ExifShortArray(ExifTag.MaxSampleValue); + case ExifTagValue.GrayResponseCurve: return new ExifShortArray(ExifTag.GrayResponseCurve); + case ExifTagValue.ColorMap: return new ExifShortArray(ExifTag.ColorMap); + case ExifTagValue.ExtraSamples: return new ExifShortArray(ExifTag.ExtraSamples); + case ExifTagValue.PageNumber: return new ExifShortArray(ExifTag.PageNumber); + case ExifTagValue.TransferFunction: return new ExifShortArray(ExifTag.TransferFunction); + case ExifTagValue.Predictor: return new ExifShortArray(ExifTag.Predictor); + case ExifTagValue.HalftoneHints: return new ExifShortArray(ExifTag.HalftoneHints); + case ExifTagValue.SampleFormat: return new ExifShortArray(ExifTag.SampleFormat); + case ExifTagValue.TransferRange: return new ExifShortArray(ExifTag.TransferRange); + case ExifTagValue.DefaultImageColor: return new ExifShortArray(ExifTag.DefaultImageColor); + case ExifTagValue.JPEGLosslessPredictors: return new ExifShortArray(ExifTag.JPEGLosslessPredictors); + case ExifTagValue.JPEGPointTransforms: return new ExifShortArray(ExifTag.JPEGPointTransforms); + case ExifTagValue.YCbCrSubsampling: return new ExifShortArray(ExifTag.YCbCrSubsampling); + case ExifTagValue.CFARepeatPatternDim: return new ExifShortArray(ExifTag.CFARepeatPatternDim); + case ExifTagValue.IntergraphPacketData: return new ExifShortArray(ExifTag.IntergraphPacketData); + case ExifTagValue.ISOSpeedRatings: return new ExifShortArray(ExifTag.ISOSpeedRatings); + case ExifTagValue.SubjectArea: return new ExifShortArray(ExifTag.SubjectArea); + case ExifTagValue.SubjectLocation: return new ExifShortArray(ExifTag.SubjectLocation); + + case ExifTagValue.ShutterSpeedValue: return new ExifSignedRational(ExifTag.ShutterSpeedValue); + case ExifTagValue.BrightnessValue: return new ExifSignedRational(ExifTag.BrightnessValue); + case ExifTagValue.ExposureBiasValue: return new ExifSignedRational(ExifTag.ExposureBiasValue); + case ExifTagValue.AmbientTemperature: return new ExifSignedRational(ExifTag.AmbientTemperature); + case ExifTagValue.WaterDepth: return new ExifSignedRational(ExifTag.WaterDepth); + case ExifTagValue.CameraElevationAngle: return new ExifSignedRational(ExifTag.CameraElevationAngle); + + case ExifTagValue.Decode: return new ExifSignedRationalArray(ExifTag.Decode); + + case ExifTagValue.ImageDescription: return new ExifString(ExifTag.ImageDescription); + case ExifTagValue.Make: return new ExifString(ExifTag.Make); + case ExifTagValue.Model: return new ExifString(ExifTag.Model); + case ExifTagValue.Software: return new ExifString(ExifTag.Software); + case ExifTagValue.DateTime: return new ExifString(ExifTag.DateTime); + case ExifTagValue.Artist: return new ExifString(ExifTag.Artist); + case ExifTagValue.HostComputer: return new ExifString(ExifTag.HostComputer); + case ExifTagValue.Copyright: return new ExifString(ExifTag.Copyright); + case ExifTagValue.DocumentName: return new ExifString(ExifTag.DocumentName); + case ExifTagValue.PageName: return new ExifString(ExifTag.PageName); + case ExifTagValue.InkNames: return new ExifString(ExifTag.InkNames); + case ExifTagValue.TargetPrinter: return new ExifString(ExifTag.TargetPrinter); + case ExifTagValue.ImageID: return new ExifString(ExifTag.ImageID); + case ExifTagValue.MDLabName: return new ExifString(ExifTag.MDLabName); + case ExifTagValue.MDSampleInfo: return new ExifString(ExifTag.MDSampleInfo); + case ExifTagValue.MDPrepDate: return new ExifString(ExifTag.MDPrepDate); + case ExifTagValue.MDPrepTime: return new ExifString(ExifTag.MDPrepTime); + case ExifTagValue.MDFileUnits: return new ExifString(ExifTag.MDFileUnits); + case ExifTagValue.SEMInfo: return new ExifString(ExifTag.SEMInfo); + case ExifTagValue.SpectralSensitivity: return new ExifString(ExifTag.SpectralSensitivity); + case ExifTagValue.DateTimeOriginal: return new ExifString(ExifTag.DateTimeOriginal); + case ExifTagValue.DateTimeDigitized: return new ExifString(ExifTag.DateTimeDigitized); + case ExifTagValue.SubsecTime: return new ExifString(ExifTag.SubsecTime); + case ExifTagValue.SubsecTimeOriginal: return new ExifString(ExifTag.SubsecTimeOriginal); + case ExifTagValue.SubsecTimeDigitized: return new ExifString(ExifTag.SubsecTimeDigitized); + case ExifTagValue.RelatedSoundFile: return new ExifString(ExifTag.RelatedSoundFile); + case ExifTagValue.FaxSubaddress: return new ExifString(ExifTag.FaxSubaddress); + case ExifTagValue.OffsetTime: return new ExifString(ExifTag.OffsetTime); + case ExifTagValue.OffsetTimeOriginal: return new ExifString(ExifTag.OffsetTimeOriginal); + case ExifTagValue.OffsetTimeDigitized: return new ExifString(ExifTag.OffsetTimeDigitized); + case ExifTagValue.SecurityClassification: return new ExifString(ExifTag.SecurityClassification); + case ExifTagValue.ImageHistory: return new ExifString(ExifTag.ImageHistory); + case ExifTagValue.ImageUniqueID: return new ExifString(ExifTag.ImageUniqueID); + case ExifTagValue.OwnerName: return new ExifString(ExifTag.OwnerName); + case ExifTagValue.SerialNumber: return new ExifString(ExifTag.SerialNumber); + case ExifTagValue.LensMake: return new ExifString(ExifTag.LensMake); + case ExifTagValue.LensModel: return new ExifString(ExifTag.LensModel); + case ExifTagValue.LensSerialNumber: return new ExifString(ExifTag.LensSerialNumber); + case ExifTagValue.GDALMetadata: return new ExifString(ExifTag.GDALMetadata); + case ExifTagValue.GDALNoData: return new ExifString(ExifTag.GDALNoData); + case ExifTagValue.GPSLatitudeRef: return new ExifString(ExifTag.GPSLatitudeRef); + case ExifTagValue.GPSLongitudeRef: return new ExifString(ExifTag.GPSLongitudeRef); + case ExifTagValue.GPSSatellites: return new ExifString(ExifTag.GPSSatellites); + case ExifTagValue.GPSStatus: return new ExifString(ExifTag.GPSStatus); + case ExifTagValue.GPSMeasureMode: return new ExifString(ExifTag.GPSMeasureMode); + case ExifTagValue.GPSSpeedRef: return new ExifString(ExifTag.GPSSpeedRef); + case ExifTagValue.GPSTrackRef: return new ExifString(ExifTag.GPSTrackRef); + case ExifTagValue.GPSImgDirectionRef: return new ExifString(ExifTag.GPSImgDirectionRef); + case ExifTagValue.GPSMapDatum: return new ExifString(ExifTag.GPSMapDatum); + case ExifTagValue.GPSDestLatitudeRef: return new ExifString(ExifTag.GPSDestLatitudeRef); + case ExifTagValue.GPSDestLongitudeRef: return new ExifString(ExifTag.GPSDestLongitudeRef); + case ExifTagValue.GPSDestBearingRef: return new ExifString(ExifTag.GPSDestBearingRef); + case ExifTagValue.GPSDestDistanceRef: return new ExifString(ExifTag.GPSDestDistanceRef); + case ExifTagValue.GPSDateStamp: return new ExifString(ExifTag.GPSDateStamp); + + case ExifTagValue.FileSource: return new ExifByte(ExifTag.FileSource, ExifDataType.Undefined); + case ExifTagValue.SceneType: return new ExifByte(ExifTag.SceneType, ExifDataType.Undefined); + + case ExifTagValue.JPEGTables: return new ExifByteArray(ExifTag.JPEGTables, ExifDataType.Undefined); + case ExifTagValue.OECF: return new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined); + case ExifTagValue.ExifVersion: return new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined); + case ExifTagValue.ComponentsConfiguration: return new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined); + case ExifTagValue.MakerNote: return new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined); + case ExifTagValue.UserComment: return new ExifByteArray(ExifTag.UserComment, ExifDataType.Undefined); + case ExifTagValue.FlashpixVersion: return new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined); + case ExifTagValue.SpatialFrequencyResponse: return new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined); + case ExifTagValue.SpatialFrequencyResponse2: return new ExifByteArray(ExifTag.SpatialFrequencyResponse2, ExifDataType.Undefined); + case ExifTagValue.Noise: return new ExifByteArray(ExifTag.Noise, ExifDataType.Undefined); + case ExifTagValue.CFAPattern: return new ExifByteArray(ExifTag.CFAPattern, ExifDataType.Undefined); + case ExifTagValue.DeviceSettingDescription: return new ExifByteArray(ExifTag.DeviceSettingDescription, ExifDataType.Undefined); + case ExifTagValue.ImageSourceData: return new ExifByteArray(ExifTag.ImageSourceData, ExifDataType.Undefined); + case ExifTagValue.GPSProcessingMethod: return new ExifByteArray(ExifTag.GPSProcessingMethod, ExifDataType.Undefined); + case ExifTagValue.GPSAreaInformation: return new ExifByteArray(ExifTag.GPSAreaInformation, ExifDataType.Undefined); + + default: return null; + } + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs new file mode 100644 index 0000000000..fad42a8dd1 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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() + { + if (this.Value == null) + { + return null; + } + + string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, this.Value); + return description ?? this.StringValue; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs new file mode 100644 index 0000000000..fc51ebc38c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// A value of the exif profile. + /// + public interface IExifValue : IDeepCloneable + { + /// + /// Gets the data type of the exif value. + /// + ExifDataType DataType { get; } + + /// + /// Gets a value indicating whether the value is an array. + /// + bool IsArray { get; } + + /// + /// Gets the tag of the exif value. + /// + ExifTag Tag { get; } + + /// + /// Gets the value of this exif value. + /// + /// The value of this exif value. + object GetValue(); + + /// + /// Sets the value of this exif value. + /// + /// The value of this exif value. + /// A value indicating whether the value could be set. + 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 new file mode 100644 index 0000000000..8cd694dd8f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..c682839145 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// 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..1ba521f1a8 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// 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..a5a94b3f87 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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); + } + } +} 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..204c7563b7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors. +// 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..d38cf46688 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 new file mode 100644 index 0000000000..d79f8c7dc0 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// 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..785a110f8c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs @@ -0,0 +1,219 @@ +// Copyright (c) Six Labors. +// 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 + var breakPoints = new float[segmentCount - 1]; + for (int i = 0; i < breakPoints.Length; i++) + { + breakPoints[i] = this.ReadSingle(); + } + + var 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(); + var measurement = new uint[channelCount]; + for (int i = 0; i < channelCount; i++) + { + measurement[i] = this.ReadUInt32(); + } + + var xyzValues = new Vector3[channelCount]; + for (int i = 0; i < channelCount; i++) + { + xyzValues[i] = this.ReadXyzNumber(); + } + + var 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(); + var 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..bbd87b1f6e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs @@ -0,0 +1,171 @@ +// Copyright (c) Six Labors. +// 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) + { + var 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 + var 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; + + var 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; + + var 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; + + var 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); + } + } +} 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..9c9b3678fe --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// 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) + { + var 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) + { + var 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..beb399d69a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors. +// 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) + { + var 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..3347cb0a0a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs @@ -0,0 +1,181 @@ +// Copyright (c) Six Labors. +// 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() }; + var 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..9313928366 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors. +// 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) + { + var 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..20d2b2adc7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -0,0 +1,902 @@ +// Copyright (c) Six Labors. +// 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[] { 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(); + 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; + + this.ReadUInt16(); + 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); + } + } +} 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..925a86ac20 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors. +// 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 + { + /// + /// 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; + } + } +} 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..ffd7ee9988 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs @@ -0,0 +1,176 @@ +// Copyright (c) Six Labors. +// 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); + + 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 new file mode 100644 index 0000000000..a5eef3d237 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors. +// 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..aa28d25aae --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -0,0 +1,158 @@ +// Copyright (c) Six Labors. +// 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 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..aaaf0c5317 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// 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((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 new file mode 100644 index 0000000000..53dd5f008b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs @@ -0,0 +1,132 @@ +// Copyright (c) Six Labors. +// 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); + + 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..5fb8e57d2f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs @@ -0,0 +1,246 @@ +// Copyright (c) Six Labors. +// 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..fdbf2a4778 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs @@ -0,0 +1,1003 @@ +// Copyright (c) Six Labors. +// 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((IccChromaticityTagDataEntry)entry); + break; + case IccTypeSignature.ColorantOrder: + count += this.WriteColorantOrderTagDataEntry((IccColorantOrderTagDataEntry)entry); + break; + case IccTypeSignature.ColorantTable: + count += this.WriteColorantTableTagDataEntry((IccColorantTableTagDataEntry)entry); + break; + case IccTypeSignature.Curve: + count += this.WriteCurveTagDataEntry((IccCurveTagDataEntry)entry); + break; + case IccTypeSignature.Data: + count += this.WriteDataTagDataEntry((IccDataTagDataEntry)entry); + break; + case IccTypeSignature.DateTime: + count += this.WriteDateTimeTagDataEntry((IccDateTimeTagDataEntry)entry); + break; + case IccTypeSignature.Lut16: + count += this.WriteLut16TagDataEntry((IccLut16TagDataEntry)entry); + break; + case IccTypeSignature.Lut8: + count += this.WriteLut8TagDataEntry((IccLut8TagDataEntry)entry); + break; + case IccTypeSignature.LutAToB: + count += this.WriteLutAtoBTagDataEntry((IccLutAToBTagDataEntry)entry); + break; + case IccTypeSignature.LutBToA: + count += this.WriteLutBtoATagDataEntry((IccLutBToATagDataEntry)entry); + break; + case IccTypeSignature.Measurement: + count += this.WriteMeasurementTagDataEntry((IccMeasurementTagDataEntry)entry); + break; + case IccTypeSignature.MultiLocalizedUnicode: + count += this.WriteMultiLocalizedUnicodeTagDataEntry((IccMultiLocalizedUnicodeTagDataEntry)entry); + break; + case IccTypeSignature.MultiProcessElements: + count += this.WriteMultiProcessElementsTagDataEntry((IccMultiProcessElementsTagDataEntry)entry); + break; + case IccTypeSignature.NamedColor2: + count += this.WriteNamedColor2TagDataEntry((IccNamedColor2TagDataEntry)entry); + break; + case IccTypeSignature.ParametricCurve: + count += this.WriteParametricCurveTagDataEntry((IccParametricCurveTagDataEntry)entry); + break; + case IccTypeSignature.ProfileSequenceDesc: + count += this.WriteProfileSequenceDescTagDataEntry((IccProfileSequenceDescTagDataEntry)entry); + break; + case IccTypeSignature.ProfileSequenceIdentifier: + count += this.WriteProfileSequenceIdentifierTagDataEntry((IccProfileSequenceIdentifierTagDataEntry)entry); + break; + case IccTypeSignature.ResponseCurveSet16: + count += this.WriteResponseCurveSet16TagDataEntry((IccResponseCurveSet16TagDataEntry)entry); + break; + case IccTypeSignature.S15Fixed16Array: + count += this.WriteFix16ArrayTagDataEntry((IccFix16ArrayTagDataEntry)entry); + break; + case IccTypeSignature.Signature: + count += this.WriteSignatureTagDataEntry((IccSignatureTagDataEntry)entry); + break; + case IccTypeSignature.Text: + count += this.WriteTextTagDataEntry((IccTextTagDataEntry)entry); + break; + case IccTypeSignature.U16Fixed16Array: + count += this.WriteUFix16ArrayTagDataEntry((IccUFix16ArrayTagDataEntry)entry); + break; + case IccTypeSignature.UInt16Array: + count += this.WriteUInt16ArrayTagDataEntry((IccUInt16ArrayTagDataEntry)entry); + break; + case IccTypeSignature.UInt32Array: + count += this.WriteUInt32ArrayTagDataEntry((IccUInt32ArrayTagDataEntry)entry); + break; + case IccTypeSignature.UInt64Array: + count += this.WriteUInt64ArrayTagDataEntry((IccUInt64ArrayTagDataEntry)entry); + break; + case IccTypeSignature.UInt8Array: + count += this.WriteUInt8ArrayTagDataEntry((IccUInt8ArrayTagDataEntry)entry); + break; + case IccTypeSignature.ViewingConditions: + count += this.WriteViewingConditionsTagDataEntry((IccViewingConditionsTagDataEntry)entry); + break; + case IccTypeSignature.Xyz: + count += this.WriteXyzTagDataEntry((IccXyzTagDataEntry)entry); + break; + + // V2 Types: + case IccTypeSignature.TextDescription: + count += this.WriteTextDescriptionTagDataEntry((IccTextDescriptionTagDataEntry)entry); + break; + case IccTypeSignature.CrdInfo: + count += this.WriteCrdInfoTagDataEntry((IccCrdInfoTagDataEntry)entry); + break; + case IccTypeSignature.Screening: + count += this.WriteScreeningTagDataEntry((IccScreeningTagDataEntry)entry); + break; + case IccTypeSignature.UcrBg: + count += this.WriteUcrBgTagDataEntry((IccUcrBgTagDataEntry)entry); + 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) => 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) => 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; + + // TODO: Investigate cost of Linq GroupBy + IGrouping[] texts = value.Texts.GroupBy(t => t.Text).ToArray(); + + var offset = new uint[texts.Length]; + var 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; + + var 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; + 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; + + var 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) + { + 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..a3e4f55570 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs @@ -0,0 +1,247 @@ +// Copyright (c) Six Labors. +// 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..78b8c9fa71 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// 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..413bfff754 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors. +// 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..fe07c47ee7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// 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..7d51b7889a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// 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..d84f2a57d8 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// 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..a0c1c3b2a4 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// 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..c906b1bb32 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// 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..4372353213 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// 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..90579b71f6 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// 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..4e279fcc0b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// 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..d715b2be36 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// 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..cd17605c62 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// 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..9efe1ac8f2 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// 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..8a88e3dd16 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// 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..5ef7f8a1e1 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs @@ -0,0 +1,362 @@ +// Copyright (c) Six Labors. +// 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..33406b5a06 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// 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..8cd9a7ae75 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// 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..95bbf2d0b1 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// 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..7b153dd145 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs @@ -0,0 +1,176 @@ +// Copyright (c) Six Labors. +// 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..7897f59227 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// 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..5a68f54a0b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..7142f2fc3c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs @@ -0,0 +1,271 @@ +// Copyright (c) Six Labors. +// 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..cb08d116d1 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// 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..718e4cbe8e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs @@ -0,0 +1,219 @@ +// Copyright (c) Six Labors. +// 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 + var 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) + { + var 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); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs new file mode 100644 index 0000000000..898490fbab --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// 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..dd1c72c8b7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs @@ -0,0 +1,150 @@ +// Copyright (c) Six Labors. +// 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..b1783cfe4e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// 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..9cd0c1793f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors. +// 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) + { + // 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)); + + 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..387a1a1ea0 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// 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..59295c661f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// 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..8270786ed5 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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); + } +} \ 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..57fb4bd548 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// 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..6ebeca79d3 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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..e3ab9f0ab8 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// 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..508b3f9adb --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors. +// 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) => 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 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].AsSpan().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..df7c6b8e8f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// 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..0e096f0cbf --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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); + } +} \ 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..f411989e23 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs @@ -0,0 +1,133 @@ +// Copyright (c) Six Labors. +// 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..24e57ec8e6 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors. +// 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..1b885c590b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// 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..af837237ed --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// 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..45d6865c3b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// 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..8ec19d1c6a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs @@ -0,0 +1,170 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 new file mode 100644 index 0000000000..816462772d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs @@ -0,0 +1,174 @@ +// Copyright (c) Six Labors. +// 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) => 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() + { + 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..7a3b35fea5 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -0,0 +1,300 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +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) + { + 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) => 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() + { + 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() => 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..f72970cb85 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -0,0 +1,299 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +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) + { + 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) => 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) => 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 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() => 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..bb53e07579 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors. +// 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..ca713b4ed5 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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); + } +} \ 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..3dd05ca42b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// 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) + => 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); + } + } +} \ 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..64d04da91b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs @@ -0,0 +1,167 @@ +// Copyright (c) Six Labors. +// 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.AsSpan().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..25bb64dbd6 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// 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..54e5f667c1 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// 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 + /// 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 new file mode 100644 index 0000000000..e15529e8fb --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// 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..6255661404 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// 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) => 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); + } + } +} \ 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..9fe044ddb3 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors. +// 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..1b4338e2e2 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// 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..15d1e7264e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs @@ -0,0 +1,178 @@ +// Copyright (c) Six Labors. +// 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) + { + var 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); + } + } +} 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..2ee339a5f6 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// 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..2b8ec2c7e6 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// 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..9396bbc352 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// 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..38b76fd343 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// 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..f1eb93c46c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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); + } +} \ 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..1640999a32 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// 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..7634255542 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// 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..f811af2982 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// 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..826340c90f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// 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..90ef832510 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +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); + } + } +} 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..0e37b0e2d7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// 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) => 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..c90ec28a0d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors. +// 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..5a66de54db --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// 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..76c0990292 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// 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..d15833fdfa --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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) + { + return this.Name.Equals(other.Name) + && this.PcsCoordinates.AsSpan().SequenceEqual(other.PcsCoordinates) + && this.DeviceCoordinates.AsSpan().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..555b336484 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// 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..baf84b128d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 new file mode 100644 index 0000000000..dcac6fa481 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// 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..5a352f38f2 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 new file mode 100644 index 0000000000..29cf65f256 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// 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..d8e3605122 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// 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..bc272e418e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// 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..77f3646923 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// 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/Profiles/IPTC/IIMV4.2_IPTC.pdf b/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf new file mode 100644 index 0000000000..b00355181c Binary files /dev/null and b/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf differ diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs new file mode 100644 index 0000000000..e31842c537 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -0,0 +1,298 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +{ + /// + /// Represents an IPTC profile providing access to the collection of values. + /// + public sealed class IptcProfile : IDeepCloneable + { + private Collection values; + + /// + /// 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)); + + if (other.values != null) + { + this.values = new Collection(); + + 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 the byte data of the IPTC profile. + /// + public byte[] Data { get; private set; } + + /// + /// Gets the values of this iptc profile. + /// + public IEnumerable Values + { + get + { + this.Initialize(); + return this.values; + } + } + + /// + public IptcProfile DeepClone() => new IptcProfile(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) + { + var iptcValues = new List(); + 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) + { + this.Initialize(); + + 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) + { + this.Initialize(); + + 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)) + { + 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)); + } + + /// + /// 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. + public void SetDateTimeValue(IptcTag tag, DateTimeOffset dateTimeOffset) + { + if (!tag.IsDate() && !tag.IsTime()) + { + throw new ArgumentException("iptc tag is not a time or date type"); + } + + var formattedDate = tag.IsDate() + ? dateTimeOffset.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture) + : dateTimeOffset.ToString("HHmmsszzzz", System.Globalization.CultureInfo.InvariantCulture) + .Replace(":", string.Empty); + + this.SetValue(tag, Encoding.UTF8, formattedDate); + } + + /// + /// 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); + + /// + /// Updates the data of the profile. + /// + public void UpdateData() + { + var length = 0; + foreach (IptcValue value in this.Values) + { + length += value.Length + 5; + } + + this.Data = new byte[length]; + + int i = 0; + foreach (IptcValue value in this.Values) + { + this.Data[i++] = 28; + this.Data[i++] = 2; + this.Data[i++] = (byte)value.Tag; + this.Data[i++] = (byte)(value.Length >> 8); + this.Data[i++] = (byte)value.Length; + if (value.Length > 0) + { + Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length); + i += value.Length; + } + } + } + + private void Initialize() + { + if (this.values != null) + { + return; + } + + this.values = new Collection(); + + if (this.Data == null || this.Data[0] != 0x1c) + { + return; + } + + int i = 0; + while (i + 4 < this.Data.Length) + { + if (this.Data[i++] != 28) + { + continue; + } + + i++; + + var tag = (IptcTag)this.Data[i++]; + + int count = BinaryPrimitives.ReadInt16BigEndian(this.Data.AsSpan(i, 2)); + i += 2; + + var iptcData = new byte[count]; + if ((count > 0) && (i + count <= this.Data.Length)) + { + Buffer.BlockCopy(this.Data, i, iptcData, 0, count); + this.values.Add(new IptcValue(tag, iptcData, false)); + } + + i += count; + } + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs new file mode 100644 index 0000000000..084eb8de4b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs @@ -0,0 +1,397 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..b670591df7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs @@ -0,0 +1,162 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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) + { + return 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 new file mode 100644 index 0000000000..9e409ca064 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs @@ -0,0 +1,219 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +{ + /// + /// Represents a single value of the IPTC profile. + /// + public sealed class IptcValue : IDeepCloneable + { + private byte[] data = Array.Empty(); + private Encoding encoding; + + internal IptcValue(IptcValue other) + { + if (other.data != null) + { + this.data = new byte[other.data.Length]; + other.data.AsSpan().CopyTo(this.data); + } + + if (other.Encoding != null) + { + 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 = Array.Empty(); + } + else + { + int maxLength = this.Tag.MaxLength(); + byte[] valueBytes; + if (this.Strict && value.Length > maxLength) + { + var cappedValue = value.Substring(0, 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 IptcValue(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() + { + var 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 new file mode 100644 index 0000000000..1217ca0c70 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/README.md @@ -0,0 +1,11 @@ +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/PixelFormats/ColorBuilder{TPixel}.cs b/src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs deleted file mode 100644 index 2572b32933..0000000000 --- a/src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs +++ /dev/null @@ -1,104 +0,0 @@ -// 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 deleted file mode 100644 index 14df385697..0000000000 --- a/src/ImageSharp/PixelFormats/ColorConstants.cs +++ /dev/null @@ -1,278 +0,0 @@ -// 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 e8cfaa462e..534f609996 100644 --- a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs +++ b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs b/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs index 6775cbc589..64560a5727 100644 --- a/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs +++ b/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 1277406869..12b5bc7848 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.PixelFormats ///
/// The type implementing this interface public interface IPixel : IPixel, IEquatable - where TSelf : struct, IPixel + where TSelf : unmanaged, IPixel { /// /// Creates a instance for this pixel type. @@ -61,6 +61,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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. /// @@ -74,16 +80,28 @@ namespace SixLabors.ImageSharp.PixelFormats void FromBgra32(Bgra32 source); /// - /// Initializes the pixel instance from an value. + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromL8(L8 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromL16(L16 source); + + /// + /// Initializes the pixel instance from an value. /// - /// The value. - void FromGray8(Gray8 source); + /// The value. + void FromLa16(La16 source); /// - /// Initializes the pixel instance from an value. + /// Initializes the pixel instance from an value. /// - /// The value. - void FromGray16(Gray16 source); + /// The value. + void FromLa32(La32 source); /// /// Initializes the pixel instance from an value. @@ -115,4 +133,4 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 deleted file mode 100644 index 7e093de042..0000000000 --- a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs +++ /dev/null @@ -1,761 +0,0 @@ -// 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 2758a74808..6241187782 100644 --- a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs +++ b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.PixelFormats @@ -7,19 +7,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// Enumerates the various alpha composition modes. /// public enum PixelAlphaCompositionMode - { + { /// - /// returns the destination over the source. + /// Returns the destination over the source. /// - SrcOver = 0, - + SrcOver = 0, + /// - /// returns the source colors. + /// Returns the source colors. /// Src, /// - /// returns the source over the destination. + /// Returns the source over the destination. /// SrcAtop, @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.PixelFormats Dest, /// - /// The source where they don't overlap othersie dest in overlapping parts. + /// The source where they don't overlap otherwise dest in overlapping parts. /// DestAtop, diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 0cf8d6bbbf..2cb528a036 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -1,13 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { @@ -23,10 +19,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// to be opaque /// internal static class DefaultPixelBlenders - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - internal class NormalSrc : PixelBlender + /// + /// A pixel blender that implements the "NormalSrc" composition equation. + /// + public class NormalSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -38,30 +37,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplySrc" composition equation. + /// + public class MultiplySrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -73,30 +75,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddSrc" composition equation. + /// + public class AddSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -108,30 +113,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractSrc" composition equation. + /// + public class SubtractSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -143,30 +151,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenSrc" composition equation. + /// + public class ScreenSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -178,30 +189,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenSrc" composition equation. + /// + public class DarkenSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -213,30 +227,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenSrc" composition equation. + /// + public class LightenSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -248,30 +265,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlaySrc" composition equation. + /// + public class OverlaySrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -283,30 +303,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightSrc" composition equation. + /// + public class HardLightSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -318,30 +341,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalSrcAtop" composition equation. + /// + public class NormalSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -353,30 +379,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplySrcAtop" composition equation. + /// + public class MultiplySrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -388,30 +417,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddSrcAtop" composition equation. + /// + public class AddSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -423,30 +455,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractSrcAtop" composition equation. + /// + public class SubtractSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -458,30 +493,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenSrcAtop" composition equation. + /// + public class ScreenSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -493,30 +531,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenSrcAtop" composition equation. + /// + public class DarkenSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -528,30 +569,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenSrcAtop" composition equation. + /// + public class LightenSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -563,30 +607,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlaySrcAtop" composition equation. + /// + public class OverlaySrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -598,30 +645,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightSrcAtop" composition equation. + /// + public class HardLightSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -633,30 +683,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalSrcOver" composition equation. + /// + public class NormalSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -668,30 +721,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplySrcOver" composition equation. + /// + public class MultiplySrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -703,30 +759,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddSrcOver" composition equation. + /// + public class AddSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -738,30 +797,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractSrcOver" composition equation. + /// + public class SubtractSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -773,30 +835,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenSrcOver" composition equation. + /// + public class ScreenSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -808,30 +873,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenSrcOver" composition equation. + /// + public class DarkenSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -843,30 +911,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenSrcOver" composition equation. + /// + public class LightenSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -878,30 +949,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlaySrcOver" composition equation. + /// + public class OverlaySrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -913,30 +987,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightSrcOver" composition equation. + /// + public class HardLightSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -948,30 +1025,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalSrcIn" composition equation. + /// + public class NormalSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -983,30 +1063,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplySrcIn" composition equation. + /// + public class MultiplySrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1018,30 +1101,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddSrcIn" composition equation. + /// + public class AddSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1053,30 +1139,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractSrcIn" composition equation. + /// + public class SubtractSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1088,30 +1177,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenSrcIn" composition equation. + /// + public class ScreenSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1123,30 +1215,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenSrcIn" composition equation. + /// + public class DarkenSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1158,30 +1253,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenSrcIn" composition equation. + /// + public class LightenSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1193,30 +1291,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlaySrcIn" composition equation. + /// + public class OverlaySrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1228,30 +1329,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightSrcIn" composition equation. + /// + public class HardLightSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1263,30 +1367,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalSrcOut" composition equation. + /// + public class NormalSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1298,30 +1405,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplySrcOut" composition equation. + /// + public class MultiplySrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1333,30 +1443,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddSrcOut" composition equation. + /// + public class AddSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1368,30 +1481,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractSrcOut" composition equation. + /// + public class SubtractSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1403,30 +1519,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenSrcOut" composition equation. + /// + public class ScreenSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1438,30 +1557,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenSrcOut" composition equation. + /// + public class DarkenSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1473,30 +1595,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenSrcOut" composition equation. + /// + public class LightenSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1508,30 +1633,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlaySrcOut" composition equation. + /// + public class OverlaySrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1543,30 +1671,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightSrcOut" composition equation. + /// + public class HardLightSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1578,30 +1709,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalDest" composition equation. + /// + public class NormalDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1613,30 +1747,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplyDest" composition equation. + /// + public class MultiplyDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1648,30 +1785,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddDest" composition equation. + /// + public class AddDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1683,30 +1823,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractDest" composition equation. + /// + public class SubtractDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1718,30 +1861,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenDest" composition equation. + /// + public class ScreenDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1753,30 +1899,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenDest" composition equation. + /// + public class DarkenDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1788,30 +1937,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenDest" composition equation. + /// + public class LightenDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1823,30 +1975,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlayDest" composition equation. + /// + public class OverlayDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1858,30 +2013,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightDest" composition equation. + /// + public class HardLightDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1893,30 +2051,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalDestAtop" composition equation. + /// + public class NormalDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -1928,30 +2089,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplyDestAtop" composition equation. + /// + public class MultiplyDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -1963,30 +2127,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddDestAtop" composition equation. + /// + public class AddDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -1998,30 +2165,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractDestAtop" composition equation. + /// + public class SubtractDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2033,30 +2203,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenDestAtop" composition equation. + /// + public class ScreenDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2068,30 +2241,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenDestAtop" composition equation. + /// + public class DarkenDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2103,30 +2279,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenDestAtop" composition equation. + /// + public class LightenDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2138,30 +2317,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlayDestAtop" composition equation. + /// + public class OverlayDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2173,30 +2355,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightDestAtop" composition equation. + /// + public class HardLightDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2208,30 +2393,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalDestOver" composition equation. + /// + public class NormalDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2243,30 +2431,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplyDestOver" composition equation. + /// + public class MultiplyDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2278,30 +2469,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddDestOver" composition equation. + /// + public class AddDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2313,30 +2507,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractDestOver" composition equation. + /// + public class SubtractDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2348,30 +2545,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenDestOver" composition equation. + /// + public class ScreenDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2383,30 +2583,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenDestOver" composition equation. + /// + public class DarkenDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2418,30 +2621,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenDestOver" composition equation. + /// + public class LightenDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2453,30 +2659,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlayDestOver" composition equation. + /// + public class OverlayDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2488,30 +2697,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightDestOver" composition equation. + /// + public class HardLightDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2523,30 +2735,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalDestIn" composition equation. + /// + public class NormalDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2558,30 +2773,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplyDestIn" composition equation. + /// + public class MultiplyDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2593,30 +2811,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddDestIn" composition equation. + /// + public class AddDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2628,30 +2849,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractDestIn" composition equation. + /// + public class SubtractDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2663,30 +2887,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenDestIn" composition equation. + /// + public class ScreenDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2698,30 +2925,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenDestIn" composition equation. + /// + public class DarkenDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2733,30 +2963,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenDestIn" composition equation. + /// + public class LightenDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2768,30 +3001,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlayDestIn" composition equation. + /// + public class OverlayDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2803,30 +3039,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightDestIn" composition equation. + /// + public class HardLightDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2838,30 +3077,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalDestOut" composition equation. + /// + public class NormalDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -2873,30 +3115,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplyDestOut" composition equation. + /// + public class MultiplyDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -2908,30 +3153,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddDestOut" composition equation. + /// + public class AddDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -2943,30 +3191,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractDestOut" composition equation. + /// + public class SubtractDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -2978,30 +3229,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenDestOut" composition equation. + /// + public class ScreenDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3013,30 +3267,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenDestOut" composition equation. + /// + public class DarkenDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3048,30 +3305,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenDestOut" composition equation. + /// + public class LightenDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3083,30 +3343,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlayDestOut" composition equation. + /// + public class OverlayDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3118,30 +3381,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightDestOut" composition equation. + /// + public class HardLightDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3153,30 +3419,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalClear" composition equation. + /// + public class NormalClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3188,30 +3457,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplyClear" composition equation. + /// + public class MultiplyClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3223,30 +3495,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddClear" composition equation. + /// + public class AddClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3258,30 +3533,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractClear" composition equation. + /// + public class SubtractClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3293,30 +3571,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenClear" composition equation. + /// + public class ScreenClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3328,30 +3609,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenClear" composition equation. + /// + public class DarkenClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3363,30 +3647,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenClear" composition equation. + /// + public class LightenClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3398,30 +3685,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlayClear" composition equation. + /// + public class OverlayClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3433,30 +3723,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightClear" composition equation. + /// + public class HardLightClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3468,30 +3761,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "NormalXor" composition equation. + /// + public class NormalXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3503,30 +3799,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "MultiplyXor" composition equation. + /// + public class MultiplyXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3538,30 +3837,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "AddXor" composition equation. + /// + public class AddXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3573,30 +3875,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "SubtractXor" composition equation. + /// + public class SubtractXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3608,30 +3913,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "ScreenXor" composition equation. + /// + public class ScreenXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3643,30 +3951,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "DarkenXor" composition equation. + /// + public class DarkenXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3678,30 +3989,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "LightenXor" composition equation. + /// + public class LightenXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3713,30 +4027,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "OverlayXor" composition equation. + /// + public class OverlayXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3748,30 +4065,33 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 + /// + /// A pixel blender that implements the "HardLightXor" composition equation. + /// + public class HardLightXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3783,28 +4103,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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 2cca55e4c3..46990b53c9 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -1,5 +1,5 @@ -<# -// Copyright (c) Six Labors and contributors. +<# +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. #> <#@ template debug="false" hostspecific="false" language="C#" #> @@ -8,16 +8,12 @@ <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { @@ -33,11 +29,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// to be opaque /// internal static class DefaultPixelBlenders - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { <# - string[] composers = new []{ + var composers = new []{ "Src", "SrcAtop", "SrcOver", @@ -52,7 +48,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders "Xor", }; - string[] blenders = new []{ + var blenders = new []{ "Normal", "Multiply", "Add", @@ -67,10 +63,12 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders foreach(var composer in composers) { foreach(var blender in blenders) { - string blender_composer= $"{blender}{composer}"; - + var blender_composer= $"{blender}{composer}"; #> - internal class <#= blender_composer#> : PixelBlender + /// + /// A pixel blender that implements the "<#= blender_composer#>" composition equation. + /// + public class <#= blender_composer#> : PixelBlender { /// /// Gets the static instance of this blender. @@ -82,26 +80,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { TPixel dest = default; dest.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; + return dest; } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + 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) + 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)); - } + } } } @@ -111,4 +109,4 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders #> } -} \ 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 64148746e0..5fccb1fa68 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -1,2162 +1,3662 @@ -// 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 +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// + +using System.Numerics; +using System.Runtime.CompilerServices; + +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.NoInlining)] + public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "NormalSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "NormalDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "NormalXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplySrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "MultiplySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "MultiplyDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "MultiplyXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "AddSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "AddDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "AddXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "SubtractSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "SubtractDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "SubtractXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "ScreenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "ScreenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "ScreenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "DarkenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "DarkenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "DarkenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "LightenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "LightenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "LightenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlaySrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "OverlaySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "OverlayDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "OverlayXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "HardLightSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "HardLightDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "HardLightXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.NoInlining)] + public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index e21a78031f..31b5029080 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -1,183 +1,271 @@ -<# -// 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 +<# +// Copyright (c) Six Labors. +// 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. +// 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.Numerics; +using System.Runtime.CompilerServices; + +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.NoInlining)] + public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return 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.NoInlining)] + public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "<#=blender#>Dest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float 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.NoInlining)] + public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + 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.NoInlining)] + public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(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.NoInlining)] + public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, 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.NoInlining)] + public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(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.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) { #> + + /// + /// 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 = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + +<# } #> + +<# + +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); + } + } + +#> + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 9d0e9d04d3..918935b5eb 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -1,238 +1,270 @@ -// 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 +// Copyright (c) Six Labors. +// 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 + { + /// + /// 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) + { + return 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) + { + return 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) + { + return Vector4.Min(Vector4.One, 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) + { + return Vector4.Max(Vector4.Zero, 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) + { + return Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source)); + } + + /// + /// 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) + { + return Vector4.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) + { + return Vector4.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 "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)); + } + + /// + /// 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)); + } + + /// + /// 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 + float blendW = destination.W * source.W; + float dstW = destination.W - blendW; + float srcW = source.W - blendW; + + // calculate final alpha + float alpha = dstW + source.W; + + // calculate final color + Vector4 color = (destination * dstW) + (source * srcW) + (blend * blendW); + + // unpremultiply + color /= MathF.Max(alpha, Constants.Epsilon); + color.W = 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 Vector4 Atop(Vector4 destination, Vector4 source, Vector4 blend) + { + // calculate weights + float blendW = destination.W * source.W; + float dstW = destination.W - blendW; + + // calculate final alpha + float alpha = destination.W; + + // calculate final color + Vector4 color = (destination * dstW) + (blend * blendW); + + // unpremultiply + color /= MathF.Max(alpha, Constants.Epsilon); + color.W = alpha; + + return color; + } + + /// + /// 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) + { + float alpha = destination.W * source.W; + + Vector4 color = source * alpha; // premultiply + color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply + color.W = alpha; + + return color; + } + + /// + /// 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) + { + float alpha = (1 - destination.W) * source.W; + + Vector4 color = source * alpha; // premultiply + color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply + color.W = alpha; + + return color; + } + + /// + /// 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) + { + float srcW = 1 - destination.W; + float dstW = 1 - source.W; + + float alpha = (source.W * srcW) + (destination.W * dstW); + Vector4 color = (source.W * source * srcW) + (destination.W * destination * 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; + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index e5109fd424..100f80fe16 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Numerics; - using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.PixelFormats @@ -13,8 +12,8 @@ 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 + public abstract class PixelBlender + where TPixel : unmanaged, IPixel { /// /// Blend 2 pixels together. @@ -23,42 +22,53 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. /// - /// The final pixel value after composition + /// The final pixel value after composition. public abstract TPixel Blend(TPixel background, TPixel source, float amount); /// - /// Blend 2 rows together. + /// Blends 2 rows together /// - /// destination span + /// 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. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount); + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount) + where TPixelSrc : unmanaged, 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)); - /// - /// 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); + 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 @@ -69,7 +79,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. /// public void Blend( Configuration configuration, @@ -77,9 +87,7 @@ namespace SixLabors.ImageSharp.PixelFormats ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - this.Blend(configuration, destination, background, source, amount); - } + => this.Blend(configuration, destination, background, source, amount); /// /// Blends 2 rows together @@ -91,7 +99,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. /// public void Blend( Configuration configuration, @@ -99,7 +107,7 @@ namespace SixLabors.ImageSharp.PixelFormats ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - where TPixelSrc : struct, IPixel + where TPixelSrc : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); @@ -112,71 +120,48 @@ namespace SixLabors.ImageSharp.PixelFormats Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - PixelOperations.Instance.ToScaledVector4( - configuration, - background.Slice(0, background.Length), - backgroundSpan); - PixelOperations.Instance.ToScaledVector4( - configuration, - source.Slice(0, background.Length), - sourceSpan); + 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); - PixelOperations.Instance.FromScaledVector4( - configuration, - destinationSpan.Slice(0, background.Length), - destination); + Span sourceVectors = destinationSpan.Slice(0, background.Length); + PixelOperations.Instance.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.Scale); } } /// - /// Blends 2 rows together + /// Blend 2 rows together. /// - /// the pixel format of the source span - /// to use internally - /// the destination span + /// 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. + /// 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 : 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); - - PixelOperations.Instance.ToScaledVector4( - configuration, - background.Slice(0, background.Length), - backgroundSpan); - PixelOperations.Instance.ToScaledVector4( - configuration, - source.Slice(0, background.Length), - sourceSpan); - - this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount); - PixelOperations.Instance.FromScaledVector4( - configuration, - destinationSpan.Slice(0, background.Length), - destination); - } - } + /// + /// 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); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs b/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs index a68f7d9492..559571ac9c 100644 --- a/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs +++ b/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.PixelFormats diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs new file mode 100644 index 0000000000..7a5a3dad69 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Flags responsible to select additional operations which could be efficiently applied in + /// + /// or + /// + /// knowing the pixel type. + /// + [Flags] + public enum PixelConversionModifiers + { + /// + /// No special operation is selected + /// + None = 0, + + /// + /// Select and instead the standard (non scaled) variants. + /// + Scale = 1 << 0, + + /// + /// Enable alpha premultiplication / unpremultiplication + /// + Premultiply = 1 << 1, + + /// + /// Enable SRGB companding (defined in ). + /// + SRgbCompand = 1 << 2, + } +} diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs new file mode 100644 index 0000000000..9e8c97f81b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// 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 + { + [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 new file mode 100644 index 0000000000..b19c35a0ab --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -0,0 +1,167 @@ +// Copyright (c) Six Labors. +// 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 alpha value. + /// + /// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. + /// + /// + public 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(InliningOptions.ShortMethod)] + 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(InliningOptions.ShortMethod)] + public static bool operator !=(A8 left, A8 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.W); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly 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 FromL8(L8 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + + /// + [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 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. + [MethodImpl(InliningOptions.ShortMethod)] + 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})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + 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(InliningOptions.ShortMethod)] + private static byte Pack(float alpha) => (byte)Math.Round(alpha.Clamp(0, 1F) * 255F); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs deleted file mode 100644 index 75b7ede827..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs +++ /dev/null @@ -1,155 +0,0 @@ -// 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 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 8fc3016314..914b31672b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Argb { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -138,10 +138,29 @@ namespace SixLabors.ImageSharp.PixelFormats /// public uint PackedValue { - get => this.Argb; + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => this.Argb; + + [MethodImpl(InliningOptions.ShortMethod)] set => this.Argb = value; } + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Argb32 source) => new Color(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Argb32(Color color) => color.ToArgb32(); + /// /// Compares two objects for equality. /// @@ -165,7 +184,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -173,7 +192,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -181,12 +200,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly 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) @@ -209,7 +232,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -219,7 +242,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -228,6 +251,27 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = byte.MaxValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -279,21 +323,21 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); + public override readonly bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Argb32 other) => this.Argb == other.Argb; + 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 string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; + public override readonly string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.Argb.GetHashCode(); + public override readonly int GetHashCode() => this.Argb.GetHashCode(); /// /// Packs the four floats into a color. @@ -329,7 +373,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; @@ -337,4 +381,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 96ff7da6f6..a2ec185be2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -50,6 +50,22 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = b; } + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Bgr24 source) => new Color(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Bgr24(Color color) => color.ToBgr24(); + /// /// Compares two objects for equality. /// @@ -73,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgr24 left, Bgr24 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -81,7 +97,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -94,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -109,6 +125,10 @@ namespace SixLabors.ImageSharp.PixelFormats [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) @@ -120,7 +140,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -129,7 +149,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -137,6 +157,25 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = rgb; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -180,16 +219,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public readonly bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); /// - public override bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); /// - public override string ToString() => $"Bgra({this.B}, {this.G}, {this.R})"; + public override readonly string ToString() => $"Bgra({this.B}, {this.G}, {this.R})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + public override readonly 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 a2e4dc8802..21ec24a6e6 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgr565 left, Bgr565 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -81,12 +81,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector3(), 1F); + public readonly 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()); @@ -97,11 +101,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -132,7 +144,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector3 ToVector3() + public readonly Vector3 ToVector3() { return new Vector3( ((this.PackedValue >> 11) & 0x1F) * (1F / 31F), @@ -141,14 +153,14 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Bgr565 other && this.Equals(other); + public override readonly 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 readonly bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector3(); return FormattableString.Invariant($"Bgr565({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); @@ -156,7 +168,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector3 vector) @@ -168,4 +180,4 @@ namespace SixLabors.ImageSharp.PixelFormats | ((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 1d156222ff..68dcd8287b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Bgra { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -94,10 +94,26 @@ namespace SixLabors.ImageSharp.PixelFormats /// public uint PackedValue { - get => this.Bgra; + readonly get => this.Bgra; set => this.Bgra = value; } + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Bgra32 source) => new Color(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Bgra32(Color color) => color.ToBgra32(); + /// /// Compares two objects for equality. /// @@ -121,7 +137,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra32 left, Bgra32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -129,7 +145,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -137,7 +153,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -159,13 +175,17 @@ namespace SixLabors.ImageSharp.PixelFormats 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) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -175,7 +195,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -184,6 +204,27 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = byte.MaxValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba32(Rgba32 source) @@ -235,16 +276,16 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); /// - public bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); + public readonly bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); /// - public override int GetHashCode() => this.Bgra.GetHashCode(); + public override readonly int GetHashCode() => this.Bgra.GetHashCode(); /// - public override string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; + public override readonly string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; /// /// Packs a into a color. @@ -255,7 +296,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; @@ -263,4 +304,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 110b51822d..b2af3045a3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra4444 left, Bgra4444 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { const float Max = 1 / 15F; @@ -90,6 +90,10 @@ namespace SixLabors.ImageSharp.PixelFormats [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()); @@ -100,11 +104,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -130,14 +142,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Bgra4444 other && this.Equals(other); + public override readonly 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 readonly bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Bgra4444({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); @@ -145,16 +157,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(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 dcfb25a64b..31fa0c29ce 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. /// /// - public struct Bgra5551 : IPixel, IPackedVector + public partial struct Bgra5551 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra5551 left, Bgra5551 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( ((this.PackedValue >> 10) & 0x1F) / 31F, @@ -87,6 +87,10 @@ namespace SixLabors.ImageSharp.PixelFormats (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()); @@ -101,11 +105,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -135,10 +147,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Bgra5551({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); @@ -146,12 +158,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One); return (ushort)( (((int)Math.Round(vector.X * 31F) & 0x1F) << 10) | (((int)Math.Round(vector.Y * 31F) & 0x1F) << 5) @@ -159,4 +171,4 @@ namespace SixLabors.ImageSharp.PixelFormats | (((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 43a03dc5d1..9c5289b86e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Byte4 left, Byte4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4() / 255F; + public readonly Vector4 ToScaledVector4() => this.ToVector4() / 255F; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( this.PackedValue & 0xFF, @@ -101,16 +101,28 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 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()); @@ -131,21 +143,21 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); + public override readonly bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); - return FormattableString.Invariant($"Bgra5551({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + return FormattableString.Invariant($"Byte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); } /// @@ -159,7 +171,7 @@ namespace SixLabors.ImageSharp.PixelFormats const float Max = 255F; // Clamp the value between min and max values - vector = Vector4.Clamp(vector, Vector4.Zero, new Vector4(Max)); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, new Vector4(Max)); uint byte4 = (uint)Math.Round(vector.X) & 0xFF; uint byte3 = ((uint)Math.Round(vector.Y) & 0xFF) << 0x8; @@ -169,4 +181,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 index 2ec965dfb0..0b1292b641 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -24,55 +24,42 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + public override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); - } - - /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } - /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -82,13 +69,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -97,13 +84,13 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -113,13 +100,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -129,13 +116,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -147,49 +134,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -201,13 +224,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -219,13 +242,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -235,6 +258,32 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromArgb32(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToArgb32(configuration, sourcePixels, destinationPixels); + } } } -} \ 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 index 0a58504e15..3a4c7b459d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# 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 index 711a9d1c16..b73bb8b831 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -24,128 +24,151 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + public override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + 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 ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + 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 ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -157,13 +180,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -175,13 +198,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -193,13 +216,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -209,6 +232,32 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromBgr24(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgr24(configuration, sourcePixels, destinationPixels); + } } } -} \ 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 index 84b89aa32c..cfaefeda9c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# 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 index b669dd5348..5bdd10404d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -24,55 +24,42 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + public override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); - } - - /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } - /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -82,13 +69,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -97,13 +84,13 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -113,13 +100,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -129,13 +116,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -147,49 +134,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -201,13 +224,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -219,13 +242,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -235,6 +258,32 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromBgra32(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgra32(configuration, sourcePixels, destinationPixels); + } } } -} \ 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 index 004ceff51e..58ecfa5a6c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Bgra32"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.cs new file mode 100644 index 0000000000..f9662e0d04 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors. +// 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 Bgra5551 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgra5551(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.tt new file mode 100644 index 0000000000..c4f2fc8bdd --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.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 Bgra5551 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Bgra5551"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs deleted file mode 100644 index 288c5d92e4..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs +++ /dev/null @@ -1,190 +0,0 @@ -// 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 deleted file mode 100644 index 3cbc01e88c..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt +++ /dev/null @@ -1,19 +0,0 @@ -<#@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 deleted file mode 100644 index f7e94788e3..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs +++ /dev/null @@ -1,190 +0,0 @@ -// 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 deleted file mode 100644 index d35843ccda..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt +++ /dev/null @@ -1,19 +0,0 @@ -<#@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/L16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.cs new file mode 100644 index 0000000000..d0c96def18 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors. +// 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 L16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromL16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToL16(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.tt new file mode 100644 index 0000000000..937902346f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.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 L16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("L16"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.cs new file mode 100644 index 0000000000..31b1c96ec2 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors. +// 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 L8 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromL8(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToL8(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.tt new file mode 100644 index 0000000000..d2e802a190 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.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 L8 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("L8"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.cs new file mode 100644 index 0000000000..48e7c76e5b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors. +// 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 La16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromLa16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToLa16(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.tt new file mode 100644 index 0000000000..5d6661631d --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.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 La16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("La16"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.cs new file mode 100644 index 0000000000..f8b4bedc22 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors. +// 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 La32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromLa32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToLa32(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.tt new file mode 100644 index 0000000000..e2fb4867a9 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.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 La32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("La32"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs index dbf3102c4a..332683fc7f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -24,146 +24,169 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + public override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + 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 ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + 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 ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -175,13 +198,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -193,13 +216,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -209,6 +232,32 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgb24(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgb24(configuration, sourcePixels, destinationPixels); + } } } -} \ 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 index d96c3684b5..fc149b2380 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# 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 index 30c9972bbf..9423aa2c8f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -24,32 +24,32 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -61,13 +61,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -79,13 +79,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -97,49 +97,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -151,13 +187,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -169,13 +205,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -185,6 +221,32 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgb48(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgb48(configuration, sourcePixels, destinationPixels); + } } } -} \ 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 index 7bff336386..957c8f886c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# 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 index da2ce3770b..b05c62f1f7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -24,31 +24,31 @@ namespace SixLabors.ImageSharp.PixelFormats internal partial class PixelOperations : PixelOperations { /// - internal override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -58,13 +58,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -73,13 +73,13 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -89,13 +89,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -105,13 +105,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -123,49 +123,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -177,13 +213,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -195,13 +231,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -211,6 +247,32 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgba32(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgba32(configuration, sourcePixels, destinationPixels); + } } } -} \ 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 index 6b9e2d1248..905e2cc58f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# 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 index 42c40ad5d7..3fface03b6 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -24,32 +24,32 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -61,13 +61,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -79,13 +79,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -97,49 +97,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -151,13 +187,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -169,13 +205,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -185,6 +221,32 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgba64(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgba64(configuration, sourcePixels, destinationPixels); + } } } -} \ 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 index d15945f947..03179a3920 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# 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 index f0675cb5b3..5d56731ba6 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude @@ -1,9 +1,9 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ 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. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -15,32 +15,46 @@ 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[] CommonPixelTypes = { "Argb32", "Bgr24", "Bgra32", "L8", "L16", "La16", "La32", "Rgb24", "Rgba32", "Rgb48", "Rgba64", "Bgra5551" }; static readonly string[] Optimized32BitTypes = { "Rgba32", "Argb32", "Bgra32" }; // Types with Rgba32-combatible to/from Vector4 conversion static readonly string[] Rgba32CompatibleTypes = { "Argb32", "Bgra32", "Rgb24", "Bgr24" }; + void GenerateGenericConverterMethods(string pixelType) + { +#> + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span<<#=pixelType#>> destinationPixels) + { + PixelOperations.Instance.To<#=pixelType#>(configuration, sourcePixels, destinationPixels); + } +<#+ + } + void GenerateDefaultSelfConversionMethods(string pixelType) { #> /// - internal override void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destPixels) + public override void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void To<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span<<#=pixelType#>> destPixels) + public override void To<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span<<#=pixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } <#+ @@ -51,13 +65,13 @@ using System.Runtime.InteropServices; #> /// - internal override void To<#=toPixelType#>(Configuration configuration, ReadOnlySpan<<#=fromPixelType#>> sourcePixels, Span<<#=toPixelType#>> destPixels) + public override void To<#=toPixelType#>(Configuration configuration, ReadOnlySpan<<#=fromPixelType#>> sourcePixels, Span<<#=toPixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref <#=fromPixelType#> sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destPixels); + ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -74,13 +88,13 @@ using System.Runtime.InteropServices; { #> /// - internal override void To<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=thisPixelType#>> sourcePixels, Span<<#=otherPixelType#>> destPixels) + public override void To<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=thisPixelType#>> sourcePixels, Span<<#=otherPixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As<<#=thisPixelType#>,uint>(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As<<#=otherPixelType#>, uint>(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As<<#=otherPixelType#>, uint>(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -90,13 +104,13 @@ using System.Runtime.InteropServices; } /// - internal override void From<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=otherPixelType#>> sourcePixels, Span<<#=thisPixelType#>> destPixels) + public override void From<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=otherPixelType#>> sourcePixels, Span<<#=thisPixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As<<#=otherPixelType#>,uint>(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As<<#=thisPixelType#>, uint>(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As<<#=thisPixelType#>, uint>(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -107,33 +121,25 @@ using System.Runtime.InteropServices; <#+ } - void GenerateRgba32CompatibleVector4ConversionMethods(string pixelType) + void GenerateRgba32CompatibleVector4ConversionMethods(string pixelType, bool hasAlpha) { + string removeTheseModifiers = "PixelConversionModifiers.Scale"; + if (!hasAlpha) + { + removeTheseModifiers += " | PixelConversionModifiers.Premultiply"; + } #> /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span<<#=pixelType#>> destPixels) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span<<#=pixelType#>> destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(<#=removeTheseModifiers#>)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors) + public override void ToVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(<#=removeTheseModifiers#>)); } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span<<#=pixelType#>> destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); - } - - /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); - } - <#+ } @@ -143,7 +149,7 @@ using System.Runtime.InteropServices; if (Rgba32CompatibleTypes.Contains(pixelType)) { - GenerateRgba32CompatibleVector4ConversionMethods(pixelType); + GenerateRgba32CompatibleVector4ConversionMethods(pixelType, pixelType.EndsWith("32")); } var matching32BitTypes = Optimized32BitTypes.Contains(pixelType) ? @@ -163,5 +169,7 @@ using System.Runtime.InteropServices; { GenerateDefaultConvertToMethod(pixelType, destPixelType); } + + GenerateGenericConverterMethods(pixelType); } -#> \ No newline at end of file +#> diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs deleted file mode 100644 index 2e98a28ada..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs +++ /dev/null @@ -1,180 +0,0 @@ -// 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 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 deleted file mode 100644 index 512bee39de..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs +++ /dev/null @@ -1,165 +0,0 @@ -// 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 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 8323cf3e8a..e3e6e1383b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { float single = this.ToSingle() + 1F; single /= 2F; @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToSingle(), 0, 0, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToSingle(), 0, 0, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -90,11 +90,23 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -124,20 +136,20 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); + public readonly float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); /// - public override bool Equals(object obj) => obj is HalfSingle other && this.Equals(other); + public override readonly 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 readonly bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); + public override readonly string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + 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 cb915459bc..43380eac03 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -54,20 +54,20 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(HalfVector2 left, HalfVector2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromScaledVector4(Vector4 vector) { - Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; + var scaled = new Vector2(vector.X, vector.Y) * 2F; scaled -= Vector2.One; this.PackedValue = Pack(scaled.X, scaled.Y); } /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { var vector = this.ToVector2(); return new Vector4(vector.X, vector.Y, 0F, 1F); @@ -101,11 +101,23 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -135,7 +147,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { Vector2 vector; vector.X = HalfTypeHelper.Unpack((ushort)this.PackedValue); @@ -144,14 +156,14 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is HalfVector2 other && this.Equals(other); + public override readonly 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 readonly bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"HalfVector2({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -159,7 +171,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(float x, float y) @@ -169,4 +181,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 9f60ca8c77..cb1ae1f404 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(HalfVector4 left, HalfVector4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( HalfTypeHelper.Unpack((ushort)this.PackedValue), @@ -109,11 +109,23 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -139,14 +151,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is HalfVector4 other && this.Equals(other); + public override readonly 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 readonly bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"HalfVector4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -154,7 +166,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// /// Packs a into a . @@ -171,4 +183,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 new file mode 100644 index 0000000000..087becaf2c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -0,0 +1,186 @@ +// Copyright (c) Six Labors. +// 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 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(InliningOptions.ShortMethod)] + 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(InliningOptions.ShortMethod)] + public static bool operator !=(L16 left, L16 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly 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 FromL8(L8 source) => this.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(source.L); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = source.L; + + /// + [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 readonly bool Equals(object obj) => obj is L16 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(L16 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => $"L16({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + vector.X, + vector.Y, + vector.Z); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs new file mode 100644 index 0000000000..32f963795b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors. +// 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 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 = new Vector4(255F); + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// 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(InliningOptions.ShortMethod)] + 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(InliningOptions.ShortMethod)] + public static bool operator !=(L8 left, L8 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly 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 FromL8(L8 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = source.L; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + + /// + [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 readonly bool Equals(object obj) => obj is L8 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(L8 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => $"L8({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); + this.PackedValue = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs new file mode 100644 index 0000000000..bcfe67249d --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -0,0 +1,227 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +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 + { + private static readonly Vector4 MaxBytes = new Vector4(255F); + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// 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 componant. + public La16(byte l, byte a) + { + this.L = l; + this.A = a; + } + + /// + public ushort PackedValue + { + readonly get => Unsafe.As(ref Unsafe.AsRef(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(InliningOptions.ShortMethod)] + 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(InliningOptions.ShortMethod)] + public static bool operator !=(La16 left, La16 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(La16 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly bool Equals(object obj) => obj is La16 other && this.Equals(other); + + /// + public override readonly string ToString() => $"La16({this.L}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + this.L = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.L = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.L = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.L = ImageMaths.Get8BitBT709Luminance( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); + + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + public void FromRgba64(Rgba64 source) + { + this.L = ImageMaths.Get8BitBT709Luminance( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); + + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.L; + dest.G = this.L; + dest.B = this.L; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + const float Max = 255F; + float rgb = this.L / Max; + return new Vector4(rgb, rgb, rgb, this.A / Max); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); + this.L = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); + this.A = (byte)vector.W; + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs new file mode 100644 index 0000000000..23f4b8e17a --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -0,0 +1,245 @@ +// Copyright (c) Six Labors. +// 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 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 componant. + public La32(ushort l, ushort a) + { + this.L = l; + this.A = a; + } + + /// + public uint PackedValue + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(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 ==(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(InliningOptions.ShortMethod)] + public static bool operator !=(La32 left, La32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(La32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly bool Equals(object obj) => obj is La32 other && this.Equals(other); + + /// + public override readonly string ToString() => $"La32({this.L}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + 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 FromL16(L16 source) + { + this.L = source.PackedValue; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.L = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.L = ImageMaths.UpscaleFrom8BitTo16Bit(source.L); + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.L = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.L = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(this.L); + dest.R = rgb; + dest.G = rgb; + dest.B = rgb; + dest.A = ImageMaths.DownScaleFrom16BitTo8Bit(this.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + float scaled = this.L / Max; + return new Vector4(scaled, scaled, scaled, this.A / Max); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; + this.L = ImageMaths.Get16BitBT709Luminance( + vector.X, + vector.Y, + vector.Z); + + this.A = (ushort)MathF.Round(vector.W); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index d39cfd402e..70098b6665 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats { /// - /// Packed packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. + /// 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. /// @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -107,18 +107,27 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.FromScaledVector4(this.ToScaledVector4()); - } + 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 FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -142,7 +151,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { return new Vector2( (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, @@ -150,18 +159,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"NormalizedByte2({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -178,4 +187,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 82698d5085..2762073aa1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, @@ -112,11 +112,23 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -142,18 +154,18 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"NormalizedByte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -162,14 +174,14 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, MinusOne, Vector4.One) * Half; + vector = Vector4Utilities.FastClamp(vector, MinusOne, Vector4.One) * Half; - uint byte4 = ((uint)Math.Round(vector.X) & 0xFF) << 0; - uint byte3 = ((uint)Math.Round(vector.Y) & 0xFF) << 8; - uint byte2 = ((uint)Math.Round(vector.Z) & 0xFF) << 16; - uint byte1 = ((uint)Math.Round(vector.W) & 0xFF) << 24; + 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 b9cab1e7d3..8eaec1411c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -105,13 +105,25 @@ namespace SixLabors.ImageSharp.PixelFormats [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 FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -142,7 +154,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { const float MaxVal = 0x7FFF; @@ -152,18 +164,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"NormalizedShort2({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -182,4 +194,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 3bc74e6c67..ce7c0035f6 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { const float MaxVal = 0x7FFF; @@ -112,13 +112,25 @@ namespace SixLabors.ImageSharp.PixelFormats [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 FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -144,18 +156,18 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"NormalizedShort4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -165,7 +177,7 @@ namespace SixLabors.ImageSharp.PixelFormats private static ulong Pack(ref Vector4 vector) { vector *= Max; - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); // Round rather than truncate. ulong word4 = ((ulong)MathF.Round(vector.X) & 0xFFFF) << 0x00; @@ -176,4 +188,4 @@ namespace SixLabors.ImageSharp.PixelFormats return word4 | word3 | word2 | word1; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index 6dc623518d..7c805f1480 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -93,13 +93,25 @@ namespace SixLabors.ImageSharp.PixelFormats [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 FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -130,17 +142,17 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; + public readonly 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); + public override readonly 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 readonly bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -148,7 +160,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(Vector2 vector) @@ -157,4 +169,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 86565731d2..a50c18d42c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -53,6 +53,22 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = b; } + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Rgb24 source) => new Color(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb24(Color color) => color.ToRgb24(); + /// /// Allows the implicit conversion of an instance of to a /// . @@ -92,7 +108,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgb24 left, Rgb24 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -100,7 +116,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -108,7 +124,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -139,7 +155,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -148,7 +164,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -156,13 +172,46 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = rgb; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + 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; + public void FromRgba32(Rgba32 source) + { +#if NETSTANDARD2_0 + // See https://github.com/SixLabors/ImageSharp/issues/1275 + this.R = source.R; + this.G = source.G; + this.B = source.B; +#else + this = source.Rgb; +#endif + } /// [MethodImpl(InliningOptions.ShortMethod)] @@ -193,18 +242,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Rgb24 other && this.Equals(other); + public override readonly 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); + public readonly 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 readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); /// - public override string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; + public override readonly string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; /// /// Packs a into a color. @@ -215,11 +264,11 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(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 eda116a46c..08f3bcc712 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgb48 left, Rgb48 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -79,13 +79,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(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); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R / Max, this.G / Max, this.B / Max, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.R / Max, this.G / Max, this.B / Max, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -126,9 +126,13 @@ namespace SixLabors.ImageSharp.PixelFormats [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) + public void FromL8(L8 source) { ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); this.R = rgb; @@ -138,13 +142,32 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { this.R = source.PackedValue; this.G = source.PackedValue; this.B = source.PackedValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -178,17 +201,17 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgb48(Rgb48 source) => this = source; /// - public override bool Equals(object obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); + public override readonly 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 readonly 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})"; + public override readonly string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(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 895added1b..2bf9350f8f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba1010102 left, Rgba1010102 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (this.PackedValue >> 0) & 0x03FF, @@ -101,11 +101,23 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -131,14 +143,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; + public readonly bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Rgba1010102({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -146,12 +158,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Multiplier; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Multiplier; return (uint)( (((int)Math.Round(vector.X) & 0x03FF) << 0) @@ -160,4 +172,4 @@ namespace SixLabors.ImageSharp.PixelFormats | (((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 deleted file mode 100644 index 032d0b5464..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs +++ /dev/null @@ -1,721 +0,0 @@ -// 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 index 004b25cd33..dcf304e9b0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs @@ -1,10 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.InteropServices; -using SixLabors.Memory; + +using SixLabors.ImageSharp.PixelFormats.Utils; namespace SixLabors.ImageSharp.PixelFormats { @@ -19,52 +20,36 @@ namespace SixLabors.ImageSharp.PixelFormats internal partial class PixelOperations : PixelOperations { /// - internal override void ToVector4( + public override void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) + Span destinationVectors, + PixelConversionModifiers modifiers) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); - - destVectors = destVectors.Slice(0, sourcePixels.Length); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); - SimdUtils.BulkConvertByteToNormalizedFloat( + destinationVectors = destinationVectors.Slice(0, sourcePixels.Length); + SimdUtils.ByteToNormalizedFloat( MemoryMarshal.Cast(sourcePixels), - MemoryMarshal.Cast(destVectors)); + MemoryMarshal.Cast(destinationVectors)); + Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); } /// - internal override void FromVector4( + public override void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destPixels) + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); - destPixels = destPixels.Slice(0, sourceVectors.Length); - - SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( + destinationPixels = destinationPixels.Slice(0, sourceVectors.Length); + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); + SimdUtils.NormalizedFloatToByteSaturate( MemoryMarshal.Cast(sourceVectors), - MemoryMarshal.Cast(destPixels)); - } - - /// - internal override void ToScaledVector4( - Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationVectors) - { - this.ToVector4(configuration, sourceColors, destinationVectors); - } - - /// - internal override void FromScaledVector4( - Configuration configuration, - ReadOnlySpan sourceVectors, - Span destinationColors) - { - this.FromVector4(configuration, sourceVectors, destinationColors); + MemoryMarshal.Cast(destinationPixels)); } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 5a16704ef0..0b42351011 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers.Binary; +using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -122,7 +125,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Rgba { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -133,13 +136,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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); + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => new Rgb24(this.R, this.G, this.B); [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; + set + { + this.R = value.R; + this.G = value.G; + this.B = value.B; + } } /// @@ -148,7 +154,7 @@ namespace SixLabors.ImageSharp.PixelFormats public Bgr24 Bgr { [MethodImpl(InliningOptions.ShortMethod)] - get => new Bgr24(this.R, this.G, this.B); + readonly get => new Bgr24(this.R, this.G, this.B); [MethodImpl(InliningOptions.ShortMethod)] set @@ -163,12 +169,28 @@ namespace SixLabors.ImageSharp.PixelFormats public uint PackedValue { [MethodImpl(InliningOptions.ShortMethod)] - get => this.Rgba; + readonly get => this.Rgba; [MethodImpl(InliningOptions.ShortMethod)] set => this.Rgba = value; } + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Rgba32 source) => new Color(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgba32(Color color) => color.ToRgba32(); + /// /// Allows the implicit conversion of an instance of to a /// . @@ -208,7 +230,8 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right); /// - /// Creates a new instance of the struct. + /// Creates a new instance of the struct + /// from the given hexadecimal string. /// /// /// The hexadecimal representation of the combined color components arranged @@ -217,10 +240,54 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static Rgba32 FromHex(string hex) => ColorBuilder.FromHex(hex); + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgba32 ParseHex(string hex) + { + Guard.NotNull(hex, nameof(hex)); + + if (!TryParseHex(hex, out Rgba32 rgba)) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + return rgba; + } + + /// + /// Attempts to creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool TryParseHex(string hex, out Rgba32 result) + { + result = default; + if (string.IsNullOrWhiteSpace(hex)) + { + return false; + } + + hex = ToRgbaHex(hex); + + if (hex is null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue)) + { + return false; + } + + packedValue = BinaryPrimitives.ReverseEndianness(packedValue); + result = Unsafe.As(ref packedValue); + return true; + } /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -228,7 +295,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -236,7 +303,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -268,7 +335,11 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -278,7 +349,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -287,6 +358,27 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = byte.MaxValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -330,25 +422,25 @@ namespace SixLabors.ImageSharp.PixelFormats /// Converts the value of this instance to a hexadecimal string. /// /// A hexadecimal string representation of the value. - public string ToHex() + public readonly string ToHex() { uint hexOrder = (uint)(this.A << 0 | this.B << 8 | this.G << 16 | this.R << 24); return hexOrder.ToString("X8"); } /// - public override bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); + public override readonly bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); + public readonly bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); /// - public override string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; + public override readonly string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.Rgba.GetHashCode(); + public override readonly int GetHashCode() => this.Rgba.GetHashCode(); /// /// Packs a into a color returning a new instance as a result. @@ -360,7 +452,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); return new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W); } @@ -399,12 +491,49 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; this.B = (byte)vector.Z; this.A = (byte)vector.W; } + + /// + /// 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/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index 5ae5492e23..22f58ca4ad 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats { /// - /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 635535. + /// 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. /// @@ -46,6 +46,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The green component. /// The blue component. /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] public Rgba64(ushort r, ushort g, ushort b, ushort a) { this.R = r; @@ -55,12 +56,91 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - /// Gets or sets the RGB components of this struct as + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in RGBA byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(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); + } + + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in BGRA byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(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); + } + + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in ARGB byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(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); + } + + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 3 bytes in RGB byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Rgb24 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + this.A = ushort.MaxValue; + } + + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 3 bytes in BGR byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Bgr24 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + this.A = ushort.MaxValue; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Vector4 vector) + { + vector = Vector4Utilities.FastClamp(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(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -70,12 +150,28 @@ namespace SixLabors.ImageSharp.PixelFormats public ulong PackedValue { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; } + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Rgba64 source) => new Color(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgba64(Color color) => color.ToPixel(); + /// /// Compares two objects for equality. /// @@ -99,7 +195,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba64 left, Rgba64 right) => left.PackedValue != right.PackedValue; /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -107,13 +203,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(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); @@ -122,7 +218,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -154,9 +250,13 @@ namespace SixLabors.ImageSharp.PixelFormats 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) + public void FromL8(L8 source) { ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); this.R = rgb; @@ -167,7 +267,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -175,6 +275,27 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = ushort.MaxValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -217,18 +338,86 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba64(Rgba64 source) => this = source; + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Rgba32 ToRgba32() + { + byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); + byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); + byte b = ImageMaths.DownScaleFrom16BitTo8Bit(this.B); + byte a = ImageMaths.DownScaleFrom16BitTo8Bit(this.A); + return new Rgba32(r, g, b, a); + } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Bgra32 ToBgra32() + { + byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); + byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); + byte b = ImageMaths.DownScaleFrom16BitTo8Bit(this.B); + byte a = ImageMaths.DownScaleFrom16BitTo8Bit(this.A); + return new Bgra32(r, g, b, a); + } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Argb32 ToArgb32() + { + byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); + byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); + byte b = ImageMaths.DownScaleFrom16BitTo8Bit(this.B); + byte a = ImageMaths.DownScaleFrom16BitTo8Bit(this.A); + return new Argb32(r, g, b, a); + } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Rgb24 ToRgb24() + { + byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); + byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); + byte b = ImageMaths.DownScaleFrom16BitTo8Bit(this.B); + return new Rgb24(r, g, b); + } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Bgr24 ToBgr24() + { + byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); + byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); + byte b = ImageMaths.DownScaleFrom16BitTo8Bit(this.B); + return new Bgr24(r, g, b); + } + /// - public override bool Equals(object obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); + public override readonly 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 readonly bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; + public override readonly string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + 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 index bffaf57ddd..aa0791d0cf 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs @@ -1,10 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Utils; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -18,33 +21,62 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromScaledVector4( + public override void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destinationColors) + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationColors, nameof(destinationColors)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); - MemoryMarshal.Cast(sourceVectors).CopyTo(destinationColors); + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); + MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels); } /// - internal override void ToScaledVector4( - Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationVectors) - => this.ToVector4(configuration, sourceColors, destinationVectors); - - /// - internal override void ToVector4( + public override void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) + Span destinationVectors, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); + + MemoryMarshal.Cast(sourcePixels).CopyTo(destinationVectors); + Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); + } + + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref L8 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L8 dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.ConvertFromRgbaScaledVector4(sp); + } + } + + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref L16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L16 dp = ref Unsafe.Add(ref destBaseRef, i); - MemoryMarshal.Cast(sourcePixels).CopyTo(destVectors); + dp.ConvertFromRgbaScaledVector4(sp); + } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index d65a5ade78..6ea80ca3b9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats { /// - /// Unpacked pixel type containing four 16-bit floating-point values typically ranging from 0 to 1. + /// 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. @@ -94,10 +94,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static RgbaVector FromHex(string hex) => ColorBuilder.FromHex(hex); + public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel(); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -105,13 +105,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One); this.R = vector.X; this.G = vector.Y; this.B = vector.Z; @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A); + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -134,13 +134,25 @@ namespace SixLabors.ImageSharp.PixelFormats [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()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -152,10 +164,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.FromScaledVector4(this.ToScaledVector4()); - } + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -169,7 +178,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// Converts the value of this instance to a hexadecimal string. /// /// A hexadecimal string representation of the value. - public string ToHex() + public readonly string ToHex() { // Hex is RRGGBBAA Vector4 vector = this.ToVector4() * Max; @@ -179,24 +188,23 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(RgbaVector 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 string ToString() + public override readonly 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 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 96fe15ed61..91c0e9ab5a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Short2 left, Short2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += new Vector2(32767F); @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); + public readonly Vector4 ToVector4() => new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -113,11 +113,23 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -129,10 +141,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.FromScaledVector4(this.ToScaledVector4()); - } + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -148,21 +157,21 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() => new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); + public readonly 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); + public override readonly bool Equals(object obj) => obj is Short2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"Short2({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -178,4 +187,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 d224f8eb4e..3f74b68453 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Short4 left, Short4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += new Vector4(32767F); @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (short)(this.PackedValue & 0xFFFF), @@ -118,11 +118,23 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -148,21 +160,21 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Short4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Short4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Short4 other) => this.PackedValue.Equals(other); + public readonly 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 readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Short4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -171,7 +183,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] private static ulong Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); // Clamp the value between min and max values ulong word4 = ((ulong)Math.Round(vector.X) & 0xFFFF) << 0x00; @@ -182,4 +194,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 207a8767d6..cc36c7d134 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -13,15 +13,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -36,14 +36,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void FromArgb32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromArgb32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromArgb32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -51,13 +51,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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) + /// The destination span of data. + public virtual void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Argb32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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) + public void ToArgb32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToArgb32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -85,15 +85,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -108,14 +108,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void FromBgr24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromBgr24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromBgr24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -123,13 +123,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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) + /// The destination span of data. + public virtual void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -149,7 +149,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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) + public void ToBgr24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToBgr24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -157,15 +157,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -180,14 +180,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void FromBgra32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromBgra32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromBgra32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -195,13 +195,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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) + /// The destination span of data. + public virtual void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -221,167 +221,311 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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) + public void ToBgra32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToBgra32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } /// - /// Converts all pixels in 'source` span of into a span of -s. + /// 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) + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromL8(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Gray8 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { - ref Gray8 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceBaseRef, i); ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromGray8(sp); + dp.FromL8(sp); } } /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void FromL8Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromGray8(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromL8(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// 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) + /// The destination span of data. + public virtual void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref L8 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 'destBytes' must be compatible with layout. + /// 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) + public void ToL8Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToL8(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } + + /// + /// 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 destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < source.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromL16(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 FromL16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.ToGray8(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + this.FromL16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } + + /// + /// 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. + public virtual void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L16 dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromScaledVector4(sp.ToScaledVector4()); + } } /// - /// Converts all pixels in 'source` span of into a span of -s. + /// 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 source of data. - /// The to the destination pixels. - internal virtual void FromGray16(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// 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 sourcePixels, Span destBytes, int count) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + this.ToL16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } + + /// + /// 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 destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Gray16 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { - ref Gray16 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceBaseRef, i); ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromGray16(sp); + dp.FromLa16(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 FromLa16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromLa16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } + + /// + /// 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. + public virtual void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref La16 dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromScaledVector4(sp.ToScaledVector4()); } } /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// 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)] + public void ToLa16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToLa16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } + + /// + /// 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 destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < source.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromLa32(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 to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromGray16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromLa32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromGray16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromLa32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// 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) + /// The destination span of data. + public virtual void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref La32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); 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); + ref La32 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 'destBytes' must be compatible with layout. + /// 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) + public void ToLa32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { - this.ToGray16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + this.ToLa32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -396,14 +540,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void FromRgb24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromRgb24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromRgb24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -411,13 +555,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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) + /// The destination span of data. + public virtual void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb24 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -437,7 +581,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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) + public void ToRgb24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToRgb24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -445,15 +589,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -468,14 +612,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void FromRgba32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromRgba32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromRgba32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -483,13 +627,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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) + /// The destination span of data. + public virtual void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -509,7 +653,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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) + public void ToRgba32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToRgba32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -517,15 +661,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -540,14 +684,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void FromRgb48Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromRgb48(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromRgb48(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -555,13 +699,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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) + /// The destination span of data. + public virtual void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -581,7 +725,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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) + public void ToRgb48Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToRgb48(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -589,15 +733,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -612,14 +756,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void FromRgba64Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromRgba64(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromRgba64(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -627,13 +771,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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) + /// The destination span of data. + public virtual void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -653,9 +797,81 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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) + public void ToRgba64Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToRgba64(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } + + /// + /// 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 destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + 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); + } + } + + /// + /// 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 destinationPixels, int count) + { + this.FromBgra5551(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } + + /// + /// 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. + public virtual void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + 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); + + dp.FromScaledVector4(sp.ToScaledVector4()); + } + } + + /// + /// 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)] + public 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 8579423b34..21ed328fac 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -1,5 +1,5 @@ -<# -// Copyright (c) Six Labors and contributors. +<# +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. #> <#@ template debug="false" hostspecific="false" language="C#" #> @@ -18,15 +18,15 @@ /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// 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) + /// The to the destination pixels. + public virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref <#=pixelType#> sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -41,14 +41,14 @@ /// 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 + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// 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) + public void From<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.From<#=pixelType#>(configuration, MemoryMarshal.Cast>(sourceBytes).Slice(0, count), destPixels); + this.From<#=pixelType#>(configuration, MemoryMarshal.Cast>(sourceBytes).Slice(0, count), destinationPixels); } <# @@ -62,13 +62,13 @@ /// /// 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) + /// The destination span of data. + public virtual void To<#=pixelType#>(Configuration configuration, ReadOnlySpan sourcePixels, Span<<#=pixelType#>> destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -88,7 +88,7 @@ /// 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) + public void To<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.To<#=pixelType#>(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast>(destBytes)); } @@ -96,7 +96,7 @@ } #> -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -118,11 +118,17 @@ namespace SixLabors.ImageSharp.PixelFormats GenerateFromMethods("Bgra32"); GenerateToDestFormatMethods("Bgra32"); - GenerateFromMethods("Gray8"); - GenerateToDestFormatMethods("Gray8"); + GenerateFromMethods("L8"); + GenerateToDestFormatMethods("L8"); - GenerateFromMethods("Gray16"); - GenerateToDestFormatMethods("Gray16"); + GenerateFromMethods("L16"); + GenerateToDestFormatMethods("L16"); + + GenerateFromMethods("La16"); + GenerateToDestFormatMethods("La16"); + + GenerateFromMethods("La32"); + GenerateToDestFormatMethods("La32"); GenerateFromMethods("Rgb24"); GenerateToDestFormatMethods("Rgb24"); @@ -136,5 +142,8 @@ namespace SixLabors.ImageSharp.PixelFormats GenerateFromMethods("Rgba64"); GenerateToDestFormatMethods("Rgba64"); + 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 dcddadb6a1..c245e1a6dc 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -1,217 +1,217 @@ -// 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 +// Copyright (c) Six Labors. +// 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 : 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; + } + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 115dd7a43d..2fff67b58d 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The pixel format. public partial class PixelOperations - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Gets the global instance for the pixel type @@ -24,129 +24,133 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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 virtual void FromVector4( + /// The to the destination colors. + /// The to apply during the conversion + public virtual void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destPixels) + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); - Utils.Vector4Converters.Default.DangerousFromVector4(sourceVectors, destPixels); + Utils.Vector4Converters.Default.FromVector4(sourceVectors, destinationPixels, modifiers); } /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// 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 colors. - /// The to the destination vectors. - internal virtual void ToVector4( + /// The to the source vectors. + /// The to the destination colors. + public void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourcePixels, - Span destVectors) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); - - Utils.Vector4Converters.Default.DangerousToVector4(sourcePixels, destVectors); - } + Span sourceVectors, + Span destinationPixels) + => this.FromVector4Destructive(configuration, sourceVectors, destinationPixels, PixelConversionModifiers.None); /// - /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. + /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. /// /// A to configure internal operations - /// The to the source vectors. - /// The to the destination colors. - internal virtual void FromScaledVector4( + /// The to the source colors. + /// The to the destination vectors. + /// The to apply during the conversion + public virtual void ToVector4( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destinationColors) + ReadOnlySpan sourcePixels, + Span destinationVectors, + PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationColors, nameof(destinationColors)); - Utils.Vector4Converters.Default.DangerousFromScaledVector4(sourceVectors, destinationColors); + Utils.Vector4Converters.Default.ToVector4(sourcePixels, destinationVectors, modifiers); } /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. /// /// A to configure internal operations - /// The to the source colors. + /// The to the source colors. /// The to the destination vectors. - internal virtual void ToScaledVector4( + public void ToVector4( Configuration configuration, - ReadOnlySpan sourceColors, + ReadOnlySpan sourcePixels, Span destinationVectors) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceColors, destinationVectors, nameof(destinationVectors)); - - Utils.Vector4Converters.Default.DangerousToScaledVector4(sourceColors, destinationVectors); - } + => this.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.None); /// - /// Converts 'sourceColors.Length' pixels from 'sourceColors' into 'destinationColors'. + /// Bulk operation that copies the to in + /// format. /// - /// The destination pixel type. - /// A to configure internal operations - /// The to the source colors. - /// The to the destination colors. - internal virtual void To( + /// 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 sourceColors, - Span destinationColors) - where TDestinationPixel : struct, IPixel + ReadOnlySpan sourcePixels, + Span destinationPixels) + where TSourcePixel : unmanaged, IPixel { - 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)) + const int SliceLength = 1024; + int numberOfSlices = sourcePixels.Length / SliceLength; + using (IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(SliceLength)) { - ref Gray16 gray16Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationColors)); - for (int i = 0; i < count; i++) + Span vectorSpan = tempVectors.GetSpan(); + for (int i = 0; i < numberOfSlices; i++) { - ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref gray16Ref, i); - dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); + int start = i * SliceLength; + ReadOnlySpan s = sourcePixels.Slice(start, SliceLength); + Span d = destinationPixels.Slice(start, SliceLength); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan); + this.FromVector4Destructive(configuration, vectorSpan, d); } - return; - } - - if (typeof(TDestinationPixel) == typeof(Gray8)) - { - ref Gray8 gray8Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationColors)); - for (int i = 0; i < count; i++) + int endOfCompleteSlices = numberOfSlices * SliceLength; + int remainder = sourcePixels.Length - endOfCompleteSlices; + if (remainder > 0) { - ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref gray8Ref, i); - dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); + ReadOnlySpan s = sourcePixels.Slice(endOfCompleteSlices); + Span d = destinationPixels.Slice(endOfCompleteSlices); + vectorSpan = vectorSpan.Slice(0, remainder); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan); + this.FromVector4Destructive(configuration, vectorSpan, d); } - - 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()); - } + /// + /// 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 sourcePixels, + Span destinationPixels) + where TDestinationPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + PixelOperations.Instance.From(configuration, sourcePixels, destinationPixels); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/RgbaComponent.cs b/src/ImageSharp/PixelFormats/RgbaComponent.cs index 76df62c437..90400fb54b 100644 --- a/src/ImageSharp/PixelFormats/RgbaComponent.cs +++ b/src/ImageSharp/PixelFormats/RgbaComponent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp diff --git a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs index 12ec389b06..8142640848 100644 --- a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs +++ b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Buffers.Binary; diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index e784e3b5a6..999f6325bc 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -15,16 +15,78 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils { /// /// Provides default implementations for batched to/from conversion. - /// WARNING: The methods are operating without bounds checking and input validation! + /// 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 { [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousFromVector4( + public static void FromVector4( + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + { + Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); + + UnsafeFromVector4(sourceVectors, destPixels, modifiers); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToVector4( + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + + UnsafeToVector4(sourcePixels, destVectors, modifiers); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnsafeFromVector4( + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + { + ApplyBackwardConversionModifiers(sourceVectors, modifiers); + + if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + { + UnsafeFromScaledVector4Core(sourceVectors, destPixels); + } + else + { + UnsafeFromVector4Core(sourceVectors, destPixels); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnsafeToVector4( + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + { + if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + { + UnsafeToScaledVector4Core(sourcePixels, destVectors); + } + else + { + UnsafeToVector4Core(sourcePixels, destVectors); + } + + ApplyForwardConversionModifiers(destVectors, modifiers); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromVector4Core( ReadOnlySpan sourceVectors, Span destPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); @@ -38,10 +100,10 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousToVector4( + private static void UnsafeToVector4Core( ReadOnlySpan sourcePixels, Span destVectors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Vector4 destRef = ref MemoryMarshal.GetReference(destVectors); @@ -55,10 +117,10 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousFromScaledVector4( + private static void UnsafeFromScaledVector4Core( ReadOnlySpan sourceVectors, Span destinationColors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); ref TPixel destRef = ref MemoryMarshal.GetReference(destinationColors); @@ -72,10 +134,10 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousToScaledVector4( + private static void UnsafeToScaledVector4Core( ReadOnlySpan sourceColors, Span destinationVectors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourceColors); ref Vector4 destRef = ref MemoryMarshal.GetReference(destinationVectors); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 06c33d79c7..3973f0485f 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -29,11 +29,8 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static readonly int Vector4ConversionThreshold = CalculateVector4ConversionThreshold(); /// - /// Provides an efficient default implementation for - /// and - /// 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! + /// 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( @@ -41,8 +38,8 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils PixelOperations pixelOperations, ReadOnlySpan sourcePixels, Span destVectors, - bool scaled) - where TPixel : struct, IPixel + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); @@ -52,7 +49,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils // Not worth for small buffers: if (count < Vector4ConversionThreshold) { - ToVector4Fallback(sourcePixels, destVectors, scaled); + Default.UnsafeToVector4(sourcePixels, destVectors, modifiers); return; } @@ -65,28 +62,28 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils // 'destVectors' and 'lastQuarterOfDestBuffer' are overlapping buffers, // but we are always reading/writing at different positions: - SimdUtils.BulkConvertByteToNormalizedFloat( + SimdUtils.ByteToNormalizedFloat( MemoryMarshal.Cast(lastQuarterOfDestBuffer), MemoryMarshal.Cast(destVectors.Slice(0, countWithoutLastItem))); destVectors[countWithoutLastItem] = sourcePixels[countWithoutLastItem].ToVector4(); + + // TODO: Investigate optimized 1-pass approach! + ApplyForwardConversionModifiers(destVectors, modifiers); } /// - /// Provides an efficient default implementation for - /// and - /// which is applicable for -compatible pixel types where - /// returns the same scaled result as . + /// 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, - ReadOnlySpan sourceVectors, + Span sourceVectors, Span destPixels, - bool scaled) - where TPixel : struct, IPixel + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); @@ -96,18 +93,21 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils // Not worth for small buffers: if (count < Vector4ConversionThreshold) { - FromVector4Fallback(sourceVectors, destPixels, scaled); + Default.UnsafeFromVector4(sourceVectors, destPixels, modifiers); return; } + // TODO: Investigate optimized 1-pass approach! + ApplyBackwardConversionModifiers(sourceVectors, 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; - SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( + SimdUtils.NormalizedFloatToByteSaturate( MemoryMarshal.Cast(sourceVectors), MemoryMarshal.Cast(tempSpan)); @@ -115,34 +115,6 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } } - [MethodImpl(InliningOptions.ColdPath)] - private static void ToVector4Fallback(ReadOnlySpan sourcePixels, Span destVectors, bool scaled) - where TPixel : struct, IPixel - { - if (scaled) - { - Default.DangerousToScaledVector4(sourcePixels, destVectors); - } - else - { - Default.DangerousToVector4(sourcePixels, destVectors); - } - } - - [MethodImpl(InliningOptions.ColdPath)] - private static void FromVector4Fallback(ReadOnlySpan sourceVectors, Span destPixels, bool scaled) - where TPixel : struct, IPixel - { - if (scaled) - { - Default.DangerousFromScaledVector4(sourceVectors, destPixels); - } - else - { - Default.DangerousFromVector4(sourceVectors, destPixels); - } - } - private static int CalculateVector4ConversionThreshold() { if (!Vector.IsHardwareAccelerated) @@ -150,8 +122,8 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils return int.MaxValue; } - return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.IsAvx2CompatibleArchitecture ? 256 : 128; + return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.HasVector8 ? 256 : 128; } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs new file mode 100644 index 0000000000..2dca8f2c14 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.PixelFormats.Utils +{ + internal static partial class Vector4Converters + { + /// + /// Apply modifiers used requested by ToVector4() conversion. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ApplyForwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) + { + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + SRgbCompanding.Expand(vectors); + } + + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + Vector4Utilities.Premultiply(vectors); + } + } + + /// + /// Apply modifiers used requested by FromVector4() conversion. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ApplyBackwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) + { + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + Vector4Utilities.UnPremultiply(vectors); + } + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + SRgbCompanding.Compress(vectors); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/ColorMatrix.cs b/src/ImageSharp/Primitives/ColorMatrix.cs index af2e9465a9..228b1d9b07 100644 --- a/src/ImageSharp/Primitives/ColorMatrix.cs +++ b/src/ImageSharp/Primitives/ColorMatrix.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. #pragma warning disable SA1117 // Parameters should be on same line or separate lines @@ -6,7 +6,7 @@ using System; using System.Globalization; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image. @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Primitives /// The resulting matrix. public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 + value2.M11; m.M12 = value1.M12 + value2.M12; @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Primitives /// The result of the subtraction. public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 - value2.M11; m.M12 = value1.M12 - value2.M12; @@ -269,9 +269,9 @@ namespace SixLabors.ImageSharp.Primitives /// /// The source matrix. /// The negated matrix. - public static unsafe ColorMatrix operator -(ColorMatrix value) + public static ColorMatrix operator -(ColorMatrix value) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = -value.M11; m.M12 = -value.M12; @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Primitives /// The result of the multiplication. public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); // First row m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); @@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp.Primitives /// The scaled matrix. public static ColorMatrix operator *(ColorMatrix value1, float value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 * value2; m.M12 = value1.M12 * value2; @@ -456,4 +456,4 @@ namespace SixLabors.ImageSharp.Primitives 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 new file mode 100644 index 0000000000..c4681c511e --- /dev/null +++ b/src/ImageSharp/Primitives/Complex64.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 Complex64(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 new file mode 100644 index 0000000000..3a1d4ac460 --- /dev/null +++ b/src/ImageSharp/Primitives/ComplexVector4.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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(in 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 170292e29e..e312703368 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -1,12 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Diagnostics; using System.Runtime.CompilerServices; -using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a dense matrix with arbitrary elements. @@ -98,9 +97,9 @@ namespace SixLabors.ImageSharp.Primitives } /// - /// Gets a Span wrapping the Data. + /// Gets a span wrapping the . /// - internal Span Span => new Span(this.Data); + public Span Span => new Span(this.Data); /// /// Gets or sets the item at the specified position. @@ -137,7 +136,7 @@ namespace SixLabors.ImageSharp.Primitives /// [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 { var result = new T[data.Rows, data.Columns]; @@ -154,6 +153,24 @@ namespace SixLabors.ImageSharp.Primitives 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. /// @@ -211,15 +228,32 @@ namespace SixLabors.ImageSharp.Primitives } /// - public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); + public override bool Equals(object obj) + => obj is DenseMatrix other && this.Equals(other); /// + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(DenseMatrix other) => this.Columns == other.Columns && this.Rows == other.Rows && this.Span.SequenceEqual(other.Span); /// - public override int GetHashCode() => this.Data.GetHashCode(); + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + { + HashCode code = default; + + code.Add(this.Columns); + code.Add(this.Rows); + + Span span = this.Span; + for (int i = 0; i < span.Length; i++) + { + code.Add(span[i]); + } + + return code.ToHashCode(); + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Primitives/LongRational.cs b/src/ImageSharp/Primitives/LongRational.cs index d790b110d0..9f8eb9a1a2 100644 --- a/src/ImageSharp/Primitives/LongRational.cs +++ b/src/ImageSharp/Primitives/LongRational.cs @@ -1,14 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Globalization; using System.Text; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// - /// Represents a number that can be expressed as a fraction + /// 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. diff --git a/src/ImageSharp/Primitives/Matrix3x2Extensions.cs b/src/ImageSharp/Primitives/Matrix3x2Extensions.cs new file mode 100644 index 0000000000..91fbf9e34a --- /dev/null +++ b/src/ImageSharp/Primitives/Matrix3x2Extensions.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..f6748d8565 --- /dev/null +++ b/src/ImageSharp/Primitives/Number.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 Number(value); + + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(uint value) => new Number(value); + + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(ushort value) => new Number((uint)value); + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator int(Number number) + { + return number.isSigned + ? number.signedValue + : (int)number.unsignedValue.Clamp(0, int.MaxValue); + } + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator uint(Number number) + { + return number.isSigned + ? (uint)number.signedValue.Clamp(0, int.MaxValue) + : number.unsignedValue; + } + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator ushort(Number number) + { + return number.isSigned + ? (ushort)number.signedValue.Clamp(ushort.MinValue, ushort.MaxValue) + : (ushort)number.unsignedValue.Clamp(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 new file mode 100644 index 0000000000..653ec1fe38 --- /dev/null +++ b/src/ImageSharp/Primitives/Point.cs @@ -0,0 +1,288 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 = default; + + /// + /// 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 PointF(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 Vector2(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 Size(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 Point(-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 Point(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 Point(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 Point(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 Point(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 Point(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 Point(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 Point(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 Point(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)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/PointF.cs b/src/ImageSharp/Primitives/PointF.cs new file mode 100644 index 0000000000..848bfce4af --- /dev/null +++ b/src/ImageSharp/Primitives/PointF.cs @@ -0,0 +1,293 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 = default; + + /// + /// 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 PointF(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 Vector2(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 PointF(-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 PointF(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 PointF(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 PointF(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 PointF(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 PointF(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 PointF(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 && this.Equals((PointF)obj); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/Rational.cs b/src/ImageSharp/Primitives/Rational.cs index b598f0e02f..b6f83e277d 100644 --- a/src/ImageSharp/Primitives/Rational.cs +++ b/src/ImageSharp/Primitives/Rational.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Globalization; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a number that can be expressed as a fraction. @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Primitives { if (simplify) { - LongRational rational = new LongRational(numerator, denominator).Simplify(); + var rational = new LongRational(numerator, denominator).Simplify(); this.Numerator = (uint)rational.Numerator; this.Denominator = (uint)rational.Denominator; @@ -187,4 +187,4 @@ namespace SixLabors.ImageSharp.Primitives return rational.ToString(provider); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs new file mode 100644 index 0000000000..1904b09790 --- /dev/null +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -0,0 +1,463 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 = default; + + /// + /// 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 Point(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 Size(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 RectangleF(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 Vector4(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 Rectangle(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 Point(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 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 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(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 specfied intersects the rectangular region defined by + /// this . + /// + /// The other Rectange. + /// 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() + { + return HashCode.Combine(this.X, this.Y, this.Width, this.Height); + } + + /// + public override string ToString() + { + return $"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 new file mode 100644 index 0000000000..d050c5139b --- /dev/null +++ b/src/ImageSharp/Primitives/RectangleF.cs @@ -0,0 +1,396 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 = default; + + /// + /// 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 PointF(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 SizeF(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 RectangleF(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 PointF(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() + { + return HashCode.Combine(this.X, this.Y, this.Width, this.Height); + } + + /// + public override string ToString() + { + return $"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); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/SignedRational.cs b/src/ImageSharp/Primitives/SignedRational.cs index 7e486e4f22..78ad45de7b 100644 --- a/src/ImageSharp/Primitives/SignedRational.cs +++ b/src/ImageSharp/Primitives/SignedRational.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Globalization; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a number that can be expressed as a fraction. @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Primitives { if (simplify) { - LongRational rational = new LongRational(numerator, denominator).Simplify(); + var rational = new LongRational(numerator, denominator).Simplify(); this.Numerator = (int)rational.Numerator; this.Denominator = (int)rational.Denominator; @@ -187,4 +187,4 @@ namespace SixLabors.ImageSharp.Primitives return rational.ToString(provider); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Primitives/Size.cs b/src/ImageSharp/Primitives/Size.cs new file mode 100644 index 0000000000..0790a3dbd2 --- /dev/null +++ b/src/ImageSharp/Primitives/Size.cs @@ -0,0 +1,296 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 = default; + + /// + /// 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 SizeF(size.Width, size.Height); + + /// + /// Converts the given into a . + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Point(Size size) => new Point(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 Size(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 SizeF(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 Size(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 Size(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 Size(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 Size(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) + { + var 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 Size(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 Size(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 SizeF(size.Width * multiplier, size.Height * multiplier); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/SizeF.cs b/src/ImageSharp/Primitives/SizeF.cs new file mode 100644 index 0000000000..b62aa8b0d4 --- /dev/null +++ b/src/ImageSharp/Primitives/SizeF.cs @@ -0,0 +1,233 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 = default; + + /// + /// 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 Vector2(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 Size(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 PointF(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 SizeF(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 SizeF(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 SizeF(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) + { + var 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 SizeF(size.Width * multiplier, size.Height * multiplier); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/ValueSize.cs b/src/ImageSharp/Primitives/ValueSize.cs index e5e086540d..86d54e26de 100644 --- a/src/ImageSharp/Primitives/ValueSize.cs +++ b/src/ImageSharp/Primitives/ValueSize.cs @@ -1,13 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// - /// Represents a value in relation to a value on the image + /// Represents a value in relation to a value on the image. /// internal readonly struct ValueSize : IEquatable { @@ -28,22 +27,22 @@ namespace SixLabors.ImageSharp.Primitives } /// - /// Enumerates the different value types + /// Enumerates the different value types. /// public enum ValueSizeType { /// - /// The value is the final return value + /// The value is the final return value. /// Absolute, /// - /// The value is a percentage of the image width + /// The value is a percentage of the image width. /// PercentageOfWidth, /// - /// The value is a percentage of the images height + /// The value is a percentage of the images height. /// PercentageOfHeight } @@ -59,11 +58,10 @@ namespace SixLabors.ImageSharp.Primitives public ValueSizeType Type { get; } /// - /// Implicitly converts a float into an absolute value + /// 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); + public static implicit operator ValueSize(float f) => Absolute(f); /// /// Create a new ValueSize with as a PercentageOfWidth type with value set to percentage. @@ -89,7 +87,7 @@ namespace SixLabors.ImageSharp.Primitives /// Create a new ValueSize with as a Absolute type with value set to value. /// /// The value. - /// a Values size with type Absolute( + /// a Values size with type Absolute. public static ValueSize Absolute(float value) { return new ValueSize(value, ValueSizeType.Absolute); @@ -99,7 +97,7 @@ namespace SixLabors.ImageSharp.Primitives /// Calculates the specified size. /// /// The size. - /// The calculated value + /// The calculated value. public float Calculate(Size size) { switch (this.Type) @@ -115,10 +113,7 @@ namespace SixLabors.ImageSharp.Primitives } /// - public override string ToString() - { - return $"{this.Value} - {this.Type}"; - } + public override string ToString() => $"{this.Value} - {this.Type}"; /// public override bool Equals(object obj) diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs new file mode 100644 index 0000000000..dc9965c432 --- /dev/null +++ b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source) + => source.ApplyProcessor(new AdaptiveThresholdProcessor()); + + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// 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 image this method extends. + /// 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 image this method extends. + /// 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 image this method extends. + /// 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 image this method extends. + /// 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 c3d01241c9..75f23c6b42 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -1,11 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public AffineTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + => this.Prepend(size => TransformUtilities.CreateRotationMatrixRadians(radians, size)); /// /// Prepends a rotation matrix using the given rotation in degrees at the given origin. @@ -68,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public AffineTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + => this.Append(size => TransformUtilities.CreateRotationMatrixRadians(radians, size)); /// /// Appends a rotation matrix using the given rotation in degrees at the given origin. @@ -143,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in degrees. /// The . public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + => this.Prepend(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size)); /// /// Prepends a centered skew matrix from the give angles in radians. @@ -152,7 +151,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + => this.Prepend(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -181,7 +180,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in degrees. /// The . public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + => this.Append(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size)); /// /// Appends a centered skew matrix from the give angles in radians. @@ -190,7 +189,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + => this.Append(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)); /// /// Appends a skew matrix using the given angles in degrees at the given origin. @@ -248,15 +247,33 @@ namespace SixLabors.ImageSharp.Processing /// 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) => this.Prepend(_ => matrix); + public AffineTransformBuilder PrependMatrix(Matrix3x2 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 AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) => this.Append(_ => matrix); + public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) + { + CheckDegenerate(matrix); + return this.Append(_ => matrix); + } /// /// Returns the combined matrix for a given source size. @@ -269,6 +286,11 @@ namespace SixLabors.ImageSharp.Processing /// 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 Matrix3x2 BuildMatrix(Rectangle sourceRectangle) { @@ -285,9 +307,19 @@ namespace SixLabors.ImageSharp.Processing matrix *= factory(size); } + CheckDegenerate(matrix); + return matrix; } + private static void CheckDegenerate(Matrix3x2 matrix) + { + if (TransformUtilities.IsDegenerate(matrix)) + { + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); + } + } + private AffineTransformBuilder Prepend(Func factory) { this.matrixFactories.Insert(0, factory); @@ -300,4 +332,4 @@ namespace SixLabors.ImageSharp.Processing return this; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/AnchorPositionMode.cs b/src/ImageSharp/Processing/AnchorPositionMode.cs index ef9c0fdaf2..d880de544d 100644 --- a/src/ImageSharp/Processing/AnchorPositionMode.cs +++ b/src/ImageSharp/Processing/AnchorPositionMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/AutoOrientExtensions.cs b/src/ImageSharp/Processing/AutoOrientExtensions.cs deleted file mode 100644 index d11fc96237..0000000000 --- a/src/ImageSharp/Processing/AutoOrientExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index 1ad2c92371..0000000000 --- a/src/ImageSharp/Processing/BackgroundColorExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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 deleted file mode 100644 index 788942dde4..0000000000 --- a/src/ImageSharp/Processing/BinaryDiffuseExtensions.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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 deleted file mode 100644 index 6177701964..0000000000 --- a/src/ImageSharp/Processing/BinaryDitherExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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 deleted file mode 100644 index 31f81ba4b1..0000000000 --- a/src/ImageSharp/Processing/BinaryThresholdExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/BlackWhiteExtensions.cs deleted file mode 100644 index 0484fa84e1..0000000000 --- a/src/ImageSharp/Processing/BlackWhiteExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100644 index 624da239bb..0000000000 --- a/src/ImageSharp/Processing/BoxBlurExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 deleted file mode 100644 index 2f252ad305..0000000000 --- a/src/ImageSharp/Processing/BrightnessExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 deleted file mode 100644 index 3316358954..0000000000 --- a/src/ImageSharp/Processing/ColorBlindnessExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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 2ff19e77e4..2ee14cc163 100644 --- a/src/ImageSharp/Processing/ColorBlindnessMode.cs +++ b/src/ImageSharp/Processing/ColorBlindnessMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/ContrastExtensions.cs b/src/ImageSharp/Processing/ContrastExtensions.cs deleted file mode 100644 index 776aa67518..0000000000 --- a/src/ImageSharp/Processing/ContrastExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 deleted file mode 100644 index 1c0d80afc9..0000000000 --- a/src/ImageSharp/Processing/CropExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 new file mode 100644 index 0000000000..7ed73d5176 --- /dev/null +++ b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Concurrent; +using System.Collections.Generic; +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) + { + return 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 deleted file mode 100644 index 43ba259725..0000000000 --- a/src/ImageSharp/Processing/DefaultInternalImageProcessorContext.cs +++ /dev/null @@ -1,85 +0,0 @@ -// 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 deleted file mode 100644 index 5ac89df291..0000000000 --- a/src/ImageSharp/Processing/DetectEdgesExtensions.cs +++ /dev/null @@ -1,159 +0,0 @@ -// 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 deleted file mode 100644 index 768d28116b..0000000000 --- a/src/ImageSharp/Processing/DiffuseExtensions.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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 deleted file mode 100644 index 795561e702..0000000000 --- a/src/ImageSharp/Processing/DitherExtensions.cs +++ /dev/null @@ -1,77 +0,0 @@ -// 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 deleted file mode 100644 index 1f3526760e..0000000000 --- a/src/ImageSharp/Processing/EdgeDetectionOperators.cs +++ /dev/null @@ -1,61 +0,0 @@ -// 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 deleted file mode 100644 index 157e69ef2a..0000000000 --- a/src/ImageSharp/Processing/EntropyCropExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 new file mode 100644 index 0000000000..244e93f8ba --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The ordered ditherer. + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + 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 image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + 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 new file mode 100644 index 0000000000..d21429589a --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold)); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Rectangle rectangle) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor)); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor, + Rectangle rectangle) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs new file mode 100644 index 0000000000..ea5aedeca7 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new BokehBlurProcessor()); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// 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 to allow chaining of operations. + 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 image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new BokehBlurProcessor(), rectangle); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// 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 structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle) + => 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 new file mode 100644 index 0000000000..1f75838abf --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new BoxBlurProcessor()); + + /// + /// Applies a box blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius) + => source.ApplyProcessor(new BoxBlurProcessor(radius)); + + /// + /// Applies a box blur to the image. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle) + => source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs new file mode 100644 index 0000000000..2377151bb7 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs @@ -0,0 +1,240 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + 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 image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + Rectangle rectangle) => + DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle); + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The 2D edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel) => + DetectEdges(source, kernel, true); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel, + bool grayscale) + { + var processor = new EdgeDetector2DProcessor(kernel, grayscale); + source.ApplyProcessor(processor); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The 2D edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel, + bool grayscale, + Rectangle rectangle) + { + var processor = new EdgeDetector2DProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel) => + DetectEdges(source, kernel, true); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + bool grayscale) + { + var processor = new EdgeDetectorProcessor(kernel, grayscale); + source.ApplyProcessor(processor); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + bool grayscale, + Rectangle rectangle) + { + var processor = new EdgeDetectorProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel) => + DetectEdges(source, kernel, true); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + bool grayscale) + { + var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); + source.ApplyProcessor(processor); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + bool grayscale, + Rectangle rectangle) + { + var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; + } + } +} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs new file mode 100644 index 0000000000..824094935b --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new GaussianBlurProcessor()); + + /// + /// Applies a Gaussian blur to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma) + => source.ApplyProcessor(new GaussianBlurProcessor(sigma)); + + /// + /// Applies a Gaussian blur to the image. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle) + => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs new file mode 100644 index 0000000000..78044e958a --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) => + source.ApplyProcessor(new GaussianSharpenProcessor()); + + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) => + source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); + + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen( + this IImageProcessingContext source, + float sigma, + Rectangle rectangle) => + source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs new file mode 100644 index 0000000000..f4664a5c0f --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs @@ -0,0 +1,154 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither(this IImageProcessingContext source) => + Dither(source, KnownDitherings.Bayer8x8); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither) => + source.ApplyProcessor(new PaletteDitherProcessor(dither)); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + /// The to allow chaining of operations. + 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 using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The palette to select substitute colors from. + /// The to allow chaining of operations. + 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 using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + /// The palette to select substitute colors from. + /// The to allow chaining of operations. + 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 image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither(this IImageProcessingContext source, Rectangle rectangle) => + Dither(source, KnownDitherings.Bayer8x8, rectangle); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 using ordered dithering. + /// + /// The image this method extends. + /// 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 to allow chaining of operations. + 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 using ordered dithering. + /// + /// 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 to allow chaining of operations. + 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 using ordered dithering. + /// + /// The image this method extends. + /// 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 to allow chaining of operations. + 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 new file mode 100644 index 0000000000..847b20e39f --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs @@ -0,0 +1,180 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 current one by blending their pixels. + /// + /// 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) + { + var options = source.GetGraphicsOptions(); + return source.ApplyProcessor( + new DrawImageProcessor( + image, + Point.Empty, + options.ColorBlendingMode, + options.AlphaCompositionMode, + opacity)); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// 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) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + Point.Empty, + colorBlending, + source.GetGraphicsOptions().AlphaCompositionMode, + opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// 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) => + source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// 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) => + 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 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) + { + var options = source.GetGraphicsOptions(); + return source.ApplyProcessor( + new DrawImageProcessor( + image, + location, + options.ColorBlendingMode, + options.AlphaCompositionMode, + opacity)); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// 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) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + location, + colorBlending, + source.GetGraphicsOptions().AlphaCompositionMode, + opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// 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) => + source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// 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) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + location, + options.ColorBlendingMode, + options.AlphaCompositionMode, + options.BlendPercentage)); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs new file mode 100644 index 0000000000..13d9bc3494 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + 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 image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + 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 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 to allow chaining of operations. + public static IImageProcessingContext OilPaint( + this IImageProcessingContext source, + int levels, + int brushSize, + Rectangle rectangle) => + source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs new file mode 100644 index 0000000000..4f48d4e56d --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) + => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + 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 image this method extends. + /// 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 allow chaining of operations. + 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 image this method extends. + /// 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 to allow chaining of operations. + 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 image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) + => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + 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 image this method extends. + /// 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 allow chaining of operations. + 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 image this method extends. + /// 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 to allow chaining of operations. + 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 new file mode 100644 index 0000000000..5316c46cfb --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate(this IImageProcessingContext source) => Pixelate(source, 4); + + /// + /// Pixelates an image with the given pixel size. + /// + /// The image this method extends. + /// The size of the pixels. + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size) => + source.ApplyProcessor(new PixelateProcessor(size)); + + /// + /// Pixelates an image with the given pixel size. + /// + /// The image this method extends. + /// The size of the pixels. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate( + this IImageProcessingContext source, + int size, + Rectangle rectangle) => + source.ApplyProcessor(new PixelateProcessor(size), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs new file mode 100644 index 0000000000..76861af1a5 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BlackWhite(this IImageProcessingContext source) + => source.ApplyProcessor(new BlackWhiteProcessor()); + + /// + /// Applies black and white toning to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BlackWhite(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs new file mode 100644 index 0000000000..7431e499f1 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + 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 new file mode 100644 index 0000000000..d46e3b284b --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The type of color blindness simulator to apply. + /// The to allow chaining of operations. + public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindness) + => source.ApplyProcessor(GetProcessor(colorBlindness)); + + /// + /// Applies the given colorblindness simulator to the image. + /// + /// 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 to allow chaining of operations. + 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(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs new file mode 100644 index 0000000000..01a346aace --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new ContrastProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs new file mode 100644 index 0000000000..d92f1ff48b --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The filter color matrix + /// The to allow chaining of operations. + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix) + => source.ApplyProcessor(new FilterProcessor(matrix)); + + /// + /// Filters an image by the given color matrix + /// + /// The image this method extends. + /// The filter color matrix + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 new file mode 100644 index 0000000000..f8fb4946f8 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source) + => Grayscale(source, GrayscaleMode.Bt709); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// 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) + => Grayscale(source, GrayscaleMode.Bt709, amount); + + /// + /// Applies grayscale toning to the image with the given . + /// + /// The image this method extends. + /// 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 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) + { + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor(amount) + : new GrayscaleBt601Processor(amount); + + source.ApplyProcessor(processor); + return source; + } + + /// + /// Applies grayscale toning to the image. + /// + /// 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) + => Grayscale(source, 1F, rectangle); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// 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) + => Grayscale(source, GrayscaleMode.Bt709, amount, rectangle); + + /// + /// Applies grayscale toning to the image. + /// + /// 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) + => Grayscale(source, mode, 1F, rectangle); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// 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) + { + 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 new file mode 100644 index 0000000000..59a46852d9 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The rotation angle in degrees to adjust the hue. + /// The to allow chaining of operations. + public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees) + => source.ApplyProcessor(new HueProcessor(degrees)); + + /// + /// Alters the hue component of the image. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees, Rectangle rectangle) + => source.ApplyProcessor(new HueProcessor(degrees), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs new file mode 100644 index 0000000000..03bfb2fa8e --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Invert(this IImageProcessingContext source) + => source.ApplyProcessor(new InvertProcessor(1F)); + + /// + /// Inverts the colors of the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Invert(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new InvertProcessor(1F), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs new file mode 100644 index 0000000000..bb9fe97554 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Kodachrome(this IImageProcessingContext source) + => source.ApplyProcessor(new KodachromeProcessor()); + + /// + /// Alters the colors of the image recreating an old Kodachrome camera effect. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 new file mode 100644 index 0000000000..373babb64f --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + 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 new file mode 100644 index 0000000000..2bd4350a69 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + 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 image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 new file mode 100644 index 0000000000..80d7d0c8a5 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The to allow chaining of operations. + public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new OpacityProcessor(amount)); + + /// + /// Multiplies the alpha component of the image. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new OpacityProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs new file mode 100644 index 0000000000..73fa5e1215 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + 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 image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 new file mode 100644 index 0000000000..b7d520be8a --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new SaturateProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs new file mode 100644 index 0000000000..8d818cb0b5 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source) + => Sepia(source, 1F); + + /// + /// Applies sepia toning to the image using the given amount. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new SepiaProcessor(amount)); + + /// + /// Applies sepia toning to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source, Rectangle rectangle) + => Sepia(source, 1F, rectangle); + + /// + /// Applies sepia toning to the image. + /// + /// 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 to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new SepiaProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs b/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs new file mode 100644 index 0000000000..175c7648ae --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) => + HistogramEqualization(source, HistogramEqualizationOptions.Default); + + /// + /// Equalizes the histogram of an image to increases the contrast. + /// + /// The image this method extends. + /// The histogram equalization options to use. + /// The to allow chaining of operations. + public static IImageProcessingContext HistogramEqualization( + this IImageProcessingContext source, + HistogramEqualizationOptions options) => + source.ApplyProcessor(HistogramEqualizationProcessor.FromOptions(options)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs new file mode 100644 index 0000000000..06e640f860 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The color to set as the background. + /// The to allow chaining of operations. + 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 image this method extends. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 image this method extends. + /// The options effecting pixel blending. + /// The color to set as the background. + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + 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 new file mode 100644 index 0000000000..9cffd6e1f0 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source) => + Glow(source, source.GetGraphicsOptions()); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The color to set as the glow. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color) + { + return Glow(source, source.GetGraphicsOptions(), color); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The the radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) => + Glow(source, source.GetGraphicsOptions(), radius); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) => + source.Glow(source.GetGraphicsOptions(), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// 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 to allow chaining of operations. + 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 image this method extends. + /// The options effecting things like blending. + /// The to allow chaining of operations. + 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 image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The to allow chaining of operations. + 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 image this method extends. + /// The options effecting things like blending. + /// The the radius. + /// The to allow chaining of operations. + 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 image this method extends. + /// The options effecting things like blending. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + 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 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 to allow chaining of operations. + 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 image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// The to allow chaining of operations. + 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 new file mode 100644 index 0000000000..43d86b8d5b --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs @@ -0,0 +1,177 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source) => + Vignette(source, source.GetGraphicsOptions()); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The color to set as the vignette. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) => + Vignette(source, source.GetGraphicsOptions(), color); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// The to allow chaining of operations. + 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 image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) => + Vignette(source, source.GetGraphicsOptions(), rectangle); + + /// + /// Applies a radial vignette effect to an image. + /// + /// 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 to allow chaining of operations. + 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 image this method extends. + /// The options effecting pixel blending. + /// The to allow chaining of operations. + 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 image this method extends. + /// The options effecting pixel blending. + /// The color to set as the vignette. + /// The to allow chaining of operations. + 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 image this method extends. + /// The options effecting pixel blending. + /// The the x-radius. + /// The the y-radius. + /// The to allow chaining of operations. + 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 image this method extends. + /// The options effecting pixel blending. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 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 to allow chaining of operations. + 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.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs new file mode 100644 index 0000000000..0bf83812d0 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs @@ -0,0 +1,299 @@ +// Copyright (c) Six Labors. +// 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 + { + /// + /// 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.GetConfiguration(), 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.GetConfiguration(), 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.GetConfiguration(), 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.GetConfiguration(), 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(); + + var visitor = new ProcessingVisitor(configuration, operation, false); + source.AcceptVisitor(visitor); + return visitor.ResultImage; + } + + /// + /// 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.GetConfiguration(), 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.GetConfiguration(), 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; + + public ProcessingVisitor(Configuration configuration, Action operation, bool mutate) + { + this.configuration = configuration; + this.operation = operation; + this.mutate = mutate; + } + + public Image ResultImage { get; private set; } + + 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 new file mode 100644 index 0000000000..f0a35b71b0 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source) => + Quantize(source, KnownQuantizers.Octree); + + /// + /// Applies quantization to the image. + /// + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) => + source.ApplyProcessor(new QuantizeProcessor(quantizer)); + + /// + /// Applies quantization to the image using the . + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) => + Quantize(source, KnownQuantizers.Octree, rectangle); + + /// + /// Applies quantization to the image. + /// + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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 new file mode 100644 index 0000000000..0011101e6a --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image to auto rotate. + /// The to allow chaining of operations. + public static IImageProcessingContext AutoOrient(this IImageProcessingContext source) + => source.ApplyProcessor(new AutoOrientProcessor()); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs new file mode 100644 index 0000000000..5b62b226d7 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image to resize. + /// The target image width. + /// The target image height. + /// The to allow chaining of operations. + 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 image to crop. + /// + /// The structure that specifies the portion of the image object to retain. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => + source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs new file mode 100644 index 0000000000..9324a6977c --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image to crop. + /// The to allow chaining of operations. + public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) => + source.ApplyProcessor(new EntropyCropProcessor()); + + /// + /// Crops an image to the area of greatest entropy. + /// + /// The image to crop. + /// The threshold for entropic density. + /// The to allow chaining of operations. + public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) => + source.ApplyProcessor(new EntropyCropProcessor(threshold)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs new file mode 100644 index 0000000000..9f08ecaaf3 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image to rotate, flip, or both. + /// The to perform the flip. + /// The to allow chaining of operations. + public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) + => source.ApplyProcessor(new FlipProcessor(flipMode)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs new file mode 100644 index 0000000000..5b0614e798 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 source image to pad. + /// The new width. + /// The new height. + /// The to allow chaining of operations. + 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 source image to pad. + /// The new width. + /// The new height. + /// The background color with which to pad the image. + /// The to allow chaining of operations. + public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color) + { + var options = new ResizeOptions + { + Size = new Size(width, height), + Mode = ResizeMode.BoxPad, + Sampler = KnownResamplers.NearestNeighbor, + }; + + return color.Equals(default) ? source.Resize(options) : source.Resize(options).BackgroundColor(color); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs new file mode 100644 index 0000000000..572cc2891d --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs @@ -0,0 +1,176 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image to resize. + /// The target image size. + /// The to allow chaining of operations. + /// 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 image to resize. + /// The target image size. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// 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 image to resize. + /// The target image width. + /// The target image height. + /// The to allow chaining of operations. + /// 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 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 to allow chaining of operations. + /// 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 image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// The to allow chaining of operations. + /// 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 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 to allow chaining of operations. + /// 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 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 to allow chaining of operations. + /// 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 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 to allow chaining of operations. + /// 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) + { + var options = new ResizeOptions + { + 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 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 to allow chaining of operations. + /// 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) + { + var options = new ResizeOptions + { + 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 image to resize. + /// The resize options. + /// The to allow chaining of operations. + /// 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 new file mode 100644 index 0000000000..4b2ee8144f --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image to rotate. + /// The to perform the rotation. + /// The to allow chaining of operations. + public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) => + Rotate(source, (float)rotateMode); + + /// + /// Rotates an image by the given angle in degrees. + /// + /// The image to rotate. + /// The angle in degrees to perform the rotation. + /// The to allow chaining of operations. + 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 image to rotate. + /// The angle in degrees to perform the rotation. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Rotate( + this IImageProcessingContext source, + float degrees, + IResampler sampler) => + source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs new file mode 100644 index 0000000000..f1e3823a04 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image to rotate, flip, or both. + /// The to perform the rotation. + /// The to perform the flip. + /// The to allow chaining of operations. + public static IImageProcessingContext RotateFlip(this IImageProcessingContext source, RotateMode rotateMode, FlipMode flipMode) + => source.Rotate(rotateMode).Flip(flipMode); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs new file mode 100644 index 0000000000..b3fc43dde9 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 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 allow chaining of operations. + 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 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 to allow chaining of operations. + public static IImageProcessingContext Skew( + this IImageProcessingContext source, + float degreesX, + float degreesY, + IResampler sampler) => + source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs new file mode 100644 index 0000000000..15430b28f3 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs @@ -0,0 +1,144 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 image to transform. + /// 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 . + /// The affine transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + AffineTransformBuilder builder, + IResampler sampler) => + ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The affine transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + AffineTransformBuilder builder, + IResampler sampler) + { + Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); + Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform); + return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); + } + + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The transformation matrix. + /// The size of the result image. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + Matrix3x2 transform, + Size targetDimensions, + IResampler sampler) + { + return ctx.ApplyProcessor( + new AffineTransformProcessor(transform, sampler, targetDimensions), + sourceRectangle); + } + + /// + /// Performs a projective transform of an image. + /// + /// The image to transform. + /// The affine transform builder. + /// The to allow chaining of operations. + 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 . + /// The projective transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + ProjectiveTransformBuilder builder, + IResampler sampler) => + ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The projective transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + ProjectiveTransformBuilder builder, + IResampler sampler) + { + Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); + Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform); + return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); + } + + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The transformation matrix. + /// The size of the result image. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + Matrix4x4 transform, + Size targetDimensions, + IResampler sampler) + { + return ctx.ApplyProcessor( + new ProjectiveTransformProcessor(transform, sampler, targetDimensions), + sourceRectangle); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/FilterExtensions.cs b/src/ImageSharp/Processing/FilterExtensions.cs deleted file mode 100644 index 70ac232863..0000000000 --- a/src/ImageSharp/Processing/FilterExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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 deleted file mode 100644 index dfbff7e4da..0000000000 --- a/src/ImageSharp/Processing/FlipExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 96cd38de4a..59e7e8a9c4 100644 --- a/src/ImageSharp/Processing/FlipMode.cs +++ b/src/ImageSharp/Processing/FlipMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/GaussianBlurExtensions.cs deleted file mode 100644 index 165c4ce1a6..0000000000 --- a/src/ImageSharp/Processing/GaussianBlurExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 deleted file mode 100644 index 675bbc142d..0000000000 --- a/src/ImageSharp/Processing/GaussianSharpenExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 deleted file mode 100644 index 8b6e8ffc22..0000000000 --- a/src/ImageSharp/Processing/GlowExtensions.cs +++ /dev/null @@ -1,172 +0,0 @@ -// 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 deleted file mode 100644 index 9ab664056b..0000000000 --- a/src/ImageSharp/Processing/GrayscaleExtensions.cs +++ /dev/null @@ -1,130 +0,0 @@ -// 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 e42a2e6333..27b190d232 100644 --- a/src/ImageSharp/Processing/GrayscaleMode.cs +++ b/src/ImageSharp/Processing/GrayscaleMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs deleted file mode 100644 index 8dabfcc05c..0000000000 --- a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 global contrast using 65536 luminance levels. - /// - /// The pixel format. - /// The image this method extends. - /// The . - public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) - where TPixel : struct, IPixel - => HistogramEqualization(source, 65536); - - /// - /// Equalizes the histogram of an image to increases the global contrast. - /// - /// The pixel format. - /// The image this method extends. - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// The . - public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, int luminanceLevels) - where TPixel : struct, IPixel - => source.ApplyProcessor(new HistogramEqualizationProcessor(luminanceLevels)); - } -} diff --git a/src/ImageSharp/Processing/HueExtensions.cs b/src/ImageSharp/Processing/HueExtensions.cs deleted file mode 100644 index 246d4bf2bd..0000000000 --- a/src/ImageSharp/Processing/HueExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 new file mode 100644 index 0000000000..fb0c2d1c71 --- /dev/null +++ b/src/ImageSharp/Processing/IImageProcessingContext.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +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 1ec2d191f3..1b782d1881 100644 --- a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs +++ b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -14,11 +14,12 @@ namespace SixLabors.ImageSharp.Processing /// Called during mutate operations to generate the image operations provider. /// /// 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(Image source, bool mutate) - where TPixel : struct, IPixel; + IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) + where TPixel : unmanaged, IPixel; } /// @@ -27,10 +28,10 @@ namespace SixLabors.ImageSharp.Processing internal class DefaultImageOperationsProviderFactory : IImageProcessingContextFactory { /// - public IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) - where TPixel : struct, IPixel + public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) + where TPixel : unmanaged, IPixel { - return new DefaultInternalImageProcessorContext(source, mutate); + return new DefaultImageProcessorContext(configuration, source, mutate); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/IImageProcessingContext{TPixel}.cs b/src/ImageSharp/Processing/IImageProcessingContext{TPixel}.cs deleted file mode 100644 index 4897cc58b4..0000000000 --- a/src/ImageSharp/Processing/IImageProcessingContext{TPixel}.cs +++ /dev/null @@ -1,59 +0,0 @@ -// 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 new file mode 100644 index 0000000000..5bbd24cbe0 --- /dev/null +++ b/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 deleted file mode 100644 index 9e031bc95a..0000000000 --- a/src/ImageSharp/Processing/InvertExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100644 index 2b10312fee..0000000000 --- a/src/ImageSharp/Processing/KnownDiffusers.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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 deleted file mode 100644 index dad5bb38c7..0000000000 --- a/src/ImageSharp/Processing/KnownDitherers.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 new file mode 100644 index 0000000000..1c086b7408 --- /dev/null +++ b/src/ImageSharp/Processing/KnownDitherings.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 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 new file mode 100644 index 0000000000..2e279d3408 --- /dev/null +++ b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 9c725d0277..8627707fd1 100644 --- a/src/ImageSharp/Processing/KnownFilterMatrices.cs +++ b/src/ImageSharp/Processing/KnownFilterMatrices.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Primitives; -// Many of these matrices are tranlated from Chromium project where +// Many of these matrices are translated from Chromium project where // SkScalar[] is memory-mapped to a row-major matrix. // The following translates to our column-major form: // @@ -141,7 +140,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets an approximated black and white filter /// - public static ColorMatrix BlackWhiteFilter { get; } = new ColorMatrix() + public static ColorMatrix BlackWhiteFilter { get; } = new ColorMatrix { M11 = 1.5F, M12 = 1.5F, @@ -432,6 +431,32 @@ namespace SixLabors.ImageSharp.Processing return m; } + /// + /// 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--; + + return new ColorMatrix + { + M11 = 1F, + M22 = 1F, + M33 = 1F, + M44 = 1F, + M51 = amount, + M52 = amount, + M53 = amount + }; + } + /// /// Create a sepia filter matrix using the given amount. /// The formula used matches the svg specification. @@ -461,4 +486,4 @@ namespace SixLabors.ImageSharp.Processing }; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/KnownQuantizers.cs b/src/ImageSharp/Processing/KnownQuantizers.cs index e4a7a75d5f..9fc8cf543a 100644 --- a/src/ImageSharp/Processing/KnownQuantizers.cs +++ b/src/ImageSharp/Processing/KnownQuantizers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Quantization; diff --git a/src/ImageSharp/Processing/KnownResamplers.cs b/src/ImageSharp/Processing/KnownResamplers.cs index 70a413ec07..469d662ba4 100644 --- a/src/ImageSharp/Processing/KnownResamplers.cs +++ b/src/ImageSharp/Processing/KnownResamplers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -13,86 +13,86 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x) /// - public static IResampler Bicubic { get; } = new BicubicResampler(); + 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; } = new BoxResampler(); + 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; } = new CatmullRomResampler(); + 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; } = new HermiteResampler(); + 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; } = new Lanczos2Resampler(); + 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; } = new Lanczos3Resampler(); + 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; } = new Lanczos5Resampler(); + 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; } = new Lanczos8Resampler(); + 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; } = new MitchellNetravaliResampler(); + 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; } = new NearestNeighborResampler(); + 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 comprable to . + /// detail preservation (sharpness) and smoothness comparable to . /// - public static IResampler Robidoux { get; } = new RobidouxResampler(); + public static IResampler Robidoux { get; } = CubicResampler.Robidoux; /// - /// Gets the Robidoux Sharp sampler. A sharpend form of the sampler + /// Gets the Robidoux Sharp sampler. A sharpened form of the sampler /// - public static IResampler RobidouxSharp { get; } = new RobidouxSharpResampler(); + public static IResampler RobidouxSharp { get; } = CubicResampler.RobidouxSharp; /// - /// Gets the Spline sampler. A seperable cubic algorithm similar to but yielding smoother results. + /// Gets the Spline sampler. A separable cubic algorithm similar to but yielding smoother results. /// - public static IResampler Spline { get; } = new SplineResampler(); + 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; } = new TriangleResampler(); + public static IResampler Triangle { get; } = default(TriangleResampler); /// - /// Gets the Welch sampler. A high speed algorthm that delivers very sharpened results. + /// Gets the Welch sampler. A high speed algorithm that delivers very sharpened results. /// - public static IResampler Welch { get; } = new WelchResampler(); + public static IResampler Welch { get; } = default(WelchResampler); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/KodachromeExtensions.cs b/src/ImageSharp/Processing/KodachromeExtensions.cs deleted file mode 100644 index e438b131ed..0000000000 --- a/src/ImageSharp/Processing/KodachromeExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100644 index 7dff164026..0000000000 --- a/src/ImageSharp/Processing/LomographExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100644 index b6fa4149a6..0000000000 --- a/src/ImageSharp/Processing/OilPaintExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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 deleted file mode 100644 index fc3fd331de..0000000000 --- a/src/ImageSharp/Processing/OpacityExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 index ba55425b81..a8ba5a55c0 100644 --- a/src/ImageSharp/Processing/OrientationMode.cs +++ b/src/ImageSharp/Processing/OrientationMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/PadExtensions.cs b/src/ImageSharp/Processing/PadExtensions.cs deleted file mode 100644 index f730339686..0000000000 --- a/src/ImageSharp/Processing/PadExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 new file mode 100644 index 0000000000..466bbc5456 --- /dev/null +++ b/src/ImageSharp/Processing/PixelRowOperation.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 deleted file mode 100644 index 4507f63923..0000000000 --- a/src/ImageSharp/Processing/PixelateExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 deleted file mode 100644 index 5d4beee221..0000000000 --- a/src/ImageSharp/Processing/PolaroidExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100644 index 9d06c61d4c..0000000000 --- a/src/ImageSharp/Processing/ProcessingExtensions.cs +++ /dev/null @@ -1,116 +0,0 @@ -// 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 new file mode 100644 index 0000000000..c21d69f90c --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..43023c9382 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Binarization +{ + /// + /// Performs Bradley Adaptive Threshold filter against an image. + /// + 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) + { + var intersect = 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; + + int startY = intersect.Y; + int endY = intersect.Bottom; + int startX = intersect.X; + int endX = intersect.Right; + + int width = intersect.Width; + int height = intersect.Height; + + // 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.Truncate((width / 16f) - 1); + + // Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data. + using (Buffer2D intImage = this.Configuration.MemoryAllocator.Allocate2D(width, height)) + { + Rgba32 rgb = default; + for (int x = startX; x < endX; x++) + { + ulong sum = 0; + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + ref TPixel rowRef = ref MemoryMarshal.GetReference(row); + ref TPixel color = ref Unsafe.Add(ref rowRef, x); + color.ToRgba32(ref rgb); + + sum += (ulong)(rgb.R + rgb.G + rgb.G); + if (x - startX != 0) + { + intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum; + } + else + { + intImage[x - startX, y - startY] = sum; + } + } + } + + var operation = new RowOperation(intersect, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); + ParallelRowIterator.IterateRows( + configuration, + intersect, + in operation); + } + } + + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly Buffer2D intImage; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly float thresholdLimit; + private readonly int startX; + private readonly int endX; + private readonly int startY; + private readonly byte clusterSize; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + ImageFrame source, + Buffer2D intImage, + TPixel upper, + TPixel lower, + float thresholdLimit, + byte clusterSize, + int startX, + int endX, + int startY) + { + this.bounds = bounds; + this.source = source; + this.intImage = intImage; + this.upper = upper; + this.lower = lower; + this.thresholdLimit = thresholdLimit; + this.startX = startX; + this.endX = endX; + this.startY = startY; + this.clusterSize = clusterSize; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Rgba32 rgb = default; + Span pixelRow = this.source.GetPixelRowSpan(y); + + for (int x = this.startX; x < this.endX; x++) + { + TPixel pixel = pixelRow[x]; + pixel.ToRgba32(ref rgb); + + var x1 = Math.Max(x - this.startX - this.clusterSize + 1, 0); + var x2 = Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1); + var y1 = Math.Max(y - this.startY - this.clusterSize + 1, 0); + var y2 = Math.Min(y - this.startY + this.clusterSize + 1, this.bounds.Height - 1); + + var count = (uint)((x2 - x1) * (y2 - y1)); + var sum = (long)Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], long.MaxValue); + + if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.thresholdLimit) + { + this.source[x, y] = this.lower; + } + else + { + this.source[x, y] = this.upper; + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs deleted file mode 100644 index 32cc2f434b..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs +++ /dev/null @@ -1,122 +0,0 @@ -// 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 deleted file mode 100644 index cfdaf107c3..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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 67dcfc7f1b..460a82f0a0 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -1,38 +1,31 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Binarization { /// /// Performs simple binary threshold filtering against an image. /// - /// The pixel format. - internal class BinaryThresholdProcessor : ImageProcessor - where TPixel : struct, IPixel + public class BinaryThresholdProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// 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) + : this(threshold, Color.White, Color.Black) { } /// - /// Initializes a new instance of the class. + /// 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) + public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor) { Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); this.Threshold = threshold; @@ -46,56 +39,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization public float Threshold { get; } /// - /// Gets or sets the color to use for pixels that are above the threshold. + /// Gets the color to use for pixels that are above the threshold. /// - public TPixel UpperColor { get; set; } + public Color UpperColor { get; } /// - /// Gets or sets the color to use for pixels that fall below the threshold. + /// Gets the color to use for pixels that fall below the threshold. /// - public TPixel LowerColor { get; set; } - - /// - 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; - - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + public Color LowerColor { get; } - var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - - 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; - } - } - }); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BinaryThresholdProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..e5672ee9df --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.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; + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + bool isAlphaOnly = typeof(TPixel) == typeof(A8); + + var operation = new RowOperation(interest, source, upper, lower, threshold, isAlphaOnly); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + /// + /// A implementing the clone logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly byte threshold; + private readonly int minX; + private readonly int maxX; + private readonly bool isAlphaOnly; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + ImageFrame source, + TPixel upper, + TPixel lower, + byte threshold, + bool isAlphaOnly) + { + this.source = source; + this.upper = upper; + this.lower = lower; + this.threshold = threshold; + this.minX = bounds.X; + this.maxX = bounds.Right; + this.isAlphaOnly = isAlphaOnly; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Rgba32 rgba = default; + Span row = this.source.GetPixelRowSpan(y); + ref TPixel rowRef = ref MemoryMarshal.GetReference(row); + + for (int x = this.minX; x < this.maxX; x++) + { + ref TPixel color = ref Unsafe.Add(ref rowRef, x); + color.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + color = luminance >= this.threshold ? this.upper : this.lower; + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs index 8150d59218..f1bc828d9e 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs @@ -1,141 +1,21 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 processing algorithms to a clone of the original image. + /// The base class for all cloning image processors. /// - /// The pixel format. - internal abstract class CloningImageProcessor : ICloningImageProcessor - where TPixel : struct, IPixel + public abstract class CloningImageProcessor : ICloningImageProcessor { /// - 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 abstract ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; /// - 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) - { - } + IImageProcessor IImageProcessor.CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => this.CreatePixelSpecificCloningProcessor(configuration, source, 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 new file mode 100644 index 0000000000..2a41329a63 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -0,0 +1,196 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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() + { + try + { + Image clone = this.CreateTarget(); + this.CheckFrameCount(this.Source, clone); + + Configuration configuration = this.Configuration; + 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; + } +#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 + } + } + + /// + 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 from the clone to the source. + this.CheckFrameCount(this.Source, clone); + this.Source.SwapOrCopyPixelsBuffersFrom(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) + { + } + + /// + /// 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) + { + } + + /// + /// 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. + var 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 new file mode 100644 index 0000000000..27eca523c0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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(DefaultRadius, DefaultComponents, 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.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); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs new file mode 100644 index 0000000000..a3b3665900 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -0,0 +1,317 @@ +// Copyright (c) Six Labors. +// 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.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 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; + + // Get the bokeh blur data + BokehBlurKernelData data = BokehBlurKernelDataProvider.GetBokehBlurKernelData( + definition.Radius, + (definition.Radius * 2) + 1, + 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) + { + // Preliminary gamma highlight pass + var gammaOperation = new ApplyGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); + ParallelRowIterator.IterateRows( + this.Configuration, + this.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, this.SourceRectangle, this.Configuration, processingBuffer); + + float inverseGamma = 1 / this.gamma; + + // Apply the inverse gamma exposure pass, and write the final pixel data + var operation = new ApplyInverseGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); + ParallelRowIterator.IterateRows( + this.Configuration, + this.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 = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + // Perform two 1D convolutions for each component in the current instance + ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); + ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); + for (int i = 0; i < this.kernels.Length; i++) + { + // Compute the resulting complex buffer for the current component + Complex64[] kernel = Unsafe.Add(ref baseRef, i); + Vector4 parameters = Unsafe.Add(ref paramsRef, i); + + // Compute the vertical 1D convolution + var verticalOperation = new ApplyVerticalConvolutionRowOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in verticalOperation); + + // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer + var horizontalOperation = new ApplyHorizontalConvolutionRowOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in horizontalOperation); + } + } + + /// + /// A implementing the vertical convolution logic for . + /// + private readonly struct ApplyVerticalConvolutionRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourcePixels; + private readonly Complex64[] kernel; + private readonly int maxY; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyVerticalConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourcePixels, + Complex64[] kernel) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetValues = targetValues; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); + } + } + } + + /// + /// A implementing the horizontal convolution logic for . + /// + private readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourceValues; + private readonly Complex64[] kernel; + private readonly float z; + private readonly float w; + private readonly int maxY; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyHorizontalConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourceValues, + Complex64[] kernel, + float z, + float w) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetValues = targetValues; + this.sourceValues = sourceValues; + this.kernel = kernel; + this.z = z; + this.w = w; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); + } + } + } + + /// + /// 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 void Invoke(int y, Span span) + { + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, 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, 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 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; + var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(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, x); + var clamp = Vector4Utilities.FastClamp(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.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 644d6c9e17..da6b967181 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -1,68 +1,47 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// Applies box blur processing to the image. + /// Defines a box blur processor of a given radius. /// - /// The pixel format. - internal class BoxBlurProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class BoxBlurProcessor : IImageProcessor { /// - /// The maximum size of the kernel in either direction. + /// The default radius used by the parameterless constructor. /// - private readonly int kernelSize; + public const int DefaultRadius = 7; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The 'radius' value representing the size of the area to sample. /// - public BoxBlurProcessor(int radius = 7) + public BoxBlurProcessor(int radius) { this.Radius = radius; - this.kernelSize = (radius * 2) + 1; - this.KernelX = this.CreateBoxKernel(); - this.KernelY = this.KernelX.Transpose(); } /// - /// Gets the Radius + /// Initializes a new instance of the class. /// - public int Radius { get; } - - /// - /// Gets the horizontal gradient operator. - /// - public DenseMatrix KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public DenseMatrix KernelY { get; } - - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); + public BoxBlurProcessor() + : this(DefaultRadius) + { + } /// - /// Create a 1 dimensional Box kernel. + /// Gets the Radius. /// - /// The - private DenseMatrix CreateBoxKernel() - { - int size = this.kernelSize; - var kernel = new DenseMatrix(size, 1); - - kernel.Fill(1F / size); + public int Radius { get; } - return kernel; - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BoxBlurProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..8c5358770c --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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.KernelX = CreateBoxKernel(kernelSize); + this.KernelY = this.KernelX.Transpose(); + } + + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + protected override void OnFrameApply(ImageFrame source) + { + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); + } + + /// + /// Create a 1 dimensional Box kernel. + /// + /// The maximum size of the kernel in either direction. + /// The . + private static DenseMatrix CreateBoxKernel(int kernelSize) + { + var kernel = new DenseMatrix(kernelSize, 1); + kernel.Fill(1F / kernelSize); + return kernel; + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs deleted file mode 100644 index bd1419e4bb..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ /dev/null @@ -1,93 +0,0 @@ -// 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.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. - public Convolution2DProcessor(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; } - - /// - protected override void OnFrameApply( - ImageFrame source, - Rectangle sourceRectangle, - Configuration configuration) - { - DenseMatrix matrixY = this.KernelY; - DenseMatrix matrixX = this.KernelX; - - 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; - - 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); - - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); - } - - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), 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 new file mode 100644 index 0000000000..3a5f35cd14 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -0,0 +1,161 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 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 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) + { + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + + source.CopyTo(targetPixels); + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, 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 int maxY; + private readonly int maxX; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernelY; + private readonly DenseMatrix kernelX; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernelY, + DenseMatrix kernelX, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernelY = kernelY; + this.kernelX = kernelX; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D3( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D4( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs deleted file mode 100644 index 05007c3706..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ /dev/null @@ -1,108 +0,0 @@ -// 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.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. - public Convolution2PassProcessor(DenseMatrix kernelX, DenseMatrix kernelY) - { - this.KernelX = kernelX; - this.KernelY = kernelY; - } - - /// - /// Gets the horizontal gradient operator. - /// - public DenseMatrix KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public DenseMatrix KernelY { get; } - - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - using (Buffer2D firstPassPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) - { - source.CopyTo(firstPassPixels); - - 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; - 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; - - 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); - - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX); - } - - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), 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 new file mode 100644 index 0000000000..b61690415a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 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 Convolution2PassProcessor( + Configuration configuration, + in DenseMatrix kernelX, + in DenseMatrix kernelY, + bool preserveAlpha, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + 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) + { + using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // Horizontal convolution + var horizontalOperation = new RowOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in horizontalOperation); + + // Vertical convolution + var verticalOperation = new RowOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in verticalOperation); + } + + /// + /// 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 DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; + + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs deleted file mode 100644 index 8ef64bdacc..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ /dev/null @@ -1,77 +0,0 @@ -// 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.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. - /// - /// The pixel format. - internal class ConvolutionProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The 2d gradient operator. - public ConvolutionProcessor(DenseMatrix kernelXY) => this.KernelXY = kernelXY; - - /// - /// Gets the 2d gradient operator. - /// - public DenseMatrix KernelXY { get; } - - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - DenseMatrix matrix = this.KernelXY; - 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.Size())) - { - 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; - - 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); - - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); - } - - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), 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 new file mode 100644 index 0000000000..7b1ceff276 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + internal static class ConvolutionProcessorHelpers + { + /// + /// Kernel radius is calculated using the minimum viable value. + /// See . + /// + internal static int GetDefaultGaussianRadius(float sigma) + { + return (int)MathF.Ceiling(sigma * 3); + } + + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function. + /// + /// The . + internal static DenseMatrix CreateGaussianBlurKernel(int size, float weight) + { + var kernel = new DenseMatrix(size, 1); + + 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; + } + + // Normalize kernel so that the sum of all weights equals 1 + for (int i = 0; i < size; i++) + { + kernel[0, i] /= sum; + } + + return kernel; + } + + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function + /// + /// The . + internal static DenseMatrix CreateGaussianSharpenKernel(int size, float weight) + { + var kernel = new DenseMatrix(size, 1); + + float sum = 0; + + 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/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs new file mode 100644 index 0000000000..95fef15f62 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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) + : base(configuration, source, sourceRectangle) + { + this.KernelXY = kernelXY; + this.PreserveAlpha = preserveAlpha; + } + + /// + /// 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; } + + /// + protected override void OnFrameApply(ImageFrame source) + { + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + source.CopyTo(targetPixels); + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, 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 int maxY; + private readonly int maxX; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs index 8927716492..1d0a29a359 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs @@ -1,57 +1,42 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 a processor that detects edges within an image using two one-dimensional matrices. + /// Defines edge detection using the two 1D gradient operators. /// - /// The pixel format. - internal abstract class EdgeDetector2DProcessor : ImageProcessor, IEdgeDetectorProcessor - where TPixel : struct, IPixel + public sealed class EdgeDetector2DProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// 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(DenseMatrix kernelX, DenseMatrix kernelY, bool grayscale) + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetector2DProcessor(EdgeDetector2DKernel kernel, 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.Kernel = kernel; this.Grayscale = grayscale; } /// - /// Gets the horizontal gradient operator. + /// Gets the 2D edge detector kernel. /// - public DenseMatrix KernelX { get; } + public EdgeDetector2DKernel Kernel { get; } /// - /// Gets the vertical gradient operator. + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. /// - public DenseMatrix KernelY { get; } - - /// - public bool Grayscale { get; set; } + public bool Grayscale { get; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2DProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); - - /// - protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - if (this.Grayscale) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetector2DProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..80f8a7706b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 var processor = new Convolution2DProcessor( + 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 4165cf024e..083a69bd24 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs @@ -1,177 +1,42 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Defines a processor that detects edges within an image using a eight two dimensional matrices. + /// Defines edge detection using eight gradient operators. /// - /// The pixel format. - internal abstract class EdgeDetectorCompassProcessor : ImageProcessor, IEdgeDetectorProcessor - where TPixel : struct, IPixel + public sealed class EdgeDetectorCompassProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// Whether to convert the image to grayscale before performing edge detection. - protected EdgeDetectorCompassProcessor(bool grayscale) + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetectorCompassProcessor(EdgeDetectorCompassKernel kernel, bool grayscale) { + this.Kernel = kernel; this.Grayscale = grayscale; } /// - /// Gets the North gradient operator - /// - public abstract DenseMatrix North { get; } - - /// - /// Gets the NorthWest gradient operator + /// Gets the edge detector kernel. /// - public abstract DenseMatrix NorthWest { get; } + public EdgeDetectorCompassKernel Kernel { get; } /// - /// Gets the West gradient operator + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. /// - 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 }; - - 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]).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]).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); - } - } - }); - } - } - } - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetectorCompassProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..27963613e1 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // We need a clean copy for each pass to start from + using ImageFrame cleanCopy = source.Clone(); + + using (var processor = new ConvolutionProcessor(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 (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[i], true, this.Source, interest)) + { + processor.Apply(pass); + } + + var operation = new RowOperation(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 int minX; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Buffer2D targetPixels, + Buffer2D passPixels, + Rectangle bounds) + { + this.targetPixels = targetPixels; + this.passPixels = passPixels; + this.minX = bounds.X; + this.maxX = bounds.Right; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y)); + + for (int 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); + + var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); + + currentTargetPixel.FromVector4(pixelValue); + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index 9173bc229b..6fcfca6625 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -1,52 +1,42 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 a processor that detects edges within an image using a single two dimensional matrix. + /// Defines edge detection using a single 2D gradient operator. /// - /// The pixel format. - internal abstract class EdgeDetectorProcessor : ImageProcessor, IEdgeDetectorProcessor - where TPixel : struct, IPixel + public sealed class EdgeDetectorProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The 2d gradient operator. - /// Whether to convert the image to grayscale before performing edge detection. - protected EdgeDetectorProcessor(DenseMatrix kernelXY, bool grayscale) + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetectorProcessor(EdgeDetectorKernel kernel, bool grayscale) { - this.KernelXY = kernelXY; + this.Kernel = kernel; this.Grayscale = grayscale; } - /// - public bool Grayscale { get; } - /// - /// Gets the 2d gradient operator. + /// Gets the edge detector kernel. /// - public DenseMatrix KernelXY { get; } + public EdgeDetectorKernel Kernel { get; } - /// - protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - if (this.Grayscale) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - } + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. + /// + public bool Grayscale { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new ConvolutionProcessor(this.KernelXY).Apply(source, sourceRectangle, configuration); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetectorProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..62dd549190 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 var processor = new ConvolutionProcessor(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 b3bc15d391..1fa65b62cd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -1,38 +1,39 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// Applies Gaussian blur processing to the image. + /// Defines Gaussian blur by a (Sigma, Radius) pair. /// - /// The pixel format. - internal class GaussianBlurProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class GaussianBlurProcessor : IImageProcessor { /// - /// The maximum size of the kernel in either direction. + /// The default value for . /// - private readonly int kernelSize; + public const float DefaultSigma = 3f; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. + /// + public GaussianBlurProcessor() + : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) + { + } + + /// + /// 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)) + public GaussianBlurProcessor(float sigma) + : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma)) { - // Kernel radius is calculated using the minimum viable value. - // http://chemaguerra.com/gaussian-filter-radius/ } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The 'radius' value representing the size of the area to sample. @@ -43,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The 'sigma' value representing the weight of the blur. @@ -54,10 +55,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public GaussianBlurProcessor(float sigma, int radius) { - this.kernelSize = (radius * 2) + 1; this.Sigma = sigma; - this.KernelX = this.CreateGaussianKernel(); - this.KernelY = this.KernelX.Transpose(); + this.Radius = radius; } /// @@ -66,47 +65,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public float Sigma { get; } /// - /// Gets the horizontal gradient operator. + /// Gets the radius defining the size of the area to sample. /// - public DenseMatrix KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public DenseMatrix KernelY { get; } - - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); + public int Radius { 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); - - 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; - } - - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) - { - kernel[0, i] /= sum; - } - - return kernel; - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new GaussianBlurProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..a9b692a015 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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. + public GaussianBlurProcessor( + Configuration configuration, + GaussianBlurProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + int kernelSize = (definition.Radius * 2) + 1; + this.KernelX = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); + this.KernelY = this.KernelX.Transpose(); + } + + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + protected override void OnFrameApply(ImageFrame source) + { + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 786bf7757a..7e1f029066 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -1,40 +1,39 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// Applies Gaussian sharpening processing to the image. + /// Defines Gaussian sharpening by a (Sigma, Radius) pair. /// - /// The pixel format. - internal class GaussianSharpenProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class GaussianSharpenProcessor : IImageProcessor { /// - /// The maximum size of the kernel in either direction. + /// The default value for . /// - private readonly int kernelSize; + public const float DefaultSigma = 3f; /// - /// Initializes a new instance of the class. + /// 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)) + public GaussianSharpenProcessor() + : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) + { + } + + /// + /// 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)) { - // Kernel radius is calculated using the minimum viable value. - // http://chemaguerra.com/gaussian-filter-radius/ } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The 'radius' value representing the size of the area to sample. @@ -45,10 +44,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The 'sigma' value representing the weight of the sharpen. + /// The 'sigma' value representing the weight of the blur. /// /// /// The 'radius' value representing the size of the area to sample. @@ -56,10 +55,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public GaussianSharpenProcessor(float sigma, int radius) { - this.kernelSize = (radius * 2) + 1; this.Sigma = sigma; - this.KernelX = this.CreateGaussianKernel(); - this.KernelY = this.KernelX.Transpose(); + this.Radius = radius; } /// @@ -68,63 +65,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public float Sigma { get; } /// - /// Gets the horizontal gradient operator. - /// - public DenseMatrix KernelX { get; } - - /// - /// Gets the vertical gradient operator. + /// Gets the radius defining the size of the area to sample. /// - public DenseMatrix KernelY { get; } + public int Radius { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); - - /// - /// 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); - - float sum = 0; - - 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; - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new GaussianSharpenProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..5e20865e5c --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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. + public GaussianSharpenProcessor( + Configuration configuration, + GaussianSharpenProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + int kernelSize = (definition.Radius * 2) + 1; + this.KernelX = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma); + this.KernelY = this.KernelX.Transpose(); + } + + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + protected override void OnFrameApply(ImageFrame source) + { + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/IEdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/IEdgeDetectorProcessor.cs deleted file mode 100644 index b2ecbf1158..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/IEdgeDetectorProcessor.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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 deleted file mode 100644 index dd4d023025..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KayyaliKernels.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100644 index 8652efa120..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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/Kernels/EdgeDetector2DKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs new file mode 100644 index 0000000000..ed363595d7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 EdgeDetector2DKernel KayyaliKernel = new EdgeDetector2DKernel(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY); + + /// + /// An edge detection kernel containing two Prewitt operators. + /// . + /// + public static EdgeDetector2DKernel PrewittKernel = new EdgeDetector2DKernel(PrewittKernels.PrewittX, PrewittKernels.PrewittY); + + /// + /// An edge detection kernel containing two Roberts-Cross operators. + /// . + /// + public static EdgeDetector2DKernel RobertsCrossKernel = new EdgeDetector2DKernel(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY); + + /// + /// An edge detection kernel containing two Scharr operators. + /// + public static EdgeDetector2DKernel ScharrKernel = new EdgeDetector2DKernel(ScharrKernels.ScharrX, ScharrKernels.ScharrY); + + /// + /// An edge detection kernel containing two Sobel operators. + /// . + /// + public static EdgeDetector2DKernel SobelKernel = new EdgeDetector2DKernel(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 new file mode 100644 index 0000000000..bda8617993 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 EdgeDetectorCompassKernel Kirsch = + new EdgeDetectorCompassKernel( + 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 EdgeDetectorCompassKernel Robinson = + new EdgeDetectorCompassKernel( + 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() => + new[] + { + 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 new file mode 100644 index 0000000000..86b8a24d90 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 EdgeDetectorKernel Laplacian3x3 = new EdgeDetectorKernel(LaplacianKernels.Laplacian3x3); + + /// + /// An edge detection kernel containing a 5x5 Laplacian operator. + /// + /// + public static EdgeDetectorKernel Laplacian5x5 = new EdgeDetectorKernel(LaplacianKernels.Laplacian5x5); + + /// + /// An edge detection kernel containing a Laplacian of Gaussian operator. + /// . + /// + public static EdgeDetectorKernel LaplacianOfGaussian = new EdgeDetectorKernel(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 new file mode 100644 index 0000000000..dbd749c1ca --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..87ccb174d4 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..960ff30ebe --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/Kernels/Implementation/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs new file mode 100644 index 0000000000..7036300c72 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..67e52a8f16 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/Kernels/Implementation/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs new file mode 100644 index 0000000000..40c811ca6e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/Kernels/Implementation/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs new file mode 100644 index 0000000000..7d0478aa50 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..72c48b273c --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/Kernels/Implementation/SobelKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs new file mode 100644 index 0000000000..333b275baf --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/KirschKernels.cs deleted file mode 100644 index 86232e306a..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KirschKernels.cs +++ /dev/null @@ -1,101 +0,0 @@ -// 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 deleted file mode 100644 index c3188676f3..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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 deleted file mode 100644 index f498d374cc..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index 558acf7b35..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index 19f2d1161b..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernelFactory.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 deleted file mode 100644 index e7b7f965b9..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernels.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 deleted file mode 100644 index 6cc65dc587..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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/Parameters/BokehBlurKernelData.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs new file mode 100644 index 0000000000..09ce7864ad --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..ff52d14ba1 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +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 ConcurrentDictionary(); + + /// + /// Gets the kernel scales to adjust the component values in each kernel + /// + private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; + + /// + /// Gets the available bokeh blur kernel parameters + /// + private static IReadOnlyList KernelComponents { get; } = new[] + { + // 1 component + new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) }, + + // 2 components + new[] + { + new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), + new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) + }, + + // 3 components + new[] + { + 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[] + { + 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[] + { + 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[] + { + 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 + var parameters = new BokehBlurParameters(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.Count)); + + 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) + { + var 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, 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) + { + var 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, 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, i); + int length = kernelRef.Length; + ref Complex64 valueRef = ref kernelRef[0]; + ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, i); + + for (int j = 0; j < length; j++) + { + for (int k = 0; k < length; k++) + { + ref Complex64 jRef = ref Unsafe.Add(ref valueRef, j); + ref Complex64 kRef = ref Unsafe.Add(ref valueRef, 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, i); + int length = kernelsRef.Length; + ref Complex64 valueRef = ref kernelsRef[0]; + + for (int j = 0; j < length; j++) + { + Unsafe.Add(ref valueRef, j) *= scalar; + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs new file mode 100644 index 0000000000..d2d20e06ae --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 deleted file mode 100644 index 381e028d49..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/PrewittKernels.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100644 index 75ef4dac62..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossKernels.cs deleted file mode 100644 index f61220e1ec..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossKernels.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 deleted file mode 100644 index d685860f62..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index 4f47184e30..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonKernels.cs +++ /dev/null @@ -1,101 +0,0 @@ -// 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 deleted file mode 100644 index 193c1008dd..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 deleted file mode 100644 index f0662c6672..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ScharrKernels.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100644 index 79fc0e79fc..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index 113957c839..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/SobelKernels.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100644 index 3ca53f6f0f..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index 7a9753d1a9..0000000000 --- a/src/ImageSharp/Processing/Processors/DelegateProcessor.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 deleted file mode 100644 index 17c97ddc9b..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 deleted file mode 100644 index b7fdfbfe5f..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 deleted file mode 100644 index 4f6d5dd077..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 deleted file mode 100644 index 8d0c23aa30..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 deleted file mode 100644 index 84455b24ad..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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/ErroDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs new file mode 100644 index 0000000000..4c01fb4023 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs @@ -0,0 +1,188 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 ErrorDither Atkinson = CreateAtkinson(); + + /// + /// Applies error diffusion based dithering using the Burks image dithering algorithm. + /// + public static ErrorDither Burkes = CreateBurks(); + + /// + /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. + /// + public static ErrorDither FloydSteinberg = CreateFloydSteinberg(); + + /// + /// Applies error diffusion based dithering using the Jarvis, Judice, Ninke image dithering algorithm. + /// + public static ErrorDither JarvisJudiceNinke = CreateJarvisJudiceNinke(); + + /// + /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. + /// + public static ErrorDither Sierra2 = CreateSierra2(); + + /// + /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. + /// + public static ErrorDither Sierra3 = CreateSierra3(); + + /// + /// Applies error diffusion based dithering using the Sierra Lite image dithering algorithm. + /// + public static ErrorDither SierraLite = CreateSierraLite(); + + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. + /// + public static ErrorDither StevensonArce = CreateStevensonArce(); + + /// + /// Applies error diffusion based dithering using the Stucki image dithering algorithm. + /// + public static ErrorDither Stucki = CreateStucki(); + + private static ErrorDither CreateAtkinson() + { + const float Divisor = 8F; + const int Offset = 1; + + var matrix = new float[,] + { + { 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; + + var matrix = new float[,] + { + { 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; + + var matrix = new float[,] + { + { 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; + + var matrix = new float[,] + { + { 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; + + var matrix = new float[,] + { + { 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; + + var matrix = new float[,] + { + { 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; + + var matrix = new float[,] + { + { 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; + + var matrix = new float[,] + { + { 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; + + var matrix = new float[,] + { + { 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/ErrorDiffuserBase.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs deleted file mode 100644 index 642da2f001..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs +++ /dev/null @@ -1,126 +0,0 @@ -// 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(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 deleted file mode 100644 index 911d3e8fdc..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs new file mode 100644 index 0000000000..30ac5f135b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -0,0 +1,215 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +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) + { + 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 + { + int offsetY = bounds.Top; + int offsetX = bounds.Left; + float scale = quantizer.Options.DitherScale; + + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + + for (int x = bounds.Left; x < bounds.Right; x++) + { + TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x); + Unsafe.Add(ref destinationRowRef, x - offsetX) = 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 + { + float scale = processor.DitherScale; + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + for (int x = bounds.Left; x < bounds.Right; x++) + { + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); + TPixel transformed = Unsafe.AsRef(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; + + // 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 = image.GetPixelRowSpan(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]; + var result = pixel.ToVector4(); + + result += error * coefficient; + pixel.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); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs deleted file mode 100644 index 6a7655b593..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 new file mode 100644 index 0000000000..1b046c7bc7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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. + 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. + 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 deleted file mode 100644 index 5b30c0dc4d..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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 deleted file mode 100644 index 571929b99d..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 new file mode 100644 index 0000000000..5ab3be70d6 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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. + /// + Configuration Configuration { get; } + + /// + /// Gets the dithering palette. + /// + ReadOnlyMemory Palette { get; } + + /// + /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. + /// + float DitherScale { get; } + + /// + /// Returns the color from the dithering palette corresponding to the given color. + /// + /// The color to match. + /// The match. + TPixel GetPaletteColor(TPixel color); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs deleted file mode 100644 index a69557d6de..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 new file mode 100644 index 0000000000..71ad6db973 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 OrderedDither Bayer2x2 = new OrderedDither(2); + + /// + /// Applies order dithering using the 4x4 Bayer dithering matrix. + /// + public static OrderedDither Bayer4x4 = new OrderedDither(4); + + /// + /// Applies order dithering using the 8x8 Bayer dithering matrix. + /// + public static OrderedDither Bayer8x8 = new OrderedDither(8); + + /// + /// Applies order dithering using the 3x3 ordered dithering matrix. + /// + public static OrderedDither Ordered3x3 = new OrderedDither(3); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 174732f802..b11411e32a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -1,50 +1,282 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// /// An ordered dithering matrix with equal sides of arbitrary length /// - public class OrderedDither : IOrderedDither + public readonly partial struct OrderedDither : IDither, IEquatable, IEquatable { - private readonly DenseMatrix thresholdMatrix; + private readonly DenseMatrix thresholdMatrix; private readonly int modulusX; private readonly int modulusY; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The length of the matrix sides + [MethodImpl(InliningOptions.ShortMethod)] public OrderedDither(uint 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 + var thresholdMatrix = new DenseMatrix((int)length); + float m2 = length * length; + for (int y = 0; y < length; y++) + { + for (int x = 0; x < length; x++) + { + thresholdMatrix[y, x] = ((ditherMatrix[y, x] + 1) / m2) - .5F; + } + } + 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 + { + var ditherOperation = new QuantizeDitherRowOperation( + ref quantizer, + in Unsafe.AsRef(this), + source, + destination, + bounds); + + ParallelRowIterator.IterateRows( + quantizer.Configuration, + bounds, + in ditherOperation); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, + ImageFrame source, + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel + { + var ditherOperation = new PaletteDitherRowOperation( + in processor, + in Unsafe.AsRef(this), + source, + bounds); - // 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++) + ParallelRowIterator.IterateRows( + processor.Configuration, + bounds, + in ditherOperation); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal TPixel Dither( + TPixel source, + int x, + int y, + int bitDepth, + float scale) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + source.ToRgba32(ref rgba); + Rgba32 attempt; + + // Spread assumes an even colorspace distribution and precision. + // Calculated as 0-255/component count. 256 / bitDepth + // https://bisqwit.iki.fi/story/howto/dither/jy/ + // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm + int spread = 256 / bitDepth; + float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; + + attempt.R = (byte)(rgba.R + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.G = (byte)(rgba.G + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.B = (byte)(rgba.B + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.A = (byte)(rgba.A + factor).Clamp(byte.MinValue, byte.MaxValue); + + TPixel result = default; + result.FromRgba32(attempt); + + return result; + } + + /// + 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); + + private readonly struct QuantizeDitherRowOperation : IRowOperation + where TFrameQuantizer : struct, IQuantizer + where TPixel : unmanaged, IPixel + { + private readonly TFrameQuantizer quantizer; + private readonly OrderedDither dither; + private readonly ImageFrame source; + private readonly IndexedImageFrame destination; + private readonly Rectangle bounds; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public QuantizeDitherRowOperation( + ref TFrameQuantizer quantizer, + in OrderedDither dither, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) { - for (int x = 0; x < ditherMatrix.Columns; x++) + this.quantizer = quantizer; + this.dither = dither; + this.source = source; + this.destination = destination; + this.bounds = bounds; + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Length); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + float scale = this.quantizer.Options.DitherScale; + + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1; + TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale); + Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _); } } - - this.thresholdMatrix = ditherMatrix; } - /// - public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) - where TPixel : struct, IPixel + private readonly struct PaletteDitherRowOperation : IRowOperation + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel { - image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; + private readonly TPaletteDitherImageProcessor processor; + private readonly OrderedDither dither; + private readonly ImageFrame source; + private readonly Rectangle bounds; + private readonly float scale; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteDitherRowOperation( + in TPaletteDitherImageProcessor processor, + in OrderedDither dither, + ImageFrame source, + Rectangle bounds) + { + this.processor = processor; + this.dither = dither; + this.source = source; + this.bounds = bounds; + this.scale = processor.DitherScale; + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Span.Length); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); + TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); + sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered); + } + } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs deleted file mode 100644 index 93bce0578a..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 4b93c42590..5e2f30f74a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { @@ -21,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { // Calculate the the logarithm of length to the base 2 uint exponent = 0; - uint bayerLength = 0; + uint bayerLength; do { exponent++; @@ -91,4 +90,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering 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 deleted file mode 100644 index 1b4910a147..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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 new file mode 100644 index 0000000000..bb6614a7e0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 = ditherScale.Clamp(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 deleted file mode 100644 index 43f6c8a454..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs +++ /dev/null @@ -1,105 +0,0 @@ -// 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.ToScaledVector4(configuration, this.Palette, this.paletteVector); - } - } - } -} \ 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 new file mode 100644 index 0000000000..789d02046e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +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(this.Configuration, sourcePalette, this.paletteOwner.Memory.Span); + + this.ditherProcessor = new DitherProcessor( + this.Configuration, + this.paletteOwner.Memory, + definition.DitherScale); + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + var 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.paletteOwner = null; + base.Dispose(disposing); + } + + /// + /// Used to allow inlining of calls to + /// . + /// + private readonly struct DitherProcessor : IPaletteDitherImageProcessor + { + private readonly EuclideanPixelMap pixelMap; + + [MethodImpl(InliningOptions.ShortMethod)] + public DitherProcessor( + Configuration configuration, + ReadOnlyMemory palette, + float ditherScale) + { + this.Configuration = configuration; + this.pixelMap = new EuclideanPixelMap(configuration, palette); + 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; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs deleted file mode 100644 index 13660d30ab..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 deleted file mode 100644 index ebde2ceaf8..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 deleted file mode 100644 index 144a83a821..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 deleted file mode 100644 index d71fba9f2e..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 deleted file mode 100644 index 4b1323065f..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 deleted file mode 100644 index 1dd510a5ec..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 ea412f6351..27dea8af17 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt +++ b/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt @@ -1,3 +1,6 @@ +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 new file mode 100644 index 0000000000..42fb22c959 Binary files /dev/null and b/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf differ diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs new file mode 100644 index 0000000000..fc954412e5 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 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. + public DrawImageProcessor( + Image image, + Point location, + PixelColorBlendingMode colorBlendingMode, + PixelAlphaCompositionMode alphaCompositionMode, + float opacity) + { + this.Image = image; + this.Location = location; + this.ColorBlendingMode = colorBlendingMode; + this.AlphaCompositionMode = alphaCompositionMode; + this.Opacity = opacity; + } + + /// + /// Gets the image to blend. + /// + public Image Image { get; } + + /// + /// Gets the location to draw the blended image. + /// + public Point Location { 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 + { + var visitor = new ProcessorFactoryVisitor(configuration, this, source, sourceRectangle); + this.Image.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; + private readonly Rectangle sourceRectangle; + + public ProcessorFactoryVisitor(Configuration configuration, DrawImageProcessor definition, Image source, Rectangle sourceRectangle) + { + this.configuration = configuration; + this.definition = definition; + this.source = source; + this.sourceRectangle = sourceRectangle; + } + + 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.sourceRectangle, + this.definition.Location, + 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 new file mode 100644 index 0000000000..9b3dbcaa36 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -0,0 +1,157 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.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 source area to process for the current processor instance. + /// 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( + Configuration configuration, + Image image, + Image source, + Rectangle sourceRectangle, + Point location, + PixelColorBlendingMode colorBlendingMode, + PixelAlphaCompositionMode alphaCompositionMode, + float opacity) + : base(configuration, source, sourceRectangle) + { + 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 = this.SourceRectangle; + Configuration configuration = this.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."); + } + + var operation = new RowOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); + ParallelRowIterator.IterateRows( + configuration, + workingRect, + in operation); + } + + /// + /// A implementing the draw logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame sourceFrame; + private readonly Image targetImage; + private readonly PixelBlender blender; + private readonly Configuration configuration; + private readonly int minX; + private readonly int width; + private readonly int locationY; + private readonly int targetX; + private readonly float opacity; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + ImageFrame sourceFrame, + Image targetImage, + PixelBlender blender, + Configuration configuration, + int minX, + int width, + int locationY, + int targetX, + float opacity) + { + this.sourceFrame = sourceFrame; + this.targetImage = targetImage; + this.blender = blender; + this.configuration = configuration; + this.minX = minX; + this.width = width; + this.locationY = locationY; + this.targetX = targetX; + this.opacity = opacity; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.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 new file mode 100644 index 0000000000..00049ec1be --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 1b17c470ed..692d222eed 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -1,27 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Effects { /// - /// Applies oil painting effect processing to the image. + /// Defines an oil painting effect. /// - /// Adapted from by Dewald Esterhuizen. - /// The pixel format. - internal class OilPaintingProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class OilPaintingProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// 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. @@ -39,111 +29,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects } /// - /// Gets the intensity levels + /// Gets the number of intensity levels. /// public int Levels { get; } /// - /// Gets the brush size + /// 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; - - 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); - } - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new OilPaintingProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..21ec8a9c74 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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.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 brushSize = this.definition.BrushSize; + if (brushSize <= 0 || brushSize > source.Height || brushSize > source.Width) + { + throw new ArgumentOutOfRangeException(nameof(brushSize)); + } + + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + source.CopyTo(targetPixels); + + var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); + ParallelRowIterator.IterateRowIntervals( + this.Configuration, + this.SourceRectangle, + in operation); + + 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 ImageFrame source; + private readonly Configuration configuration; + private readonly int radius; + private readonly int levels; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + Buffer2D targetPixels, + ImageFrame 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); + + ref float binsRef = ref bins.GetReference(); + ref int intensityBinRef = ref Unsafe.As(ref binsRef); + ref float redBinRef = ref Unsafe.Add(ref binsRef, this.levels); + ref float blueBinRef = ref Unsafe.Add(ref redBinRef, this.levels); + ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, this.levels); + + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRowPixelSpan = this.source.GetPixelRowSpan(y); + Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); + + PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); + + 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 = offsetY.Clamp(0, maxY); + + Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); + + for (int fx = 0; fx <= this.radius; fx++) + { + int fxr = fx - this.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 * (this.levels - 1)); + + Unsafe.Add(ref intensityBinRef, currentIntensity)++; + Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; + Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; + Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; + + if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) + { + maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); + maxIndex = currentIntensity; + } + } + + float red = MathF.Abs(Unsafe.Add(ref redBinRef, maxIndex) / maxIntensity); + float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, maxIndex) / maxIntensity); + float green = MathF.Abs(Unsafe.Add(ref greenBinRef, maxIndex) / maxIntensity); + float alpha = sourceRowVector4Span[x].W; + + targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); + } + } + + Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs new file mode 100644 index 0000000000..a9c942b305 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 + { + 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); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs new file mode 100644 index 0000000000..6b63c885a0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowOperation(interest.X, source, 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 ImageFrame source; + private readonly Configuration configuration; + private readonly PixelConversionModifiers modifiers; + private readonly TDelegate rowProcessor; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + ImageFrame 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 void Invoke(int y, Span span) + { + Span rowSpan = this.source.GetPixelRowSpan(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(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 50f76efed7..b8c5eb7b3a 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -1,25 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// Applies a pixelation effect processing to the image. + /// Defines a pixelation effect of a given size. /// - /// The pixel format. - internal class PixelateProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class PixelateProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The size of the pixels. Must be greater than 0. /// @@ -36,80 +28,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// 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; - } - - // 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; - } - } - } - }); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new PixelateProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..0f307f8f15 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +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) + { + var 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).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 ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + int size, + ImageFrame 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.GetPixelRowSpan(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 new file mode 100644 index 0000000000..2e6bf95913 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 57c1bad39b..63326d2996 100644 --- a/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. /// - /// The pixel format. - internal class AchromatomalyProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class AchromatomalyProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public AchromatomalyProcessor() : base(KnownFilterMatrices.AchromatomalyFilter) diff --git a/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs index 696a854ab8..782bac529e 100644 --- a/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. /// - /// The pixel format. - internal class AchromatopsiaProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class AchromatopsiaProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// 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 9925ce5c21..e5e556dc21 100644 --- a/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Applies a black and white filter matrix to the image + /// Applies a black and white filter matrix to the image. /// - /// The pixel format. - internal class BlackWhiteProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class BlackWhiteProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public BlackWhiteProcessor() : base(KnownFilterMatrices.BlackWhiteFilter) diff --git a/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs index b1b8ad7478..bc424e4629 100644 --- a/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a brightness filter matrix using the given amount. /// - /// The pixel format. - internal class BrightnessProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class BrightnessProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// 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. diff --git a/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs index ebec464d5c..cc7385d4be 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a contrast filter matrix using the given amount. /// - /// The pixel format. - internal class ContrastProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class ContrastProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// 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. @@ -27,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters } /// - /// Gets the proportion of the conversion + /// Gets the proportion of the conversion. /// public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs index 0d1b1da902..3afef7b7eb 100644 --- a/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. /// - /// The pixel format. - internal class DeuteranomalyProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class DeuteranomalyProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DeuteranomalyProcessor() : base(KnownFilterMatrices.DeuteranomalyFilter) diff --git a/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs index ae0727048e..9bd7f449be 100644 --- a/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. /// - /// The pixel format. - internal class DeuteranopiaProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class DeuteranopiaProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DeuteranopiaProcessor() : base(KnownFilterMatrices.DeuteranopiaFilter) diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs index d6a32d8899..51760f8a2e 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs @@ -1,25 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// Provides methods that accept a matrix to apply free-form filters to images. + /// Defines a free-form color filter by a . /// - /// The pixel format. - internal class FilterProcessor : ImageProcessor - where TPixel : struct, IPixel + public class FilterProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The matrix used to apply the image filter public FilterProcessor(ColorMatrix matrix) => this.Matrix = matrix; @@ -29,31 +21,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// 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); - - PixelOperations.Instance.FromVector4(configuration, vectorSpan, rowSpan); - } - }); - } + /// + public virtual IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new FilterProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..584ba072c8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowOperation(interest.X, source, 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 ImageFrame source; + private readonly ColorMatrix matrix; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + ImageFrame source, + ColorMatrix matrix, + Configuration configuration) + { + this.startX = startX; + this.source = source; + this.matrix = matrix; + this.configuration = configuration; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); + + Vector4Utilities.Transform(span, ref Unsafe.AsRef(this.matrix)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs index c933d4858f..f2c5e023da 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.601 /// - /// The pixel format. - internal class GrayscaleBt601Processor : FilterProcessor - where TPixel : struct, IPixel + public sealed class GrayscaleBt601Processor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The proportion of the conversion. Must be between 0 and 1. public GrayscaleBt601Processor(float amount) diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs index 1716773b4c..ace25f1fb6 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 /// - /// The pixel format. - internal class GrayscaleBt709Processor : FilterProcessor - where TPixel : struct, IPixel + public sealed class GrayscaleBt709Processor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The proportion of the conversion. Must be between 0 and 1. public GrayscaleBt709Processor(float amount) @@ -23,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters } /// - /// Gets the proportion of the conversion + /// Gets the proportion of the conversion. /// public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs index 4c3a0c73ed..2ff99009a1 100644 --- a/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs @@ -1,18 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a hue filter matrix using the given angle of rotation in degrees /// - internal class HueProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class HueProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The angle of rotation in degrees public HueProcessor(float degrees) diff --git a/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs index 462c420707..95937e5b30 100644 --- a/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a filter matrix that inverts the colors of an image /// - /// The pixel format. - internal class InvertProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class InvertProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The proportion of the conversion. Must be between 0 and 1. public InvertProcessor(float amount) diff --git a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs index 003766e8ab..fa9cc0874d 100644 --- a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a filter matrix recreating an old Kodachrome camera effect matrix to the image /// - /// The pixel format. - internal class KodachromeProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class KodachromeProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public KodachromeProcessor() : base(KnownFilterMatrices.KodachromeFilter) diff --git a/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs new file mode 100644 index 0000000000..ba73793c71 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 737ebf6188..c2ea2b6274 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs @@ -1,33 +1,30 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Processors.Filters { /// /// Converts the colors of the image recreating an old Lomograph effect. /// - /// The pixel format. - internal class LomographProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class LomographProcessor : FilterProcessor { - private static readonly TPixel VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public LomographProcessor() + /// Graphics options to use within the processor. + public LomographProcessor(GraphicsOptions graphicsOptions) : base(KnownFilterMatrices.LomographFilter) { + this.GraphicsOptions = graphicsOptions; } - /// - protected override void AfterFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle, configuration); - } + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } + + /// + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => + new LomographProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..9ee52c5a74 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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.FromRgba(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 0fea61cad9..0ccdfafbd1 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies an opacity filter matrix using the given amount. /// - /// The pixel format. - internal class OpacityProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class OpacityProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The proportion of the conversion. Must be between 0 and 1. public OpacityProcessor(float amount) @@ -23,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters } /// - /// Gets the proportion of the conversion + /// Gets the proportion of the conversion. /// public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs new file mode 100644 index 0000000000..9bb3644762 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +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) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + var operation = new OpaqueRowOperation(this.Configuration, source, interest); + ParallelRowIterator.IterateRows(this.Configuration, interest, in operation); + } + + private readonly struct OpaqueRowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly ImageFrame target; + private readonly Rectangle bounds; + + [MethodImpl(InliningOptions.ShortMethod)] + public OpaqueRowOperation( + Configuration configuration, + ImageFrame target, + Rectangle bounds) + { + this.configuration = configuration; + this.target = target; + this.bounds = bounds; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span targetRowSpan = this.target.GetPixelRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, 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, 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 fb065ac176..9f5b495a51 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs @@ -1,35 +1,30 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Processors.Filters { /// /// Converts the colors of the image recreating an old Polaroid effect. /// - /// The pixel format. - internal class PolaroidProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class PolaroidProcessor : FilterProcessor { - private static readonly TPixel VeryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); - private static readonly TPixel LightOrange = ColorBuilder.FromRGBA(255, 153, 102, 128); - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public PolaroidProcessor() + /// Graphics options to use within the processor. + public PolaroidProcessor(GraphicsOptions graphicsOptions) : base(KnownFilterMatrices.PolaroidFilter) { + this.GraphicsOptions = graphicsOptions; } - /// - 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); - } + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } + + /// + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => + new PolaroidProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..96238c5b31 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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.FromRgba(255, 153, 102, 128); + private static readonly Color VeryDarkOrange = Color.FromRgb(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 79eb708518..0e8f571f5c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Converts the colors of the image recreating Protanomaly (Red-Weak) color blindness. /// - /// The pixel format. - internal class ProtanomalyProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class ProtanomalyProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ProtanomalyProcessor() : base(KnownFilterMatrices.ProtanomalyFilter) diff --git a/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs index c6a01439a2..59735a28cc 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. /// - /// The pixel format. - internal class ProtanopiaProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class ProtanopiaProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ProtanopiaProcessor() : base(KnownFilterMatrices.ProtanopiaFilter) diff --git a/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs index 75e956071e..051c4ca6bd 100644 --- a/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a saturation filter matrix using the given amount. /// - /// The pixel format. - internal class SaturateProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class SaturateProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. diff --git a/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs index 2009dccd56..8a0780e46f 100644 --- a/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Applies a sepia filter matrix using the given amount. /// - /// The pixel format. - internal class SepiaProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class SepiaProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The proportion of the conversion. Must be between 0 and 1. public SepiaProcessor(float amount) diff --git a/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs index 593f7f5b01..bb031d0ef2 100644 --- a/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. /// - /// The pixel format. - internal class TritanomalyProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class TritanomalyProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public TritanomalyProcessor() : base(KnownFilterMatrices.TritanomalyFilter) diff --git a/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs index 153ad5559a..926fd70c54 100644 --- a/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs @@ -1,19 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// /// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. /// - /// The pixel format. - internal class TritanopiaProcessor : FilterProcessor - where TPixel : struct, IPixel + public sealed class TritanopiaProcessor : FilterProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public TritanopiaProcessor() : base(KnownFilterMatrices.TritanopiaFilter) diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs index 024ccbced1..d7ef2982cb 100644 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs @@ -1,32 +1,27 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { /// - /// Encapsulates methods to alter the pixels of a new image, cloned from the original image. + /// Defines an algorithm to alter the pixels of a cloned image. /// - /// The pixel format. - internal interface ICloningImageProcessor : IImageProcessor - where TPixel : struct, IPixel + public interface ICloningImageProcessor : IImageProcessor { /// - /// Applies the process to the specified portion of the specified . + /// Creates a pixel specific that is capable of executing + /// the processing algorithm on an . /// + /// 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. /// - /// - /// 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); + /// The + ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs new file mode 100644 index 0000000000..9ae89b22ac --- /dev/null +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 d7fe0465be..86a514bb61 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor.cs @@ -1,31 +1,30 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { /// - /// Encapsulates methods to alter the pixels of an image. + /// 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. /// - /// The pixel format. - public interface IImageProcessor - where TPixel : struct, IPixel + public interface IImageProcessor { /// - /// Applies the process to the specified portion of the specified . + /// Creates a pixel specific that is capable of executing + /// the processing algorithm on an . /// + /// 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. /// - /// - /// is null. - /// - /// - /// doesn't fit the dimension of the image. - /// - void Apply(Image source, Rectangle sourceRectangle); + /// The + IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs new file mode 100644 index 0000000000..3f2779c822 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 deleted file mode 100644 index 0d27a9e1e8..0000000000 --- a/src/ImageSharp/Processing/Processors/ImageProcessor.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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 new file mode 100644 index 0000000000..bbd2261284 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..b0896636ea --- /dev/null +++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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() + { + try + { + this.BeforeImageApply(); + + foreach (ImageFrame sourceFrame in this.Source.Frames) + { + this.Apply(sourceFrame); + } + + this.AfterImageApply(); + } +#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 a single image frame. + /// + /// the source image. + public void Apply(ImageFrame source) + { + try + { + this.BeforeFrameApply(source); + this.OnFrameApply(source); + this.AfterFrameApply(source); + } +#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 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 prepare the processor. + /// + /// The source image. Cannot be null. + protected virtual void AfterFrameApply(ImageFrame source) + { + } + + /// + /// This method is called after the process is applied to prepare the processor. + /// + protected virtual void AfterImageApply() + { + } + + /// + /// 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/AdaptiveHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs new file mode 100644 index 0000000000..56593acb84 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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) + { + return 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 new file mode 100644 index 0000000000..b5b07d7a87 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -0,0 +1,645 @@ +// Copyright (c) Six Labors. +// 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 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 = 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(this.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; + int yStart = halfTileHeight; + for (int tile = 0; tile < tileCount - 1; tile++) + { + tileYStartPositions.Add((yStart, cdfY)); + cdfY++; + yStart += tileHeight; + } + + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); + ParallelRowIterator.IterateRowIntervals( + this.Configuration, + new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), + in operation); + + ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); + + // Fix left column + ProcessBorderColumn(ref pixelsBase, cdfData, 0, sourceWidth, sourceHeight, this.Tiles, 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, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); + + // Fix top row + ProcessBorderRow(ref pixelsBase, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + + // Fix bottom row + int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; + ProcessBorderRow(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, 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 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( + ref TPixel pixelBase, + CdfTileData cdfData, + int cdfX, + int sourceWidth, + int sourceHeight, + int tileCount, + int tileHeight, + int xStart, + int xEnd, + int luminanceLevels) + { + int halfTileHeight = 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++) + { + int dyOffSet = dy * sourceWidth; + 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)); + } + + tileY++; + } + + cdfY++; + y += tileHeight; + } + } + + /// + /// 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 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( + ref TPixel pixelBase, + CdfTileData cdfData, + int cdfY, + int sourceWidth, + int tileCount, + int tileWidth, + int yStart, + int yEnd, + int luminanceLevels) + { + int halfTileWidth = tileWidth / 2; + + int cdfX = 0; + int x = halfTileWidth; + for (int tile = 0; tile < tileCount - 1; tile++) + { + 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++; + } + } + + 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 ImageFrame 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, + ImageFrame 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) + { + ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0); + + for (int index = rows.Min; index < rows.Max; index++) + { + (int y, int cdfY) tileYStartPosition = this.tileYStartPositions[index]; + int y = tileYStartPosition.y; + int cdfYY = tileYStartPosition.cdfY; + + 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++) + { + int dyOffSet = dy * this.sourceWidth; + int tileX = 0; + for (int dx = x; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenFourTiles( + pixel, + this.cdfData, + this.tileCount, + this.tileCount, + tileX, + tileY, + cdfX, + cdfYY, + this.tileWidth, + this.tileHeight, + this.luminanceLevels); + + pixel.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 = 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) + { + var operation = new RowIntervalOperation( + processor, + this.memoryAllocator, + this.cdfMinBuffer2D, + this.cdfLutBuffer2D, + this.tileYStartPositions, + this.tileWidth, + this.tileHeight, + this.luminanceLevels, + source); + + 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.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(); + } + + 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 ImageFrame 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, + ImageFrame 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) + { + ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0); + + 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); + ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); + + 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.GetRowSpan(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++) + { + int dyOffset = dy * this.sourceWidth; + for (int dx = x; dx < xlimit; dx++) + { + int luminance = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), this.luminanceLevels); + histogram[luminance]++; + } + } + + if (this.processor.ClipHistogramEnabled) + { + this.processor.ClipHistogram(histogram, this.processor.ClipLimit); + } + + Unsafe.Add(ref cdfMinBase, cdfX) = this.processor.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 new file mode 100644 index 0000000000..046405fc44 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..a61c68de3b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -0,0 +1,441 @@ +// Copyright (c) Six Labors. +// 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; + +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; + + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }; + int tileWidth = source.Width / this.Tiles; + int tileHeight = tileWidth; + int pixelInTile = tileWidth * tileHeight; + int halfTileHeight = tileHeight / 2; + int halfTileWidth = halfTileHeight; + var slidingWindowInfos = new SlidingWindowInfos(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. + var innerOperation = new SlidingWindowOperation( + 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. + var leftBorderOperation = new SlidingWindowOperation( + 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. + var rightBorderOperation = new SlidingWindowOperation( + 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. + var topBorderOperation = new SlidingWindowOperation( + 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. + var bottomBorderOperation = new SlidingWindowOperation( + 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 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 = ImageMaths.GetBT709Luminance(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 = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + Unsafe.Add(ref histogramBase, 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) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + + this.processor.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 + ? this.processor.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) + : this.processor.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, luminance) / numberOfPixelsMinusCdfMin; + this.targetPixels[x, y].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) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + this.processor.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) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + this.processor.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/GlobalHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs new file mode 100644 index 0000000000..30478117aa --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Defines a global histogram equalization applicable to an . + /// + public class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// 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) + { + } + + /// + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new GlobalHistogramEqualizationProcessor( + configuration, + this.LuminanceLevels, + this.ClipHistogram, + this.ClipLimit, + source, + sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs new file mode 100644 index 0000000000..19514c4b6f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -0,0 +1,166 @@ +// Copyright (c) Six Labors. +// 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; + +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; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + + // Build the histogram of the grayscale levels + var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source, this.LuminanceLevels); + ParallelRowIterator.IterateRows( + 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 = 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 + var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in cdfOperation); + } + + /// + /// A implementing the grayscale levels logic for . + /// + private readonly struct GrayscaleLevelsRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner histogramBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + + [MethodImpl(InliningOptions.ShortMethod)] + public GrayscaleLevelsRowOperation( + Rectangle bounds, + IMemoryOwner histogramBuffer, + ImageFrame source, + int luminanceLevels) + { + this.bounds = bounds; + this.histogramBuffer = histogramBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) + { + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; + } + } + } + + /// + /// A implementing the cdf application levels logic for . + /// + private readonly struct CdfApplicationRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner cdfBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + private readonly float numberOfPixelsMinusCdfMin; + + [MethodImpl(InliningOptions.ShortMethod)] + public CdfApplicationRowOperation( + Rectangle bounds, + IMemoryOwner cdfBuffer, + ImageFrame source, + int luminanceLevels, + float numberOfPixelsMinusCdfMin) + { + this.bounds = bounds; + this.cdfBuffer = cdfBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.luminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs new file mode 100644 index 0000000000..3b81e1134f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// 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 + { + /// + /// A global histogram equalization. + /// + Global, + + /// + /// Adaptive histogram equalization using a tile interpolation approach. + /// + AdaptiveTileInterpolation, + + /// + /// 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 new file mode 100644 index 0000000000..602dc0c4be --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// 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 + { + /// + /// Gets the default instance. + /// + public static HistogramEqualizationOptions Default { get; } = new HistogramEqualizationOptions(); + + /// + /// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization. + /// + public HistogramEqualizationMethod Method { get; set; } = HistogramEqualizationMethod.Global; + + /// + /// 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 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; } = false; + + /// + /// 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 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; + } +} diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index 580adc7fe9..60686f4014 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -1,35 +1,27 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -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 a global histogram equalization to the image. + /// Defines a processor that normalizes the histogram of an image. /// - /// The pixel format. - internal class HistogramEqualizationProcessor : ImageProcessor - where TPixel : struct, IPixel + public abstract class HistogramEqualizationProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// 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. - public HistogramEqualizationProcessor(int luminanceLevels) + /// 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) { - Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); - this.LuminanceLevels = luminanceLevels; + this.ClipHistogram = clipHistogram; + this.ClipLimit = clipLimit; } /// @@ -37,92 +29,64 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// public int LuminanceLevels { 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(); - - // Build the histogram of the grayscale levels. - using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - { - Span histogram = histogramBuffer.GetSpan(); - for (int i = 0; i < pixels.Length; i++) - { - TPixel sourcePixel = pixels[i]; - int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels); - histogram[luminance]++; - } - - // Calculate the cumulative distribution function, which will map each input pixel to a new value. - Span cdf = cdfBuffer.GetSpan(); - int cdfMin = this.CalculateCdf(cdf, histogram); - - // Apply the cdf to each pixel of the image - float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; - for (int i = 0; i < pixels.Length; i++) - { - TPixel sourcePixel = pixels[i]; + /// + /// Gets a value indicating whether to clip the histogram bins at a specific value. + /// + public bool ClipHistogram { get; } - int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels); - float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; + /// + /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// + public int ClipLimit { get; } - pixels[i].FromVector4(new Vector4(luminanceEqualized)); - } - } - } + /// + public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; /// - /// Calculates the cumulative distribution function. + /// Creates the that implements the algorithm + /// defined by the given . /// - /// The array holding the cdf. - /// The histogram of the input image. - /// The first none zero value of the cdf. - private int CalculateCdf(Span cdf, Span histogram) + /// The . + /// The . + public static HistogramEqualizationProcessor FromOptions(HistogramEqualizationOptions options) { - // Calculate the cumulative histogram - int histSum = 0; - for (int i = 0; i < histogram.Length; i++) - { - histSum += histogram[i]; - cdf[i] = histSum; - } + HistogramEqualizationProcessor processor; - // Get the first none zero value of the cumulative histogram - int cdfMin = 0; - for (int i = 0; i < histogram.Length; i++) + switch (options.Method) { - if (cdf[i] != 0) - { - cdfMin = cdf[i]; + case HistogramEqualizationMethod.Global: + processor = new GlobalHistogramEqualizationProcessor( + options.LuminanceLevels, + options.ClipHistogram, + options.ClipLimit); break; - } - } - // Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop - for (int i = 0; i < histogram.Length; i++) - { - cdf[i] = Math.Max(0, cdf[i] - cdfMin); - } + case HistogramEqualizationMethod.AdaptiveTileInterpolation: + processor = new AdaptiveHistogramEqualizationProcessor( + options.LuminanceLevels, + options.ClipHistogram, + options.ClipLimit, + options.NumberOfTiles); + break; - return cdfMin; - } + case HistogramEqualizationMethod.AdaptiveSlidingWindow: + processor = new AdaptiveHistogramEqualizationSlidingWindowProcessor( + options.LuminanceLevels, + options.ClipHistogram, + options.ClipLimit, + options.NumberOfTiles); + break; - /// - /// 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)] - private int GetLuminance(TPixel sourcePixel, int luminanceLevels) - { - // Convert to grayscale using ITU-R Recommendation BT.709 - var vector = sourcePixel.ToVector4(); - int luminance = Convert.ToInt32(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1)); + default: + processor = new GlobalHistogramEqualizationProcessor( + options.LuminanceLevels, + options.ClipHistogram, + options.ClipLimit); + break; + } - return luminance; + return processor; } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs new file mode 100644 index 0000000000..2849574bc2 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -0,0 +1,149 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.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 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; + } + + // 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 (int i = 0; i < 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 (int i = 0; i < histogram.Length; i++) + { + Unsafe.Add(ref histogramBase, i) += addToEachBin; + } + } + + int residual = sumOverClip - (addToEachBin * this.LuminanceLevels); + if (residual != 0) + { + int residualStep = Math.Max(this.LuminanceLevels / residual, 1); + for (int i = 0; i < 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) + { + var vector = sourcePixel.ToVector4(); + return ImageMaths.GetBT709Luminance(ref vector, luminanceLevels); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs index 25787ff922..bce604c655 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs @@ -1,31 +1,21 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// Sets the background color of the image. + /// Defines a processing operation to replace the background color of an . /// - /// The pixel format. - internal class BackgroundColorProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class BackgroundColorProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// 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) + /// The to set the background color to. + public BackgroundColorProcessor(GraphicsOptions options, Color color) { this.Color = color; this.GraphicsOptions = options; @@ -39,69 +29,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// Gets the background color value. /// - public TPixel Color { get; } + public Color 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; - } - - 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()); - } - }); - } - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BackgroundColorProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..76dcc2194b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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; + + var 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); + + var operation = new RowOperation(configuration, interest, blender, amount, colors, source); + 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 ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + PixelBlender blender, + IMemoryOwner amount, + IMemoryOwner colors, + ImageFrame 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.GetPixelRowSpan(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 21f6be69f8..fae5b7c55d 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -1,158 +1,56 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// An that applies a radial glow effect an . + /// Defines a radial glow effect applicable to an . /// - /// The pixel format. - internal class GlowProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class GlowProcessor : IImageProcessor { - private readonly PixelBlender blender; - - /// - /// Initializes a new instance of the class. - /// - /// The color or the glow. - public GlowProcessor(TPixel color) - : this(color, 0) - { - } - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// The options effecting blending and composition. /// The color or the glow. - /// The radius of the glow. - public GlowProcessor(TPixel color, ValueSize radius) - : this(color, radius, GraphicsOptions.Default) + public GlowProcessor(GraphicsOptions options, Color color) + : this(options, color, 0) { } /// - /// Initializes a new instance of the class. + /// 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) - { - } - - /// - /// 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) + internal GlowProcessor(GraphicsOptions options, Color color, ValueSize radius) { this.GlowColor = color; this.Radius = radius; - this.blender = PixelOperations.Instance.GetPixelBlender(options); this.GraphicsOptions = options; } /// - /// Gets the options effecting blending and composition + /// Gets the options effecting blending and composition. /// public GraphicsOptions GraphicsOptions { get; } /// - /// Gets or sets the glow color to apply. + /// Gets the glow color to apply. /// - public TPixel GlowColor { get; set; } + public Color GlowColor { get; } /// - /// Gets or sets the the radius. + /// Gets the the radius. /// - public ValueSize Radius { get; set; } + internal ValueSize Radius { get; } - /// - 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); - } - }); - } - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new GlowProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..c028903f41 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -0,0 +1,119 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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; + + var 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); + + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + 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 ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + ImageFrame 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 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] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(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 a8fa1d65c1..749f82594f 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -1,63 +1,38 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// An that applies a radial vignette effect to an . + /// Defines a radial vignette effect applicable to an . /// - /// The pixel format. - internal class VignetteProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class VignetteProcessor : IImageProcessor { - private readonly PixelBlender blender; - - /// - /// Initializes a new instance of the class. - /// - /// The color of the vignette. - public VignetteProcessor(TPixel color) - : this(color, GraphicsOptions.Default) - { - } - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The color of the vignette. /// The options effecting blending and composition. - public VignetteProcessor(TPixel color, GraphicsOptions options) + /// The color of the vignette. + public VignetteProcessor(GraphicsOptions options, Color color) { this.VignetteColor = color; this.GraphicsOptions = options; - this.blender = PixelOperations.Instance.GetPixelBlender(options); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// The options effecting blending and composition. /// 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) + internal VignetteProcessor(GraphicsOptions options, Color color, ValueSize radiusX, ValueSize radiusY) { this.VignetteColor = color; this.RadiusX = radiusX; this.RadiusY = radiusY; - this.blender = PixelOperations.Instance.GetPixelBlender(options); this.GraphicsOptions = options; } @@ -67,94 +42,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays public GraphicsOptions GraphicsOptions { get; } /// - /// Gets or sets the vignette color to apply. + /// Gets the vignette color to apply. /// - public TPixel VignetteColor { get; set; } + public Color VignetteColor { get; } /// - /// Gets or sets the the x-radius. + /// Gets the the x-radius. /// - public ValueSize RadiusX { get; set; } + internal ValueSize RadiusX { get; } /// - /// Gets or sets the the y-radius. + /// Gets 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); + internal ValueSize RadiusY { get; } - 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); - } - }); - } - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new VignetteProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..d09e3b22a9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -0,0 +1,127 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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; + + var 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); + + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + 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 ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + ImageFrame 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 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] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(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/DefaultPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs new file mode 100644 index 0000000000..ccdae0f089 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs @@ -0,0 +1,107 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +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. + + var ratio = new Rational(r); + + int denom = (int)ratio.Denominator; + int num = (int)ratio.Numerator; + + for (int pos = 0; pos < totalNumberOfRows; pos++) + { + int subPos = pos % 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); + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs new file mode 100644 index 0000000000..c194f402a3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Numerics; +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. + internal readonly struct EuclideanPixelMap + where TPixel : unmanaged, IPixel + { + private readonly Vector4[] vectorCache; + private readonly ConcurrentDictionary distanceCache; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration. + /// The color palette to map from. + [MethodImpl(InliningOptions.ShortMethod)] + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) + { + this.Palette = palette; + this.vectorCache = new Vector4[palette.Length]; + + // Use the same rules across all target frameworks. + this.distanceCache = new ConcurrentDictionary(Environment.ProcessorCount, 31); + PixelOperations.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache); + } + + /// + /// Gets the color palette of this . + /// The palette memory is owned by the palette source that created it. + /// + public ReadOnlyMemory Palette + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } + + /// + /// Returns the closest color in the palette and the index of that pixel. + /// The palette contents must match the one used in the constructor. + /// + /// The color to match. + /// The matched color. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + public int GetClosestColor(TPixel color, out TPixel match) + { + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); + + // Check if the color is in the lookup table + if (!this.distanceCache.TryGetValue(color, out int index)) + { + return this.GetClosestColorSlow(color, ref paletteRef, out match); + } + + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int GetClosestColorSlow(TPixel color, ref TPixel paletteRef, out TPixel match) + { + // Loop through the palette and find the nearest match. + int index = 0; + float leastDistance = float.MaxValue; + var vector = color.ToVector4(); + ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference(this.vectorCache); + for (int i = 0; i < this.Palette.Length; i++) + { + Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); + float distance = Vector4.DistanceSquared(vector, candidate); + + // If it's an exact match, exit the loop + if (distance == 0) + { + index = i; + break; + } + + if (distance < leastDistance) + { + // Less than... assign. + index = i; + leastDistance = distance; + } + } + + // Now I have the index, pop it into the cache for next time + this.distanceCache[color] = index; + match = Unsafe.Add(ref paletteRef, index); + return index; + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs new file mode 100644 index 0000000000..a998685c08 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +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(); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs deleted file mode 100644 index 3b9b046a00..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs +++ /dev/null @@ -1,211 +0,0 @@ -// 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.ToScaledVector4(image.Configuration, palette, this.paletteVector); - 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; - } - - /// - /// 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/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs deleted file mode 100644 index 50fdb5b587..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 to allow the execution of the quantization process on an image frame. - /// - /// The pixel format. - public interface IFrameQuantizer - 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 new file mode 100644 index 0000000000..b31dba6148 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Provides an abstraction to enumerate pixel regions within a multi-framed . + /// + public interface IPixelSamplingStrategy + { + /// + /// Enumerates pixel regions within the image as . + /// + /// The image. + /// The pixel type. + /// An enumeration of pixel regions. + IEnumerable> EnumeratePixelRegions(Image image) + where TPixel : unmanaged, IPixel; + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs index f1490a6d2b..62e04b3f20 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -12,27 +11,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public interface IQuantizer { /// - /// Gets the error diffusion algorithm to apply to the output image. + /// Gets the quantizer options defining quantization rules. /// - IErrorDiffuser Diffuser { get; } + QuantizerOptions Options { get; } /// - /// Creates the generic frame quantizer + /// Creates the generic frame quantizer. /// /// The to configure internal operations. /// The pixel format. - /// The - IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel; + /// The . + IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) + where TPixel : unmanaged, IPixel; /// - /// Creates the generic frame quantizer + /// 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; + /// The options to create the quantizer with. + /// The . + IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, 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 new file mode 100644 index 0000000000..993a8d7fac --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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. + /// + Configuration Configuration { get; } + + /// + /// Gets the quantizer options defining quantization rules. + /// + QuantizerOptions Options { get; } + + /// + /// Gets the quantized color palette. + /// + /// + /// The palette has not been built via . + /// + ReadOnlyMemory Palette { get; } + + /// + /// Adds colors to the quantized palette from the given pixel source. + /// + /// The of source pixels to register. + void AddPaletteColors(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 . + /// + 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. + 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/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs deleted file mode 100644 index dd56375f63..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ /dev/null @@ -1,574 +0,0 @@ -// 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 d49023886b..861697594a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -1,88 +1,48 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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. /// - /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 - /// /// public class OctreeQuantizer : IQuantizer { - /// - /// Initializes a new instance of the class. - /// - public OctreeQuantizer() - : this(true) - { - } - - /// - /// 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) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// using the default . /// - /// Whether to apply dithering to the output image - public OctreeQuantizer(bool dither) - : this(GetDiffuser(dither), QuantizerConstants.MaxColors) - { - } - - /// - /// 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 OctreeQuantizer() + : this(DefaultOptions) { } /// /// 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) + /// The quantizer options defining quantization rules. + public OctreeQuantizer(QuantizerOptions options) { - this.Diffuser = diffuser; - this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + Guard.NotNull(options, nameof(options)); + this.Options = options; } /// - public IErrorDiffuser Diffuser { get; } + public QuantizerOptions Options { 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); - } + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) + where TPixel : unmanaged, IPixel + => this.CreatePixelSpecificQuantizer(configuration, this.Options); - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + => new OctreeQuantizer(configuration, options); } -} \ 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 new file mode 100644 index 0000000000..f4d55ebeb3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -0,0 +1,568 @@ +// Copyright (c) Six Labors. +// 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; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Encapsulates methods to calculate the color palette if an image using an Octree pattern. + /// + /// + /// The pixel format. + public struct OctreeQuantizer : IQuantizer + where TPixel : unmanaged, IPixel + { + private readonly int maxColors; + private readonly Octree octree; + private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; + private EuclideanPixelMap pixelMap; + private readonly bool isDithering; + private bool isDisposed; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + [MethodImpl(InliningOptions.ShortMethod)] + public OctreeQuantizer(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.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.maxColors).Clamp(1, 8)); + this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.palette = default; + this.pixelMap = default; + this.isDithering = !(this.Options.Dither is null); + this.isDisposed = false; + } + + /// + public Configuration Configuration { get; } + + /// + public QuantizerOptions Options { get; } + + /// + public ReadOnlyMemory Palette + { + get + { + QuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; + } + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void AddPaletteColors(Buffer2DRegion pixelRegion) + { + Rectangle bounds = pixelRegion.Rectangle; + Buffer2D source = pixelRegion.Buffer; + using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); + + // Loop through each row + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); + + for (int x = 0; x < bufferSpan.Length; x++) + { + Rgba32 rgba = bufferSpan[x]; + + // Add the color to the Octree + this.octree.AddColor(rgba); + } + } + + Span paletteSpan = this.paletteOwner.GetSpan(); + int paletteIndex = 0; + this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); + + // Length of reduced palette + transparency. + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + + this.palette = result; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + { + // Octree only maps the RGB component of a color + // so cannot tell the difference between a fully transparent + // pixel and a black one. + if (this.isDithering || color.Equals(default)) + { + return (byte)this.pixelMap.GetClosestColor(color, out match); + } + + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); + var index = (byte)this.octree.GetPaletteIndex(color); + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.paletteOwner.Dispose(); + this.paletteOwner = null; + } + } + + /// + /// Class which does the actual quantization. + /// + private sealed class Octree + { + /// + /// 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 Rgba32 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 the mask used when getting the appropriate pixels for a given node. + /// + private static ReadOnlySpan Mask => new byte[] + { + 0b10000000, + 0b1000000, + 0b100000, + 0b10000, + 0b1000, + 0b100, + 0b10, + 0b1 + }; + + /// + /// Gets or sets the number of leaves in the tree + /// + public int Leaves + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + + [MethodImpl(InliningOptions.ShortMethod)] + set; + } + + /// + /// Gets the array of reducible nodes + /// + private OctreeNode[] ReducibleNodes + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } + + /// + /// Add a given color value to the Octree + /// + /// The color to add. + public void AddColor(Rgba32 color) + { + // Check if this request is for the same color as the last + if (this.previousColor.Equals(color)) + { + // 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 = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); + } + else + { + // Just update the previous node + this.previousNode.Increment(ref color); + } + } + else + { + this.previousColor = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); + } + } + + /// + /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors + /// + /// The palette to fill. + /// The maximum number of colors + /// The palette index, used to calculate the final size of the palette. + [MethodImpl(InliningOptions.ShortMethod)] + public void Palletize(Span palette, int colorCount, ref int paletteIndex) + { + while (this.Leaves > colorCount - 1) + { + this.Reduce(); + } + + this.root.ConstructPalette(palette, ref paletteIndex); + } + + /// + /// Get the palette index for the passed color + /// + /// The color to match. + /// + /// The index. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetPaletteIndex(TPixel color) + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + return this.root.GetPaletteIndex(ref rgba, 0); + } + + /// + /// Keep track of the previous node that was quantized + /// + /// + /// The node last quantized + /// + [MethodImpl(InliningOptions.ShortMethod)] + public 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 + /// + public sealed 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(InliningOptions.ShortMethod)] + get; + } + + /// + /// Add a color into the tree + /// + /// The color to add. + /// The number of significant color bits. + /// The level in the tree. + /// The tree to which this node belongs. + public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree) + { + // Update the color information if this is a leaf + if (this.leaf) + { + this.Increment(ref color); + + // Setup the previous node + octree.TrackPrevious(this); + } + else + { + // Go to the next level down in the tree + int index = GetColorIndex(ref color, level); + + 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 color, 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(InliningOptions.ColdPath)] + public void ConstructPalette(Span 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(InliningOptions.ColdPath)] + public int GetPaletteIndex(ref Rgba32 pixel, int level) + { + if (this.leaf) + { + return this.paletteIndex; + } + + int colorIndex = GetColorIndex(ref pixel, level); + OctreeNode child = this.children[colorIndex]; + + int index = 0; + if (child != null) + { + index = child.GetPaletteIndex(ref pixel, level + 1); + } + else + { + // Check other children. + for (int i = 0; i < this.children.Length; i++) + { + child = this.children[i]; + if (child != null) + { + var childIndex = child.GetPaletteIndex(ref pixel, level + 1); + if (childIndex != 0) + { + return childIndex; + } + } + } + } + + return index; + } + + /// + /// Gets the color index at the given level. + /// + /// The color. + /// The node level. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetColorIndex(ref Rgba32 color, int level) + { + DebugGuard.MustBeLessThan(level, Mask.Length, nameof(level)); + + int shift = 7 - level; + ref byte maskRef = ref MemoryMarshal.GetReference(Mask); + byte mask = Unsafe.Add(ref maskRef, level); + + return ((color.R & mask) >> shift) + | ((color.G & mask) >> (shift - 1)) + | ((color.B & mask) >> (shift - 2)); + } + + /// + /// Increment the color count and add to the color information + /// + /// The pixel to add. + [MethodImpl(InliningOptions.ShortMethod)] + public void Increment(ref Rgba32 color) + { + this.pixelCount++; + this.red += color.R; + this.green += color.G; + this.blue += color.B; + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs deleted file mode 100644 index f8a19f8c40..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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 6b2be3d038..bc5eb783f7 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -1,76 +1,66 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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. - /// Override this class to provide your own palette. - /// - /// By default the quantizer uses dithering. - /// /// - public abstract class PaletteQuantizer : IQuantizer + public class PaletteQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + private readonly ReadOnlyMemory colorPalette; + /// /// Initializes a new instance of the class. /// - protected PaletteQuantizer() - : this(true) + /// The color palette. + public PaletteQuantizer(ReadOnlyMemory palette) + : this(palette, DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image - protected PaletteQuantizer(bool dither) - : this(GetDiffuser(dither)) + /// The color palette. + /// The quantizer options defining quantization rules. + public PaletteQuantizer(ReadOnlyMemory palette, QuantizerOptions options) { - } + 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; + } /// - 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; - - /// - /// 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 + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel { - int max = Math.Min(QuantizerConstants.MaxColors, Math.Min(maxColors, palette.Length)); + Guard.NotNull(options, nameof(options)); - if (max != palette.Length) - { - return new PaletteFrameQuantizer(this, palette.AsSpan(0, max).ToArray()); - } + // The palette quantizer can reuse the same pixel map across multiple frames + // since the palette is unchanging. This allows a reduction of memory usage across + // multi frame gifs using a global palette. + int length = Math.Min(this.colorPalette.Length, options.MaxColors); + var palette = new TPixel[length]; - return new PaletteFrameQuantizer(this, palette); - } + Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + var pixelMap = new EuclideanPixelMap(configuration, palette); + return new PaletteQuantizer(configuration, options, pixelMap); + } } -} \ 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 a350adfc0c..d0dbdae204 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs @@ -1,110 +1,71 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. 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 { /// - /// A generic palette quantizer. + /// Encapsulates methods to create a quantized image based upon the given palette. + /// /// /// The pixel format. - public class PaletteQuantizer : IQuantizer - where TPixel : struct, IPixel + internal struct PaletteQuantizer : IQuantizer + where TPixel : unmanaged, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// The color palette to use. - public PaletteQuantizer(TPixel[] palette) - : this(palette, true) - { - } + private readonly EuclideanPixelMap pixelMap; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// - /// The color palette to use. - /// Whether to apply dithering to the output image - public PaletteQuantizer(TPixel[] palette, bool dither) - : this(palette, GetDiffuser(dither)) + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + /// The pixel map for looking up color matches from a predefined palette. + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteQuantizer( + Configuration configuration, + QuantizerOptions options, + EuclideanPixelMap pixelMap) { - } + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); - /// - /// 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; + this.Configuration = configuration; + this.Options = options; + this.pixelMap = pixelMap; } /// - public IErrorDiffuser Diffuser { get; } + public Configuration Configuration { get; } - /// - /// Gets the palette. - /// - public TPixel[] Palette { get; } + /// + public QuantizerOptions Options { get; } - /// - /// Creates the generic frame quantizer. - /// - /// The to configure internal operations. - /// The . - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - => ((IQuantizer)this).CreateFrameQuantizer(configuration); + /// + public ReadOnlyMemory Palette => this.pixelMap.Palette; - /// - /// 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 IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// - IFrameQuantizer IQuantizer.CreateFrameQuantizer(Configuration configuration) + [MethodImpl(InliningOptions.ShortMethod)] + public void AddPaletteColors(Buffer2DRegion pixelRegion) { - 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)); } /// - 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."); - } + [MethodImpl(InliningOptions.ShortMethod)] + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + => (byte)this.pixelMap.GetClosestColor(color, out match); - 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()); - } - - return new PaletteFrameQuantizer(this, castPalette); + /// + public void Dispose() + { } - - 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 8da89bf94a..a085e1484c 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs @@ -1,59 +1,30 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// Enables the quantization of images to reduce the number of colors used in the image palette. + /// Defines quantization processing for images to reduce the number of colors used in the image palette. /// - /// The pixel format. - internal class QuantizeProcessor : ImageProcessor - where TPixel : struct, IPixel + public class QuantizeProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The quantizer used to reduce the color palette + /// The quantizer used to reduce the color palette. public QuantizeProcessor(IQuantizer quantizer) - { - Guard.NotNull(quantizer, nameof(quantizer)); - this.Quantizer = quantizer; - } + => this.Quantizer = quantizer; /// - /// Gets the quantizer + /// Gets the quantizer. /// public IQuantizer Quantizer { get; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - 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; - - for (int x = 0; x < source.Width; x++) - { - int i = x + yy; - row[x] = quantized.Palette[Math.Min(paletteCount, quantizedPixelSpan[i])]; - } - } - } - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new QuantizeProcessor(configuration, this.Quantizer, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..bb6d3d44a6 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.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) + { + var 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); + + var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); + ParallelRowIterator.IterateRowIntervals( + configuration, + interest, + in operation); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly IndexedImageFrame quantized; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + ImageFrame source, + IndexedImageFrame quantized) + { + this.bounds = bounds; + this.source = source; + this.quantized = quantized; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ReadOnlySpan paletteSpan = this.quantized.Palette.Span; + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = this.source.GetPixelRowSpan(y); + ReadOnlySpan quantizedRow = this.quantized.GetPixelRowSpan(y - offsetY); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + row[x] = paletteSpan[quantizedRow[x - offsetX]]; + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs deleted file mode 100644 index 38862ef446..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ /dev/null @@ -1,80 +0,0 @@ -// 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 d79a91c301..83178bc95a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs @@ -1,12 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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. /// - internal static class QuantizerConstants + public static class QuantizerConstants { /// /// The minimum number of colors to use when quantizing an image. @@ -17,5 +19,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The maximum number of colors to use when quantizing an image. /// public const int MaxColors = 256; + + /// + /// The minumim dithering scale used to adjust the amount of dither. + /// + public const float MinDitherScale = 0; + + /// + /// The max dithering scale used to adjust the amount of dither. + /// + public const float MaxDitherScale = 1F; + + /// + /// Gets the default dithering algorithm to use. + /// + public static IDither DefaultDither { get; } = KnownDitherings.FloydSteinberg; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs new file mode 100644 index 0000000000..d304810437 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Defines options for quantization. + /// + public class QuantizerOptions + { + private float ditherScale = QuantizerConstants.MaxDitherScale; + private int maxColors = QuantizerConstants.MaxColors; + + /// + /// 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 { return this.ditherScale; } + set { this.ditherScale = value.Clamp(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 { return this.maxColors; } + set { this.maxColors = value.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs new file mode 100644 index 0000000000..d9bc818560 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.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 + { + /// + /// 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 + /// + public static void CheckPaletteState(in ReadOnlyMemory palette) + where TPixel : unmanaged, IPixel + { + if (palette.Equals(default)) + { + 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)); + + var interest = Rectangle.Intersect(source.Bounds(), bounds); + Buffer2DRegion region = source.PixelBuffer.GetRegion(interest); + + // Collect the palette. Required before the second pass runs. + quantizer.AddPaletteColors(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)); + var interest = Rectangle.Intersect(source.Bounds(), bounds); + + var destination = new IndexedImageFrame( + 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; + } + + internal static void BuildPalette( + this IQuantizer quantizer, + IPixelSamplingStrategy pixelSamplingStrategy, + Image image) + where TPixel : unmanaged, IPixel + { + foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(image)) + { + quantizer.AddPaletteColors(region); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void SecondPass( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IQuantizer + where TPixel : unmanaged, IPixel + { + IDither dither = quantizer.Options.Dither; + + if (dither is null) + { + var operation = new RowIntervalOperation( + ref quantizer, + source, + destination, + bounds); + + ParallelRowIterator.IterateRowIntervals( + quantizer.Configuration, + bounds, + in operation); + + return; + } + + dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TFrameQuantizer : struct, IQuantizer + where TPixel : unmanaged, IPixel + { + private readonly TFrameQuantizer quantizer; + private readonly ImageFrame source; + private readonly IndexedImageFrame destination; + private readonly Rectangle bounds; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + { + this.quantizer = quantizer; + this.source = source; + this.destination = destination; + this.bounds = bounds; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index 93630a9166..5dda17dc60 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -11,37 +10,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WebSafePaletteQuantizer : PaletteQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WebSafePaletteQuantizer() - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image - public WebSafePaletteQuantizer(bool dither) - : base(dither) + public WebSafePaletteQuantizer() + : this(DefaultOptions) { } /// /// 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) + /// The quantizer options defining quantization rules. + public WebSafePaletteQuantizer(QuantizerOptions options) + : base(Color.WebSafePalette, options) { } - - /// - 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 2ff9f5090c..6675263df6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -1,9 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// @@ -12,37 +9,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WernerPaletteQuantizer : PaletteQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WernerPaletteQuantizer() - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image - public WernerPaletteQuantizer(bool dither) - : base(dither) + public WernerPaletteQuantizer() + : this(DefaultOptions) { } /// /// 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) + /// The quantizer options defining quantization rules. + public WernerPaletteQuantizer(QuantizerOptions options) + : base(Color.WernerPalette, options) { } - - /// - 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 deleted file mode 100644 index 44df226cfd..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ /dev/null @@ -1,948 +0,0 @@ -// 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; - -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 - { - // TODO: The WuFrameQuantizer code is rising several questions: - // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) JS. I'm afraid so. - // - 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! - // - It's a frequently used class, we need tests! (So we can optimize safely.) There are tests in the original!!! We should just adopt them! - // https://github.com/JeremyAnsel/JeremyAnsel.ColorQuant/blob/master/JeremyAnsel.ColorQuant/JeremyAnsel.ColorQuant.Tests/WuColorQuantizerTests.cs - - /// - /// The index bits. - /// - private const int IndexBits = 5; - - /// - /// The index alpha bits. Keep separate for now to allow easy adjustment. - /// - 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. - /// - 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; - - try - { - 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); - } - finally - { - 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.R1, cube.G1, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - } - - /// - /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (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.R0, cube.G1, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Green - case 2: - return -moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Blue - case 1: - return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Alpha - case 0: - return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - default: - throw new ArgumentOutOfRangeException(nameof(direction)); - } - } - - /// - /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (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.G1, cube.B1, cube.A1)] - - moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A0)]; - - // Green - case 2: - return moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A0)]; - - // Blue - case 1: - return moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A0)]; - - // Alpha - case 0: - return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, position)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, position)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, position)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, position)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, position)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, position)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, position)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, 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; - float 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 float 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(); - - float xx = - m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - - m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - - m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - - m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - - m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - var vector = new Vector4(dr, dg, db, da); - return xx - (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.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxg = this.Maximize(ref set1, 2, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxb = this.Maximize(ref set1, 1, set1.B0 + 1, set1.B1, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxa = this.Maximize(ref set1, 0, set1.A0 + 1, set1.A1, 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.R1 = set1.R1; - set2.G1 = set1.G1; - set2.B1 = set1.B1; - set2.A1 = set1.A1; - - switch (dir) - { - // Red - case 3: - set2.R0 = set1.R1 = cutr; - set2.G0 = set1.G0; - set2.B0 = set1.B0; - set2.A0 = set1.A0; - break; - - // Green - case 2: - set2.G0 = set1.G1 = cutg; - set2.R0 = set1.R0; - set2.B0 = set1.B0; - set2.A0 = set1.A0; - break; - - // Blue - case 1: - set2.B0 = set1.B1 = cutb; - set2.R0 = set1.R0; - set2.G0 = set1.G0; - set2.A0 = set1.A0; - break; - - // Alpha - case 0: - set2.A0 = set1.A1 = cuta; - set2.R0 = set1.R0; - set2.G0 = set1.G0; - set2.B0 = set1.B0; - break; - } - - set1.Volume = (set1.R1 - set1.R0) * (set1.G1 - set1.G0) * (set1.B1 - set1.B0) * (set1.A1 - set1.A0); - set2.Volume = (set2.R1 - set2.R0) * (set2.G1 - set2.G0) * (set2.B1 - set2.B0) * (set2.A1 - set2.A0); - - 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.R0 + 1; r <= cube.R1; r++) - { - for (int g = cube.G0 + 1; g <= cube.G1; g++) - { - for (int b = cube.B0 + 1; b <= cube.B1; b++) - { - for (int a = cube.A0 + 1; a <= cube.A1; a++) - { - tagSpan[GetPaletteIndex(r, g, b, a)] = label; - } - } - } - } - } - - /// - /// Builds the cube. - /// - private void BuildCube() - { - this.colorCube = new Box[this.colors]; - float[] vv = new float[this.colors]; - - ref Box cube = ref this.colorCube[0]; - cube.R0 = cube.G0 = cube.B0 = cube.A0 = 0; - cube.R1 = cube.G1 = cube.B1 = IndexCount - 1; - cube.A1 = 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] = 0F; - i--; - } - - next = 0; - - float temp = vv[0]; - for (int k = 1; k <= i; k++) - { - if (vv[k] > temp) - { - temp = vv[k]; - next = k; - } - } - - if (temp <= 0F) - { - 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 - { - /// - /// Gets or sets the min red value, exclusive. - /// - public int R0; - - /// - /// Gets or sets the max red value, inclusive. - /// - public int R1; - - /// - /// Gets or sets the min green value, exclusive. - /// - public int G0; - - /// - /// Gets or sets the max green value, inclusive. - /// - public int G1; - - /// - /// Gets or sets the min blue value, exclusive. - /// - public int B0; - - /// - /// Gets or sets the max blue value, inclusive. - /// - public int B1; - - /// - /// Gets or sets the min alpha value, exclusive. - /// - public int A0; - - /// - /// Gets or sets the max alpha value, inclusive. - /// - public int A1; - - /// - /// Gets or sets the volume. - /// - public int Volume; - } - } -} \ 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 eb8b0fec91..95adb7e5d6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -1,87 +1,47 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 - /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 - /// /// public class WuQuantizer : IQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WuQuantizer() - : this(true) - { - } - - /// - /// 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) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// using the default . /// - /// Whether to apply dithering to the output image - public WuQuantizer(bool dither) - : this(GetDiffuser(dither), QuantizerConstants.MaxColors) - { - } - - /// - /// 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 WuQuantizer() + : this(DefaultOptions) { } /// /// 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) + /// The quantizer options defining quantization rules. + public WuQuantizer(QuantizerOptions options) { - this.Diffuser = diffuser; - this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + Guard.NotNull(options, nameof(options)); + this.Options = options; } /// - public IErrorDiffuser Diffuser { get; } + public QuantizerOptions Options { 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); - } + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) + where TPixel : unmanaged, IPixel + => this.CreatePixelSpecificQuantizer(configuration, this.Options); - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + => new WuQuantizer(configuration, options); } -} \ 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 new file mode 100644 index 0000000000..e449678559 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -0,0 +1,856 @@ +// Copyright (c) Six Labors. +// 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; + +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 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! + + /// + /// 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; + + private IMemoryOwner momentsOwner; + private IMemoryOwner tagsOwner; + private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; + private int maxColors; + private readonly Box[] colorCube; + private EuclideanPixelMap pixelMap; + private readonly bool isDithering; + private bool isDisposed; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration which allows altering default behaviour 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.palette = default; + this.colorCube = new Box[this.maxColors]; + this.isDisposed = false; + this.pixelMap = default; + this.isDithering = this.isDithering = !(this.Options.Dither is null); + } + + /// + public Configuration Configuration { get; } + + /// + public QuantizerOptions Options { get; } + + /// + public ReadOnlyMemory Palette + { + get + { + QuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; + } + } + + /// + public void AddPaletteColors(Buffer2DRegion pixelRegion) + { + Rectangle bounds = pixelRegion.Rectangle; + Buffer2D source = pixelRegion.Buffer; + + this.Build3DHistogram(source, bounds); + this.Get3DMoments(this.memoryAllocator); + this.BuildCube(); + + ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); + Span paletteSpan = this.paletteOwner.GetSpan(); + for (int k = 0; k < this.maxColors; k++) + { + this.Mark(ref this.colorCube[k], (byte)k); + + Moment moment = Volume(ref this.colorCube[k], momentsSpan); + + if (moment.Weight > 0) + { + ref TPixel color = ref paletteSpan[k]; + color.FromScaledVector4(moment.Normalize()); + } + } + + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, this.maxColors); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + this.palette = result; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + + /// + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + { + if (this.isDithering) + { + return (byte)this.pixelMap.GetClosestColor(color, out match); + } + + Rgba32 rgba = default; + color.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); + + ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); + byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.momentsOwner?.Dispose(); + this.tagsOwner?.Dispose(); + this.paletteOwner?.Dispose(); + this.momentsOwner = null; + this.tagsOwner = null; + this.paletteOwner = 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) + { + 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 Moment Volume(ref Box cube, ReadOnlySpan moments) + { + return 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. + private static Moment Bottom(ref Box cube, int direction, ReadOnlySpan moments) + { + switch (direction) + { + // Red + case 3: + return -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 + case 2: + return -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 + case 1: + return -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 + case 0: + return -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)]; + + 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 Moment Top(ref Box cube, int direction, int position, ReadOnlySpan moments) + { + switch (direction) + { + // Red + case 3: + return 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 + case 2: + return 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 + case 1: + return 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 + case 0: + return 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)]; + + default: + throw new ArgumentOutOfRangeException(nameof(direction)); + } + } + + /// + /// Builds a 3-D color histogram of counts, r/g/b, c^2. + /// + /// The source data. + /// The bounds within the source image to quantize. + private void Build3DHistogram(Buffer2D source, Rectangle bounds) + { + Span momentSpan = this.momentsOwner.GetSpan(); + + // Build up the 3-D color histogram + using IMemoryOwner buffer = this.memoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); + + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); + + for (int x = 0; x < bufferSpan.Length; x++) + { + Rgba32 rgba = bufferSpan[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; + + momentSpan[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 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(); + int baseIndex = GetPaletteIndex(1, 0, 0, 0); + + for (int r = 1; r < IndexCount; r++) + { + volumeSpan.Clear(); + + for (int g = 1; g < IndexCount; g++) + { + areaSpan.Clear(); + + for (int b = 1; b < IndexCount; b++) + { + Moment line = default; + + for (int a = 1; a < IndexAlphaCount; a++) + { + int ind1 = GetPaletteIndex(r, g, b, a); + line += momentSpan[ind1]; + + areaSpan[a] += line; + + int inv = (b * IndexAlphaCount) + 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 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)]; + + var vector = new Vector4(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 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; + } + + var vector = new Vector4(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 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 void Mark(ref Box cube, byte label) + { + Span tagSpan = this.tagsOwner.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() + { + // Store the volume variance. + using IMemoryOwner vvOwner = this.Configuration.MemoryAllocator.Allocate(this.maxColors); + Span vv = vvOwner.GetSpan(); + + 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.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++; + + var vector = new Vector4(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 readonly override 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 readonly 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(); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs deleted file mode 100644 index 5a3b5943b7..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ /dev/null @@ -1,142 +0,0 @@ -// 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.FromVector4(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 deleted file mode 100644 index a610ae5bb3..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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 3b1d7e94dd..a2c46dd3f8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -1,32 +1,26 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; -using System.Linq; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides methods to allow the cropping of an image. + /// Defines a crop operation on an image. /// - /// The pixel format. - internal class CropProcessor : TransformProcessorBase - where TPixel : struct, IPixel + public sealed class CropProcessor : CloningImageProcessor { /// - /// Initializes a new instance of the class. + /// 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."); + Guard.IsTrue( + new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), + nameof(cropRectangle), + "Crop rectangle should be smaller than the source bounds."); + this.CropRectangle = cropRectangle; } @@ -35,44 +29,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// 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); - } - - /// - 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); - } - }); - } + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new CropProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..df9c1146b8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.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; + + /// + /// 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; + + /// + protected override Size GetDestinationSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height); + + /// + 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); + + var operation = new RowOperation(bounds, source, destination); + + ParallelRowIterator.IterateRows( + bounds, + in parallelSettings, + in operation); + } + + /// + /// A implementing the processor logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly ImageFrame 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, ImageFrame source, ImageFrame destination) + { + this.bounds = bounds; + this.source = source; + this.destination = destination; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs b/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs new file mode 100644 index 0000000000..eea095aa50 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 6de717afd9..b1110bfe2b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -1,31 +1,25 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { /// - /// Provides methods to allow the cropping of an image to preserve areas of highest entropy. + /// Defines cropping operation that preserves areas of highest entropy. /// - /// The pixel format. - internal class EntropyCropProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class EntropyCropProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public EntropyCropProcessor() - : this(.5F) + : this(.5F) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The threshold to split the image. Must be between 0 and 1. /// @@ -42,33 +36,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// 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); - - // 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(); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EntropyCropProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..dd9c069385 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// 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; + +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 (var temp = new Image(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() })) + { + Configuration configuration = this.Source.GetConfiguration(); + + // 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 = ImageMaths.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(); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs deleted file mode 100644 index c6f5e9d7b8..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 6db03d5b41..ae3b4da60c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -1,6 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -21,5 +23,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// 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; } -} \ 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 new file mode 100644 index 0000000000..2f4b19e10d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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/Linear/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs new file mode 100644 index 0000000000..77ba9582d6 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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, nameof(sampler)); + + 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 new file mode 100644 index 0000000000..cd7f46d926 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -0,0 +1,229 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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.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 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.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); + } + + /// + 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. + if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return; + } + + // Convert from screen to world space. + Matrix3x2.Invert(matrix, out matrix); + + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNAffineOperation(source, destination, matrix); + ParallelRowIterator.IterateRows( + configuration, + destination.Bounds(), + in nnOperation); + + return; + } + + int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); + var radialExtents = new Vector2(xRadius, yRadius); + int yLength = (yRadius * 2) + 1; + int xLength = (xRadius * 2) + 1; + + // We use 2D buffers so that we can access the weight spans per row in parallel. + using Buffer2D yKernelBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); + using Buffer2D xKernelBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); + + int maxX = source.Width - 1; + int maxY = source.Height - 1; + var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); + + var operation = new AffineOperation( + configuration, + source, + destination, + yKernelBuffer, + xKernelBuffer, + in sampler, + matrix, + radialExtents, + maxSourceExtents); + + ParallelRowIterator.IterateRows, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + private readonly struct NNAffineOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Rectangle bounds; + private readonly Matrix3x2 matrix; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNAffineOperation( + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + { + this.source = source; + this.destination = destination; + this.bounds = source.Bounds(); + this.matrix = matrix; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + var 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)) + { + destRow[x] = this.source[px, py]; + } + } + } + } + + private readonly struct AffineOperation : IRowOperation + where TResampler : struct, IResampler + { + private readonly Configuration configuration; + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Buffer2D yKernelBuffer; + private readonly Buffer2D xKernelBuffer; + private readonly TResampler sampler; + private readonly Matrix3x2 matrix; + private readonly Vector2 radialExtents; + private readonly Vector4 maxSourceExtents; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public AffineOperation( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Buffer2D yKernelBuffer, + Buffer2D xKernelBuffer, + in TResampler sampler, + Matrix3x2 matrix, + Vector2 radialExtents, + Vector4 maxSourceExtents) + { + this.configuration = configuration; + this.source = source; + this.destination = destination; + this.yKernelBuffer = yKernelBuffer; + this.xKernelBuffer = xKernelBuffer; + this.sampler = sampler; + this.matrix = matrix; + this.radialExtents = radialExtents; + this.maxSourceExtents = maxSourceExtents; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Buffer2D sourceBuffer = this.source.PixelBuffer; + + PixelOperations.Instance.ToVector4( + this.configuration, + this.destination.GetPixelRowSpan(y), + span); + + ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); + ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); + + for (int x = 0; x < this.maxX; 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), this.matrix); + LinearTransformUtilities.Convolve( + in this.sampler, + point, + sourceBuffer, + span, + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs new file mode 100644 index 0000000000..5539c10f62 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..3a06f5c2c3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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() + { + OrientationMode orientation = GetExifOrientation(this.Source); + Size size = this.SourceRectangle.Size; + switch (orientation) + { + case OrientationMode.TopRight: + new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.BottomRight: + new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.BottomLeft: + new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.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 OrientationMode.RightTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.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 OrientationMode.LeftBottom: + new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.Unknown: + case OrientationMode.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 OrientationMode GetExifOrientation(Image source) + { + if (source.Metadata.ExifProfile is null) + { + return OrientationMode.Unknown; + } + + IExifValue 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; + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs new file mode 100644 index 0000000000..3912617c8a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 new file mode 100644 index 0000000000..840881b145 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +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; + + /// + /// 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 FlipProcessor(Configuration configuration, FlipProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + switch (this.definition.FlipMode) + { + // No default needed as we have already set the pixels. + case FlipMode.Vertical: + this.FlipX(source, this.Configuration); + break; + case FlipMode.Horizontal: + this.FlipY(source, this.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) + { + var operation = new RowOperation(source); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } + + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation(ImageFrame source) => this.source = source; + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) => this.source.GetPixelRowSpan(y).Reverse(); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs new file mode 100644 index 0000000000..e198541474 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Utility methods for affine and projective transforms. + /// + internal static class LinearTransformUtilities + { + [MethodImpl(InliningOptions.ShortMethod)] + internal static int GetSamplingRadius(in TResampler sampler, int sourceSize, int destinationSize) + where TResampler : struct, IResampler + { + double scale = sourceSize / destinationSize; + if (scale < 1) + { + scale = 1; + } + + return (int)Math.Ceiling(scale * sampler.Radius); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal static void Convolve( + in TResampler sampler, + Vector2 transformedPoint, + Buffer2D sourcePixels, + Span targetRow, + int column, + ref float yKernelSpanRef, + ref float xKernelSpanRef, + Vector2 radialExtents, + Vector4 maxSourceExtents) + where TResampler : struct, IResampler + where TPixel : unmanaged, IPixel + { + // Clamp sampling pixel radial extents to the source image edges + Vector2 minXY = transformedPoint - radialExtents; + Vector2 maxXY = transformedPoint + radialExtents; + + // left, top, right, bottom + var sourceExtents = new Vector4( + MathF.Ceiling(minXY.X), + MathF.Ceiling(minXY.Y), + MathF.Floor(maxXY.X), + MathF.Floor(maxXY.Y)); + + sourceExtents = Vector4Utilities.FastClamp(sourceExtents, Vector4.Zero, maxSourceExtents); + + int left = (int)sourceExtents.X; + int top = (int)sourceExtents.Y; + int right = (int)sourceExtents.Z; + int bottom = (int)sourceExtents.W; + + if (left == right || top == bottom) + { + return; + } + + CalculateWeights(in sampler, top, bottom, transformedPoint.Y, ref yKernelSpanRef); + CalculateWeights(in sampler, left, right, transformedPoint.X, ref xKernelSpanRef); + + Vector4 sum = Vector4.Zero; + for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) + { + float yWeight = Unsafe.Add(ref yKernelSpanRef, kernelY); + + for (int kernelX = 0, x = left; x <= right; x++, kernelX++) + { + float xWeight = Unsafe.Add(ref xKernelSpanRef, kernelX); + + // Values are first premultiplied to prevent darkening of edge pixels. + var current = sourcePixels[x, y].ToVector4(); + Vector4Utilities.Premultiply(ref current); + sum += current * xWeight * yWeight; + } + } + + // Reverse the premultiplication + Vector4Utilities.UnPremultiply(ref sum); + targetRow[column] = sum; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void CalculateWeights(in TResampler sampler, int min, int max, float point, ref float weightsRef) + where TResampler : struct, IResampler + { + float sum = 0; + for (int x = 0, i = min; i <= max; i++, x++) + { + float weight = sampler.GetValue(i - point); + sum += weight; + Unsafe.Add(ref weightsRef, x) = weight; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs new file mode 100644 index 0000000000..338489d3f3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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, nameof(sampler)); + + 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 new file mode 100644 index 0000000000..4f75377964 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -0,0 +1,229 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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.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); + } + + /// + 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. + if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return; + } + + // Convert from screen to world space. + Matrix4x4.Invert(matrix, out matrix); + + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNProjectiveOperation(source, destination, matrix); + ParallelRowIterator.IterateRows( + configuration, + destination.Bounds(), + in nnOperation); + + return; + } + + int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); + var radialExtents = new Vector2(xRadius, yRadius); + int yLength = (yRadius * 2) + 1; + int xLength = (xRadius * 2) + 1; + + // We use 2D buffers so that we can access the weight spans per row in parallel. + using Buffer2D yKernelBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); + using Buffer2D xKernelBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); + + int maxX = source.Width - 1; + int maxY = source.Height - 1; + var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); + + var operation = new ProjectiveOperation( + configuration, + source, + destination, + yKernelBuffer, + xKernelBuffer, + in sampler, + matrix, + radialExtents, + maxSourceExtents); + + ParallelRowIterator.IterateRows, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + private readonly struct NNProjectiveOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Rectangle bounds; + private readonly Matrix4x4 matrix; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNProjectiveOperation( + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + { + this.source = source; + this.destination = destination; + this.bounds = source.Bounds(); + this.matrix = matrix; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + Vector2 point = TransformUtilities.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)) + { + destRow[x] = this.source[px, py]; + } + } + } + } + + private readonly struct ProjectiveOperation : IRowOperation + where TResampler : struct, IResampler + { + private readonly Configuration configuration; + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Buffer2D yKernelBuffer; + private readonly Buffer2D xKernelBuffer; + private readonly TResampler sampler; + private readonly Matrix4x4 matrix; + private readonly Vector2 radialExtents; + private readonly Vector4 maxSourceExtents; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ProjectiveOperation( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Buffer2D yKernelBuffer, + Buffer2D xKernelBuffer, + in TResampler sampler, + Matrix4x4 matrix, + Vector2 radialExtents, + Vector4 maxSourceExtents) + { + this.configuration = configuration; + this.source = source; + this.destination = destination; + this.yKernelBuffer = yKernelBuffer; + this.xKernelBuffer = xKernelBuffer; + this.sampler = sampler; + this.matrix = matrix; + this.radialExtents = radialExtents; + this.maxSourceExtents = maxSourceExtents; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Buffer2D sourceBuffer = this.source.PixelBuffer; + + PixelOperations.Instance.ToVector4( + this.configuration, + this.destination.GetPixelRowSpan(y), + span); + + ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); + ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); + + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + LinearTransformUtilities.Convolve( + in this.sampler, + point, + sourceBuffer, + span, + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs new file mode 100644 index 0000000000..3b46040751 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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( + TransformUtilities.CreateRotationMatrixDegrees(degrees, sourceSize), + sampler, + sourceSize) + => this.Degrees = degrees; + + // Helper constructor + private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) + : base(rotationMatrix, sampler, TransformUtilities.GetTransformedSize(sourceSize, rotationMatrix)) + { + } + + /// + /// 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 new file mode 100644 index 0000000000..cce6d68605 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -0,0 +1,286 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.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) + { + 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 180 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) + { + var operation = new Rotate180RowOperation(source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } + + /// + /// 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) + { + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRowIntervals( + configuration, + source.Bounds(), + in operation); + } + + /// + /// 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) + { + var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } + + private readonly struct Rotate180RowOperation : IRowOperation + { + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate180RowOperation( + int width, + int height, + ImageFrame source, + ImageFrame 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.GetPixelRowSpan(y); + Span targetRow = this.destination.GetPixelRowSpan(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 ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate270RowIntervalOperation( + Rectangle bounds, + int width, + int height, + ImageFrame source, + ImageFrame 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.GetPixelRowSpan(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 ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate90RowOperation( + Rectangle bounds, + int width, + int height, + ImageFrame source, + ImageFrame 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.GetPixelRowSpan(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 new file mode 100644 index 0000000000..e5791b82f8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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( + TransformUtilities.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, TransformUtilities.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; } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs deleted file mode 100644 index 0b5627e19b..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ /dev/null @@ -1,141 +0,0 @@ -// 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.FromVector4(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 199563bc7e..0b14eeda62 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Wikipedia /// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation. /// - public class BicubicResampler : IResampler + public readonly struct BicubicResampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -21,21 +25,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms x = -x; } - float result = 0; - // 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; + return (((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 (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; } - return result; + 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/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs index 0667226d9c..444d9c37e7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -1,18 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 class BoxResampler : IResampler + public readonly struct BoxResampler : IResampler { /// public float Radius => 0.5F; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x > -0.5F && x <= 0.5F) @@ -22,5 +26,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms 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 deleted file mode 100644 index 8995d2d8a8..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 new file mode 100644 index 0000000000..fa0dd1f605 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 CubicResampler CatmullRom = new CubicResampler(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 CubicResampler Hermite = new CubicResampler(2, 0, 0); + + /// + /// The function implements the Mitchell-Netravali algorithm as described on + /// Wikipedia + /// + public static CubicResampler MitchellNetravali = new CubicResampler(2, .3333333F, .3333333F); + + /// + /// The function implements the Robidoux algorithm. + /// + /// + public static CubicResampler Robidoux = new CubicResampler(2, .37821575509399867F, .31089212245300067F); + + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static CubicResampler RobidouxSharp = new CubicResampler(2, .2620145123990142F, .3689927438004929F); + + /// + /// The function implements the spline algorithm. + /// + /// + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static CubicResampler Spline = new CubicResampler(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 deleted file mode 100644 index 18c3fda7c0..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index 2294696de4..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 deleted file mode 100644 index 95fb206a96..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 deleted file mode 100644 index c99ed1e855..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 deleted file mode 100644 index 4efdb882b0..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 new file mode 100644 index 0000000000..7aefd8f6f1 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// 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 Lanczos kernel algorithm as described on + /// Wikipedia. + /// + public readonly struct LanczosResampler : IResampler + { + /// + /// Implements the Lanczos kernel algorithm with a radius of 2. + /// + public static LanczosResampler Lanczos2 = new LanczosResampler(2); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 3. + /// + public static LanczosResampler Lanczos3 = new LanczosResampler(3); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 5. + /// + public static LanczosResampler Lanczos5 = new LanczosResampler(5); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 8. + /// + public static LanczosResampler Lanczos8 = new LanczosResampler(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 ImageMaths.SinC(x) * ImageMaths.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 deleted file mode 100644 index d4ba954f20..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 1f12334f4f..d9e1541f1d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -1,21 +1,28 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 class NearestNeighborResampler : IResampler + public readonly struct NearestNeighborResampler : IResampler { /// public float Radius => 1; /// - public float GetValue(float x) - { - return x; - } + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) => x; + + /// + [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/RobidouxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs deleted file mode 100644 index 51938566c8..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index 015b7f0af3..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index df6c2a338f..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 57d1fa11dc..6aa585b8b6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// 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 + public readonly struct TriangleResampler : IResampler { /// public float Radius => 1; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -28,5 +32,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [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/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index edce5fcf9e..93c50af132 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -1,18 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 class WelchResampler : IResampler + public readonly struct WelchResampler : IResampler { /// public float Radius => 3; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -27,5 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [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/Resize/ResizeHelper.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs new file mode 100644 index 0000000000..5ff82a096f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs @@ -0,0 +1,429 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods to help calculate the target rectangle when resizing using the + /// enumeration. + /// + internal static class ResizeHelper + { + 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. + /// + /// The source image size. + /// The resize options. + /// + /// The tuple representing the location and the bounds + /// + public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options) + { + 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) + { + height = (int)MathF.Max(Min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); + } + + switch (options.Mode) + { + 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, 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) + { + case AnchorPositionMode.Left: + targetY = (height - sourceHeight) / 2; + targetX = 0; + break; + case AnchorPositionMode.Right: + targetY = (height - sourceHeight) / 2; + targetX = width - sourceWidth; + break; + case AnchorPositionMode.TopRight: + targetY = 0; + targetX = width - sourceWidth; + break; + case AnchorPositionMode.Top: + targetY = 0; + targetX = (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 = (width - sourceWidth) / 2; + break; + case AnchorPositionMode.BottomLeft: + targetY = height - sourceHeight; + targetX = 0; + break; + default: + targetY = (height - sourceHeight) / 2; + targetX = (width - sourceWidth) / 2; + break; + } + + // 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, Rectangle) CalculateCropRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + float ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int targetX = 0; + int targetY = 0; + int targetWidth = width; + int targetHeight = height; + + // 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; + + if (options.CenterCoordinates.HasValue) + { + float center = -(ratio * sourceHeight) * options.CenterCoordinates.Value.Y; + targetY = (int)MathF.Round(center + (height / 2F)); + + if (targetY > 0) + { + targetY = 0; + } + + if (targetY < (int)MathF.Round(height - (sourceHeight * ratio))) + { + targetY = (int)MathF.Round(height - (sourceHeight * ratio)); + } + } + else + { + switch (options.Position) + { + 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; + } + } + + targetHeight = (int)MathF.Ceiling(sourceHeight * percentWidth); + } + else + { + ratio = percentHeight; + + if (options.CenterCoordinates.HasValue) + { + float center = -(ratio * sourceWidth) * options.CenterCoordinates.Value.X; + targetX = (int)MathF.Round(center + (width / 2F)); + + if (targetX > 0) + { + targetX = 0; + } + + if (targetX < (int)MathF.Round(width - (sourceWidth * ratio))) + { + targetX = (int)MathF.Round(width - (sourceWidth * ratio)); + } + } + else + { + switch (options.Position) + { + 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; + } + } + + targetWidth = (int)MathF.Ceiling(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))); + } + + private static (Size, 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) + { + 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))); + } + + private static (Size, Rectangle) CalculateMinRectangle( + Size source, + int width, + int height) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int targetWidth = width; + int targetHeight = height; + + // Don't upscale + if (width > sourceWidth || height > sourceHeight) + { + return (new Size(sourceWidth, sourceHeight), new Rectangle(0, 0, sourceWidth, sourceHeight)); + } + + // 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) + { + float percentWidth = MathF.Abs(width / (float)sourceWidth); + targetHeight = (int)MathF.Round(sourceHeight * percentWidth); + } + else + { + float percentHeight = MathF.Abs(height / (float)sourceHeight); + targetWidth = (int)MathF.Round(sourceWidth * percentHeight); + } + } + + // 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, Rectangle) CalculatePadRectangle( + Size sourceSize, + ResizeOptions options, + int width, + int height) + { + float ratio; + int sourceWidth = sourceSize.Width; + int sourceHeight = sourceSize.Height; + + int targetX = 0; + int targetY = 0; + int targetWidth = width; + int targetHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentHeight; + targetWidth = (int)MathF.Round(sourceWidth * percentHeight); + + switch (options.Position) + { + 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; + } + } + else + { + ratio = percentWidth; + targetHeight = (int)MathF.Round(sourceHeight * percentWidth); + + switch (options.Position) + { + 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; + } + } + + // 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, Rectangle) CalculateManualRectangle( + ResizeOptions options, + int width, + int height) + { + if (!options.TargetRectangle.HasValue) + { + ThrowInvalid("Manual resizing requires a target location and size."); + } + + Rectangle targetRectangle = options.TargetRectangle.Value; + + int targetX = targetRectangle.X; + int targetY = targetRectangle.Y; + int targetWidth = targetRectangle.Width > 0 ? targetRectangle.Width : width; + int targetHeight = targetRectangle.Height > 0 ? targetRectangle.Height : 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))); + } + + private static void ThrowInvalid(string message) => throw new InvalidOperationException(message); + + private static int Sanitize(int input) => Math.Max(1, input); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index f349634ac0..35d1931d0e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { @@ -19,27 +18,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Initializes a new instance of the struct. /// [MethodImpl(InliningOptions.ShortMethod)] - internal ResizeKernel(int left, float* bufferPtr, int length) + internal ResizeKernel(int startIndex, float* bufferPtr, int length) { - this.Left = left; + this.StartIndex = startIndex; this.bufferPtr = bufferPtr; this.Length = length; } /// - /// Gets the left index for the destination row + /// Gets the start index for the destination row. /// - public int Left { get; } + public int StartIndex + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// - /// Gets the the length of the kernel + /// Gets the the length of the kernel. /// - public int Length { get; } + public int Length + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// - /// Gets the span representing the portion of the that this window covers + /// Gets the span representing the portion of the that this window covers. /// - /// The + /// The . /// public Span Values { @@ -54,10 +61,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The weighted sum [MethodImpl(InliningOptions.ShortMethod)] public Vector4 Convolve(Span rowSpan) + { + return this.ConvolveCore(ref rowSpan[this.StartIndex]); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ConvolveCore(ref Vector4 rowStartRef) { ref float horizontalValues = ref Unsafe.AsRef(this.bufferPtr); - int left = this.Left; - ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left); // Destination color components Vector4 result = Vector4.Zero; @@ -65,7 +76,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int i = 0; i < this.Length; i++) { float weight = Unsafe.Add(ref horizontalValues, i); - Vector4 v = Unsafe.Add(ref vecPtr, i); + + // Vector4 v = offsetedRowSpan[i]; + Vector4 v = Unsafe.Add(ref rowStartRef, i); result += v * weight; } @@ -73,9 +86,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// - /// Copy the contents of altering + /// Copy the contents of altering /// to the value . /// + [MethodImpl(InliningOptions.ShortMethod)] internal ResizeKernel AlterLeftValue(int left) { return new ResizeKernel(left, this.bufferPtr, this.Length); @@ -91,4 +105,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } } -} \ 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 4b81aaa64e..ef556e1a09 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -1,15 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; - -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - /// Contains - /// internal partial class ResizeKernelMap { /// @@ -23,7 +18,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public PeriodicKernelMap( MemoryAllocator memoryAllocator, - IResampler sampler, int sourceLength, int destinationLength, double ratio, @@ -33,7 +27,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int cornerInterval) : base( memoryAllocator, - sampler, sourceLength, destinationLength, (cornerInterval * 2) + period, @@ -47,15 +40,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - protected override void Initialize() + protected internal override void Initialize(in TResampler sampler) { // 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; + this.kernels[i] = this.BuildKernel(in sampler, i, i); } // Copy the mosaics: @@ -72,10 +64,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms 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; + this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i); } } } } -} \ 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 2ab574df2a..7cbda76a54 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -1,26 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides values from an optimized, - /// contiguous memory region. + /// Provides resize kernel values from an optimized contiguous memory region. /// internal partial class ResizeKernelMap : IDisposable { private static readonly TolerantMath TolerantMath = TolerantMath.Default; - private readonly IResampler sampler; - private readonly int sourceLength; private readonly double ratio; @@ -35,12 +31,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ResizeKernel[] kernels; + private bool isDisposed; + // To avoid both GC allocations, and MemoryAllocator ceremony: private readonly double[] tempValues; private ResizeKernelMap( MemoryAllocator memoryAllocator, - IResampler sampler, int sourceLength, int destinationLength, int bufferHeight, @@ -48,17 +45,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms double scale, int radius) { - this.sampler = sampler; this.ratio = ratio; this.scale = scale; this.radius = radius; this.sourceLength = sourceLength; this.DestinationLength = destinationLength; - int maxWidth = (radius * 2) + 1; - this.data = memoryAllocator.Allocate2D(maxWidth, bufferHeight, AllocationOptions.Clean); - this.pinHandle = this.data.Memory.Pin(); + this.MaxDiameter = (radius * 2) + 1; + this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); + this.pinHandle = this.data.GetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; - this.tempValues = new double[maxWidth]; + this.tempValues = new double[this.MaxDiameter]; } /// @@ -66,6 +62,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public int DestinationLength { get; } + /// + /// Gets the maximum diameter of the kernels. + /// + public int MaxDiameter { get; } + /// /// Gets a string of information to help debugging /// @@ -76,30 +77,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Disposes instance releasing it's backing buffer. /// public void Dispose() + => this.Dispose(true); + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected virtual void Dispose(bool disposing) { - this.pinHandle.Dispose(); - this.data.Dispose(); + if (!this.isDisposed) + { + this.isDisposed = true; + + if (disposing) + { + this.pinHandle.Dispose(); + this.data.Dispose(); + } + } } /// /// Returns a for an index value between 0 and DestinationSize - 1. /// [MethodImpl(InliningOptions.ShortMethod)] - public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; + internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; /// /// 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( - IResampler sampler, + 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; @@ -140,7 +158,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ResizeKernelMap result = hasAtLeast2Periods ? new PeriodicKernelMap( memoryAllocator, - sampler, sourceSize, destinationSize, ratio, @@ -150,7 +167,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms cornerInterval) : new ResizeKernelMap( memoryAllocator, - sampler, sourceSize, destinationSize, destinationSize, @@ -158,17 +174,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms scale, radius); - result.Initialize(); + result.Initialize(in sampler); return result; } - protected virtual void Initialize() + /// + /// Initializes the kernel map. + /// + protected internal virtual void Initialize(in TResampler sampler) + where TResampler : struct, IResampler { for (int i = 0; i < this.DestinationLength; i++) { - ResizeKernel kernel = this.BuildKernel(i, i); - this.kernels[i] = kernel; + this.kernels[i] = this.BuildKernel(in sampler, i, i); } } @@ -177,7 +196,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// referencing the data at row within , /// so the data reusable by other data rows. /// - private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex) + private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex) + where TResampler : struct, IResampler { double center = ((destRowIndex + .5) * this.ratio) - .5; @@ -201,7 +221,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int j = left; j <= right; j++) { - double value = this.sampler.GetValue((float)((j - center) / this.scale)); + double value = sampler.GetValue((float)((j - center) / this.scale)); sum += value; kernelValues[j - left] = value; @@ -230,12 +250,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private unsafe ResizeKernel CreateKernel(int dataRowIndex, int left, int right) { int length = right - left + 1; - - if (length > this.data.Width) - { - throw new InvalidOperationException( - $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); - } + this.ValidateSizesForCreateKernel(length, dataRowIndex, left, right); Span rowSpan = this.data.GetRowSpan(dataRowIndex); @@ -243,5 +258,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); return new ResizeKernel(left, rowPtr, length); } + + [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"); + } + } } -} \ 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 189e21de7a..576f97a938 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -1,329 +1,60 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ColorSpaces.Companding; -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 resizing of images using various algorithms. - /// Adapted from + /// Defines an image resizing operation with the given and dimensional parameters. /// - /// The pixel format. - internal class ResizeProcessor : TransformProcessorBase - where TPixel : struct, IPixel + public class ResizeProcessor : CloningImageProcessor { - // The following fields are not immutable but are optionally created on demand. - private ResizeKernelMap horizontalKernelMap; - private ResizeKernelMap verticalKernelMap; - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The resize options - /// The source image size + /// 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)); + Guard.MustBeValueType(options.Sampler, nameof(options.Sampler)); - int targetWidth = options.Size.Width; - int targetHeight = options.Size.Height; - - // 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)); - } - - if (targetHeight == 0 && targetWidth > 0) - { - targetHeight = (int)MathF.Max(min, MathF.Round(sourceSize.Height * targetWidth / (float)sourceSize.Width)); - } - - Guard.MustBeGreaterThan(targetWidth, 0, nameof(targetWidth)); - Guard.MustBeGreaterThan(targetHeight, 0, nameof(targetHeight)); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, targetWidth, targetHeight); + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options); this.Sampler = options.Sampler; - this.Width = size.Width; - this.Height = size.Height; - this.ResizeRectangle = rectangle; + this.DestinationWidth = size.Width; + this.DestinationHeight = size.Height; + this.DestinationRectangle = 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 resizeRectangle, 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)); - resizeRectangle.Width = width; - } - - if (height == 0 && width > 0) - { - height = (int)MathF.Max(min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); - resizeRectangle.Height = height; - } - - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Sampler = sampler; - this.Width = width; - this.Height = height; - this.ResizeRectangle = resizeRectangle; - this.Compand = compand; - } - /// /// Gets the sampler to perform the resize operation. /// public IResampler Sampler { get; } /// - /// Gets the target width. + /// Gets the destination width. /// - public int Width { get; } + public int DestinationWidth { get; } /// - /// Gets the target height. + /// Gets the destination height. /// - public int Height { get; } + public int DestinationHeight { get; } /// /// Gets the resize rectangle. /// - public Rectangle ResizeRectangle { get; } + public Rectangle DestinationRectangle { 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.ResizeRectangle.Width, - sourceRectangle.Width, - memoryAllocator); - - this.verticalKernelMap = ResizeKernelMap.Calculate( - this.Sampler, - this.ResizeRectangle.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.ResizeRectangle) - { - // 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.ResizeRectangle.Y; - int endY = this.ResizeRectangle.Bottom; - int startX = this.ResizeRectangle.X; - int endX = this.ResizeRectangle.Right; - - int minX = Math.Max(0, startX); - int maxX = Math.Min(width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(height, endY); - - if (this.Sampler is NearestNeighborResampler) - { - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - - ParallelHelper.IterateRows( - workingRect, - 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 = minX; x < maxX; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; - } - } - }); - - return; - } - - int sourceHeight = source.Height; - - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - using (Buffer2D firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D(sourceHeight, width)) - { - firstPassPixelsTransposed.MemorySource.Clear(); - - var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom); - - ParallelHelper.IterateRowsWithTempBuffer( - processColsRect, - configuration, - (rows, tempRowBuffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y).Slice(sourceX); - Span tempRowSpan = tempRowBuffer.Span.Slice(sourceX); - - PixelOperations.Instance.ToVector4(configuration, sourceRow, tempRowSpan); - Vector4Utils.Premultiply(tempRowSpan); - - ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; - - if (this.Compand) - { - SRgbCompanding.Expand(tempRowSpan); - } - - for (int x = minX; x < maxX; x++) - { - ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX); - Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = - kernel.Convolve(tempRowSpan); - } - } - }); - - var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY); - - // Now process the rows. - ParallelHelper.IterateRowsWithTempBuffer( - processRowsRect, - configuration, - (rows, tempRowBuffer) => - { - Span tempRowSpan = tempRowBuffer.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - // Ensure offsets are normalized for cropping and padding. - ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - startY); - - ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); - - for (int x = 0; x < width; x++) - { - Span firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY); - - // Destination color components - Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn); - } - - Vector4Utils.UnPremultiply(tempRowSpan); - - if (this.Compand) - { - SRgbCompanding.Compress(tempRowSpan); - } - - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.Instance.FromVector4(configuration, tempRowSpan, targetRowSpan); - } - }); - } - } - - 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; - } + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new ResizeProcessor(configuration, this, source, sourceRectangle); } -} \ 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 new file mode 100644 index 0000000000..9908d4f799 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -0,0 +1,244 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.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 int destinationWidth; + private readonly int destinationHeight; + private readonly IResampler resampler; + private readonly Rectangle destinationRectangle; + private readonly bool compand; + private Image destination; + + 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.resampler = definition.Sampler; + this.compand = definition.Compand; + } + + /// + protected override Size GetDestinationSize() => new Size(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. + } + + 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.compand; + + // 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; + } + + var 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]; + + 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 var horizontalKernelMap = ResizeKernelMap.Calculate( + in sampler, + destinationRectangle.Width, + sourceRectangle.Width, + allocator); + + using var 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]; + + ApplyResizeFrameTransform( + configuration, + sourceFrame, + destinationFrame, + horizontalKernelMap, + verticalKernelMap, + sourceRectangle, + destinationRectangle, + interest, + compand); + } + } + + 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; + + var operation = new NNRowOperation( + sourceRectangle, + destinationRectangle, + widthFactor, + heightFactor, + source, + destination); + + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private static void ApplyResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest, + bool compand) + { + PixelConversionModifiers conversionModifiers = + PixelConversionModifiers.Premultiply.ApplyCompanding(compand); + + Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); + + // To reintroduce parallel processing, we would launch multiple workers + // for different row intervals of the image. + using (var worker = new ResizeWorker( + configuration, + sourceRegion, + conversionModifiers, + horizontalKernelMap, + verticalKernelMap, + destination.Width, + interest, + destinationRectangle.Location)) + { + worker.Initialize(); + + var workingInterval = new RowInterval(interest.Top, interest.Bottom); + worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); + } + } + + private readonly struct NNRowOperation : IRowOperation + { + private readonly Rectangle sourceBounds; + private readonly Rectangle destinationBounds; + private readonly float widthFactor; + private readonly float heightFactor; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNRowOperation( + Rectangle sourceBounds, + Rectangle destinationBounds, + float widthFactor, + float heightFactor, + ImageFrame source, + ImageFrame destination) + { + this.sourceBounds = sourceBounds; + this.destinationBounds = destinationBounds; + 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 destX = this.destinationBounds.X; + int destY = this.destinationBounds.Y; + int destLeft = this.destinationBounds.Left; + int destRight = this.destinationBounds.Right; + + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs new file mode 100644 index 0000000000..764349884d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -0,0 +1,201 @@ +// Copyright (c) Six Labors. +// 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; + +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 + { + private readonly Buffer2D transposedFirstPassBuffer; + + private readonly Configuration configuration; + + private readonly PixelConversionModifiers conversionModifiers; + + private readonly ResizeKernelMap horizontalKernelMap; + + private readonly Buffer2DRegion source; + + private readonly Rectangle sourceRectangle; + + private readonly IMemoryOwner tempRowBuffer; + + private readonly IMemoryOwner tempColumnBuffer; + + private readonly ResizeKernelMap verticalKernelMap; + + private readonly int destWidth; + + private readonly Rectangle targetWorkingRect; + + private readonly Point targetOrigin; + + private readonly int windowBandHeight; + + private readonly int workerHeight; + + private RowInterval currentWindow; + + public ResizeWorker( + Configuration configuration, + Buffer2DRegion 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; + + // 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, + destWidth, + workingBufferLimitHintInBytes); + + 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 Dispose() + { + this.transposedFirstPassBuffer.Dispose(); + this.tempRowBuffer.Dispose(); + this.tempColumnBuffer.Dispose(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetColumnSpan(int x, int startY) + { + return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); + } + + public void Initialize() + { + this.CalculateFirstPassValues(this.currentWindow); + } + + public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) + { + Span tempColSpan = this.tempColumnBuffer.GetSpan(); + + // When creating transposedFirstPassBuffer, we made sure it's contiguous: + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); + + for (int y = rowInterval.Min; y < rowInterval.Max; y++) + { + // Ensure offsets are normalized for cropping and padding. + ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - this.targetOrigin.Y); + + while (kernel.StartIndex + kernel.Length > this.currentWindow.Max) + { + this.Slide(); + } + + ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); + + int top = kernel.StartIndex - this.currentWindow.Min; + ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; + + for (int x = 0; x < this.destWidth; x++) + { + ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight); + + // Destination color components + Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); + } + + Span targetRowSpan = destination.GetRowSpan(y); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); + } + } + + private void Slide() + { + int minY = this.currentWindow.Max - this.windowBandHeight; + int maxY = Math.Min(minY + this.workerHeight, this.sourceRectangle.Height); + + // 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); + + this.currentWindow = new RowInterval(minY, maxY); + + // Calculate the remainder: + this.CalculateFirstPassValues(this.currentWindow.Slice(this.windowBandHeight)); + } + + private void CalculateFirstPassValues(RowInterval calculationInterval) + { + Span tempRowSpan = this.tempRowBuffer.GetSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); + + for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) + { + Span sourceRow = this.source.GetRowSpan(y); + + 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]; + + 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, x * this.workerHeight) = kernel.Convolve(tempRowSpan); + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.pptx b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.pptx new file mode 100644 index 0000000000..248959170b Binary files /dev/null and b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.pptx differ diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs deleted file mode 100644 index 69ecf59215..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ /dev/null @@ -1,242 +0,0 @@ -// 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 deleted file mode 100644 index c7b1d74104..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs deleted file mode 100644 index 573120888f..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs +++ /dev/null @@ -1,161 +0,0 @@ -// 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/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs new file mode 100644 index 0000000000..a843293a98 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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) + { + } + + /// + protected override void AfterImageApply(Image destination) + { + TransformProcessorHelpers.UpdateDimensionalMetadata(destination); + base.AfterImageApply(destination); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs deleted file mode 100644 index 4973b90f46..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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 index f5536d0467..4fed7cd207 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -16,43 +16,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. /// The image to update - public static void UpdateDimensionalMetData(Image image) - where TPixel : struct, IPixel + public static void UpdateDimensionalMetadata(Image image) + where TPixel : unmanaged, IPixel { - ExifProfile profile = image.MetaData.ExifProfile; + 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. + // Only set the value if it already exists. 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); - } + profile.SetValue(ExifTag.PixelXDimension, 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); - } + profile.SetValue(ExifTag.PixelYDimension, image.Height); } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs new file mode 100644 index 0000000000..2b4c2ff149 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs @@ -0,0 +1,418 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Contains utility methods for working with transforms. + /// + internal static class TransformUtilities + { + /// + /// 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(InliningOptions.ShortMethod)] + 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(InliningOptions.ShortMethod)] + public static bool IsNaN(Matrix3x2 matrix) + { + return 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(InliningOptions.ShortMethod)] + public static bool IsNaN(Matrix4x4 matrix) + { + return 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(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); + } + + /// + /// 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 . + [MethodImpl(InliningOptions.ShortMethod)] + 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 . + [MethodImpl(InliningOptions.ShortMethod)] + 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 . + [MethodImpl(InliningOptions.ShortMethod)] + 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 . + [MethodImpl(InliningOptions.ShortMethod)] + 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 + [MethodImpl(InliningOptions.ShortMethod)] + public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) + { + Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); + + // 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); + + var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); + + // Translate back to world space. + Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); + + return 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(InliningOptions.ShortMethod)] + 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 matrix; + } + + /// + /// Returns the rectangle bounds relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) + { + Rectangle transformed = GetTransformedRectangle(rectangle, matrix); + return new Rectangle(0, 0, transformed.Width, transformed.Height); + } + + /// + /// 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) + { + if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) + { + return rectangle; + } + + 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); + + return GetBoundingRectangle(tl, tr, bl, br); + } + + /// + /// 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) + { + Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); + + if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + { + return size; + } + + Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); + + return ConstrainSize(rectangle); + } + + /// + /// Returns the rectangle relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) + { + if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) + { + return rectangle; + } + + 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); + + return GetBoundingRectangle(tl, tr, bl, br); + } + + /// + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The source size. + /// The transformation matrix. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Size GetTransformedSize(Size size, Matrix4x4 matrix) + { + Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); + + if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + { + return size; + } + + Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); + + return ConstrainSize(rectangle); + } + + [MethodImpl(InliningOptions.ShortMethod)] + 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); + + // 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; + } + + if (width <= 0) + { + width = rectangle.Width; + } + + return new Size(width, height); + } + + [MethodImpl(InliningOptions.ShortMethod)] + 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))); + + return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs deleted file mode 100644 index 0ec8c83939..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ /dev/null @@ -1,357 +0,0 @@ -// 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.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Contains utility methods for working with transforms. - /// - internal static class TransformUtils - { - /// - /// 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); - } - - /// - /// 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); - - // 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); - - var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); - var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); - - // Translate back to world space. - Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); - - return 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 - public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction) - { - 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) - { - 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 matrix; - } - - /// - /// 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) - { - Rectangle transformed = GetTransformedRectangle(rectangle, matrix); - return new Rectangle(0, 0, transformed.Width, transformed.Height); - } - - /// - /// 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) - { - if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) - { - return rectangle; - } - - 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); - - return GetBoundingRectangle(tl, tr, bl, br); - } - - /// - /// 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) - { - Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - - if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) - { - return size; - } - - Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); - - return ConstrainSize(rectangle); - } - - /// - /// 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; - } - - 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); - - return GetBoundingRectangle(tl, tr, bl, br); - } - - /// - /// 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!"); - - if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) - { - return size; - } - - Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); - - return ConstrainSize(rectangle); - } - - 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); - - // 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; - } - - if (width <= 0) - { - width = rectangle.Width; - } - - return new Size(width, height); - } - - 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))); - - return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); - } - } -} diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index c29941d071..d81ce2890b 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount to taper. /// The . public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Prepend(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Appends a matrix that performs a tapering projective transform. @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount to taper. /// The . public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Append(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Prepends a centered rotation matrix using the given rotation in degrees. @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size))); /// /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size))); /// /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size))); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -206,7 +206,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size))); /// /// Appends a skew matrix using the given angles in degrees at the given origin. @@ -264,27 +264,51 @@ namespace SixLabors.ImageSharp.Processing /// 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) => this.Prepend(_ => matrix); + 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) => this.Append(_ => matrix); + public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) + { + CheckDegenerate(matrix); + return 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)); + 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 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) { @@ -301,9 +325,19 @@ namespace SixLabors.ImageSharp.Processing matrix *= factory(size); } + CheckDegenerate(matrix); + return matrix; } + private static void CheckDegenerate(Matrix4x4 matrix) + { + if (TransformUtilities.IsDegenerate(matrix)) + { + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); + } + } + private ProjectiveTransformBuilder Prepend(Func factory) { this.matrixFactories.Insert(0, factory); @@ -316,4 +350,4 @@ namespace SixLabors.ImageSharp.Processing return this; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/QuantizeExtensions.cs b/src/ImageSharp/Processing/QuantizeExtensions.cs deleted file mode 100644 index 5bd2f49bd4..0000000000 --- a/src/ImageSharp/Processing/QuantizeExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100644 index 7b6c14d7de..0000000000 --- a/src/ImageSharp/Processing/ResizeExtensions.cs +++ /dev/null @@ -1,175 +0,0 @@ -// 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/ResizeHelper.cs b/src/ImageSharp/Processing/ResizeHelper.cs deleted file mode 100644 index 3ae632162f..0000000000 --- a/src/ImageSharp/Processing/ResizeHelper.cs +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Linq; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Provides methods to help calculate the target rectangle when resizing using the - /// enumeration. - /// - internal static class ResizeHelper - { - /// - /// 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 . - /// - public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options, int width, int height) - { - switch (options.Mode) - { - 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: - return (new Size(width, height), new Rectangle(0, 0, width, height)); - } - } - - private static (Size, Rectangle) CalculateCropRectangle(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)); - } - - 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 (percentHeight < percentWidth) - { - ratio = percentWidth; - - if (options.CenterCoordinates.Any()) - { - 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)); - } - } - else - { - 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 - { - ratio = percentHeight; - - if (options.CenterCoordinates.Any()) - { - 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); - } - - return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); - } - - private static (Size, Rectangle) CalculatePadRectangle(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)); - } - - 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 (percentHeight < percentWidth) - { - ratio = percentHeight; - destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); - - 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); - - 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; - } - } - - return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); - } - - 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 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 destinationX; - int destinationY; - int destinationWidth = sourceWidth; - int destinationHeight = sourceHeight; - width = boxPadWidth; - height = boxPadHeight; - - 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: - 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: - destinationY = height - sourceHeight; - destinationX = 0; - break; - default: - destinationY = (height - sourceHeight) / 2; - destinationX = (width - sourceWidth) / 2; - break; - } - - return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); - } - - // Switch to pad mode to downscale and calculate from there. - return CalculatePadRectangle(source, options, width, height); - } - - private static (Size, Rectangle) CalculateMaxRectangle(Size source, ResizeOptions options, int width, int height) - { - int destinationWidth = width; - int destinationHeight = 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 = options.Size.Height / (float)options.Size.Width; - float sourceRatio = source.Height / (float)source.Width; - - if (sourceRatio < ratio) - { - destinationHeight = (int)MathF.Round(source.Height * percentWidth); - height = destinationHeight; - } - else - { - destinationWidth = (int)MathF.Round(source.Width * percentHeight); - width = destinationWidth; - } - - // Replace the size to match the rectangle. - return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); - } - - 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; - - // Don't upscale - if (width > sourceWidth || height > sourceHeight) - { - return (new Size(sourceWidth, sourceWidth), new Rectangle(0, 0, sourceWidth, sourceHeight)); - } - - // Find the shortest distance to go. - int widthDiff = sourceWidth - width; - int heightDiff = sourceHeight - height; - - if (widthDiff < heightDiff) - { - 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)); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ResizeMode.cs b/src/ImageSharp/Processing/ResizeMode.cs index 6adeac66da..acfa22a7a9 100644 --- a/src/ImageSharp/Processing/ResizeMode.cs +++ b/src/ImageSharp/Processing/ResizeMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing @@ -42,6 +42,11 @@ namespace SixLabors.ImageSharp.Processing /// /// Stretches the resized image to fit the bounds of its container. /// - Stretch + Stretch, + + /// + /// The target location and size of the resized image has been manually set. + /// + Manual } } diff --git a/src/ImageSharp/Processing/ResizeOptions.cs b/src/ImageSharp/Processing/ResizeOptions.cs index 0d5bfe38bc..bad8bd4554 100644 --- a/src/ImageSharp/Processing/ResizeOptions.cs +++ b/src/ImageSharp/Processing/ResizeOptions.cs @@ -1,10 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { @@ -26,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets or sets the center coordinates. /// - public IEnumerable CenterCoordinates { get; set; } = Enumerable.Empty(); + public PointF? CenterCoordinates { get; set; } /// /// Gets or sets the target size. @@ -43,5 +40,10 @@ namespace SixLabors.ImageSharp.Processing /// or expand individual pixel colors the value on processing. /// public bool Compand { get; set; } = false; + + /// + /// Gets or sets the target rectangle to resize into. + /// + public Rectangle? TargetRectangle { get; set; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/RotateExtensions.cs b/src/ImageSharp/Processing/RotateExtensions.cs deleted file mode 100644 index 398a634d10..0000000000 --- a/src/ImageSharp/Processing/RotateExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 deleted file mode 100644 index 27ddc8de96..0000000000 --- a/src/ImageSharp/Processing/RotateFlipExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 c890f2bd67..9a738d9908 100644 --- a/src/ImageSharp/Processing/RotateMode.cs +++ b/src/ImageSharp/Processing/RotateMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/SaturateExtensions.cs b/src/ImageSharp/Processing/SaturateExtensions.cs deleted file mode 100644 index ba45ae12c9..0000000000 --- a/src/ImageSharp/Processing/SaturateExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 deleted file mode 100644 index 08676ee62a..0000000000 --- a/src/ImageSharp/Processing/SepiaExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 deleted file mode 100644 index 07e3c6087d..0000000000 --- a/src/ImageSharp/Processing/SkewExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 395b171424..b44fcadbef 100644 --- a/src/ImageSharp/Processing/TaperCorner.cs +++ b/src/ImageSharp/Processing/TaperCorner.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/TaperSide.cs b/src/ImageSharp/Processing/TaperSide.cs index 226d11aed2..209f5bf6d2 100644 --- a/src/ImageSharp/Processing/TaperSide.cs +++ b/src/ImageSharp/Processing/TaperSide.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing diff --git a/src/ImageSharp/Processing/TransformExtensions.cs b/src/ImageSharp/Processing/TransformExtensions.cs deleted file mode 100644 index db14b6baf9..0000000000 --- a/src/ImageSharp/Processing/TransformExtensions.cs +++ /dev/null @@ -1,161 +0,0 @@ -// 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/VignetteExtensions.cs b/src/ImageSharp/Processing/VignetteExtensions.cs deleted file mode 100644 index 18dd8064c6..0000000000 --- a/src/ImageSharp/Processing/VignetteExtensions.cs +++ /dev/null @@ -1,153 +0,0 @@ -// 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 7b8f933b0c..74c666c2f4 100644 --- a/src/ImageSharp/Properties/AssemblyInfo.cs +++ b/src/ImageSharp/Properties/AssemblyInfo.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; -// Ensure the other projects can see the internal helpers -[assembly: InternalsVisibleTo("SixLabors.ImageSharp.Drawing")] \ No newline at end of file +// Redundant suppressing of SA1413 for Rider. +[assembly: + System.Diagnostics.CodeAnalysis.SuppressMessage( + "StyleCop.CSharp.MaintainabilityRules", + "SA1413:UseTrailingCommasInMultiLineInitializers", + Justification = "Follows SixLabors.ruleset")] diff --git a/src/ImageSharp/ReadOrigin.cs b/src/ImageSharp/ReadOrigin.cs index f17bc82f18..e62d2fa2b6 100644 --- a/src/ImageSharp/ReadOrigin.cs +++ b/src/ImageSharp/ReadOrigin.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp diff --git a/src/Shared/AssemblyInfo.Common.cs b/src/Shared/AssemblyInfo.Common.cs deleted file mode 100644 index 82e1dac6c4..0000000000 --- a/src/Shared/AssemblyInfo.Common.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 deleted file mode 160000 index dd83f64963..0000000000 --- a/standards +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dd83f649638c6333984a757c01be6ec294e6b63c diff --git a/stylecop.json b/stylecop.json deleted file mode 100644 index 485ab604a5..0000000000 --- a/stylecop.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$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 deleted file mode 100644 index 01e342b3d2..0000000000 --- a/tests/CodeCoverage/CodeCoverage.cmd +++ /dev/null @@ -1,21 +0,0 @@ -@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 deleted file mode 100644 index 973b7f81b4..0000000000 --- a/tests/CodeCoverage/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000000..23a69362b0 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,41 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.props + tests + false + + + + $(MSBuildThisFileDirectory)..\shared-infrastructure\SixLabors.Tests.ruleset + + $(NoWarn);CS0618 + + + + + + + + + + + + + + + + + + + diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets new file mode 100644 index 0000000000..e9e93a855f --- /dev/null +++ b/tests/Directory.Build.targets @@ -0,0 +1,43 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.targets + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs index 6db03a4486..5573b1382b 100644 --- a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs @@ -1,7 +1,8 @@ -namespace SixLabors.ImageSharp.Benchmarks -{ - using SixLabors.ImageSharp.Formats; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. +namespace SixLabors.ImageSharp.Benchmarks +{ /// /// The image benchmark base class. /// @@ -15,4 +16,4 @@ // Add Image Formats } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs index 30799aabf9..58a97b65eb 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs @@ -1,13 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { @@ -31,7 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public string TestImage { get; set; } [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] - public Size BmpSystemDrawing() + public SDSize BmpSystemDrawing() { using (var memoryStream = new MemoryStream(this.bmpBytes)) { @@ -43,13 +42,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Description = "ImageSharp Bmp")] - public CoreSize BmpCore() + public Size BmpCore() { using (var memoryStream = new MemoryStream(this.bmpBytes)) { using (var image = Image.Load(memoryStream)) { - return new CoreSize(image.Width, image.Height); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs index ff378c75c3..3488d4405c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs @@ -1,14 +1,12 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // 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; +using CoreSize = SixLabors.ImageSharp.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs index be7e853000..f4cfddd885 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs @@ -1,13 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { @@ -31,7 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public string TestImage { get; set; } [Benchmark(Baseline = true, Description = "System.Drawing Gif")] - public Size GifSystemDrawing() + public SDSize GifSystemDrawing() { using (var memoryStream = new MemoryStream(this.gifBytes)) { @@ -43,13 +42,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Description = "ImageSharp Gif")] - public CoreSize GifCore() + public Size GifCore() { using (var memoryStream = new MemoryStream(this.gifBytes)) { using (var image = Image.Load(memoryStream)) { - return new CoreSize(image.Width, image.Height); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs index 39f09b6b6f..57ee308e7c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs @@ -1,17 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] public class DecodePng : BenchmarkBase { @@ -32,7 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Baseline = true, Description = "System.Drawing Png")] - public Size PngSystemDrawing() + public SDSize PngSystemDrawing() { using (var memoryStream = new MemoryStream(this.pngBytes)) { @@ -44,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Description = "ImageSharp Png")] - public CoreSize PngCore() + public Size PngCore() { using (var memoryStream = new MemoryStream(this.pngBytes)) { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs new file mode 100644 index 0000000000..4695d7ca49 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using System.IO; +using System.Threading; +using BenchmarkDotNet.Attributes; + +using ImageMagick; +using Pfim; +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class DecodeTga : BenchmarkBase + { + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + private readonly PfimConfig pfimConfig = new PfimConfig(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() + { + var settings = new MagickReadSettings { Format = MagickFormat.Tga }; + using (var image = new MagickImage(new MemoryStream(this.data), settings)) + { + return image.Width; + } + } + + [Benchmark(Description = "ImageSharp Tga")] + public int TgaCore() + { + using (var image = Image.Load(this.data, new TgaDecoder())) + { + return image.Width; + } + } + + [Benchmark(Description = "Pfim Tga")] + public int TgaPfim() + { + using (var image = Targa.Create(this.data, this.pfimConfig)) + { + return image.Width; + } + } + + private class PfimAllocator : IImageAllocator + { + private int rented; + private readonly ArrayPool shared = ArrayPool.Shared; + + public byte[] Rent(int size) + { + return 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/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs index 2a6e215569..c816aee2e9 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs index 379f8aa8bf..a7ffbe46ee 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -16,13 +16,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] public void EncodeBmpImageSharp() { - this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new BmpEncoder()); return null; }); + 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; }); + 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 index 89eb63d629..b6ce67bfdd 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; @@ -6,6 +6,7 @@ using System.IO; using BenchmarkDotNet.Attributes; 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; @@ -20,12 +21,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private SDImage bmpDrawing; private Image bmpCore; + // Try to get as close to System.Drawing's output as possible + private readonly GifEncoder encoder = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) + }; + + [Params(TestImages.Bmp.Car, TestImages.Png.Rgb48Bpp)] + public string TestImage { get; set; } + [GlobalSetup] public void ReadImages() { if (this.bmpStream == null) { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage)); this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = SDImage.FromStream(this.bmpStream); @@ -52,12 +62,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [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); + this.bmpCore.SaveAsGif(memoryStream, this.encoder); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs index bf9627f4c1..179e6946a8 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs @@ -1,10 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Benchmarks.Codecs @@ -23,15 +24,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs 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; + var options = new GifEncoder + { + 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; }); + 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 index 639d1594ee..b3113e6d78 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new OctreeQuantizer(false) }; + var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } @@ -95,9 +95,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new WuQuantizer(false) }; + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; 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 index 157dadd2c1..81b884b754 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs @@ -1,9 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; using System.IO; using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; @@ -56,8 +57,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - this.bmpCore.SaveAsPng(memoryStream); + var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; + this.bmpCore.SaveAsPng(memoryStream, encoder); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs new file mode 100644 index 0000000000..37cfa314c9 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using BenchmarkDotNet.Attributes; + +using ImageMagick; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class EncodeTga : BenchmarkBase + { + private MagickImage tgaMagick; + private Image tgaCore; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params(TestImages.Tga.Bit24BottomLeft)] + public string TestImage { get; set; } + + [GlobalSetup] + public void ReadImages() + { + if (this.tgaCore == null) + { + this.tgaCore = Image.Load(this.TestImageFullPath); + this.tgaMagick = new MagickImage(this.TestImageFullPath); + } + } + + [Benchmark(Baseline = true, Description = "Magick Tga")] + public void BmpSystemDrawing() + { + using (var memoryStream = new MemoryStream()) + { + this.tgaMagick.Write(memoryStream, MagickFormat.Tga); + } + } + + [Benchmark(Description = "ImageSharp Tga")] + public void BmpCore() + { + using (var memoryStream = new MemoryStream()) + { + this.tgaCore.SaveAsBmp(memoryStream); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs index a51ce8ebc6..197f0804d2 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Drawing; @@ -10,11 +10,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public class GetSetPixel : BenchmarkBase { [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] - public Color ResizeSystemDrawing() + public System.Drawing.Color ResizeSystemDrawing() { using (var source = new Bitmap(400, 400)) { - source.SetPixel(200, 200, Color.White); + source.SetPixel(200, 200, System.Drawing.Color.White); return source.GetPixel(200, 200); } } @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var image = new Image(400, 400)) { - image[200, 200] = Rgba32.White; + image[200, 200] = Color.White; return image[200, 200]; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs index ee77a2b6b4..61d18f0e9b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs @@ -1,14 +1,11 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // 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 @@ -78,4 +75,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } -#endif \ No newline at end of file +#endif diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs index bf9b1af338..bb7d08e22e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs @@ -1,44 +1,69 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - public class Block8x8F_CopyTo1x1 + public unsafe class Block8x8F_CopyTo1x1 { private Block8x8F block; + private readonly Block8x8F[] blockArray = new Block8x8F[1]; - private Buffer2D buffer; + private static readonly int Width = 100; - private BufferArea destArea; + 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.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { throw new InvalidOperationException("Benchmark Block8x8F_CopyTo1x1 is invalid on platforms without AVX2 support."); } - this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); - this.destArea = this.buffer.GetArea(200, 100, 64, 64); + 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.As(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride * sizeof(float); + 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); @@ -58,12 +83,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); } - [Benchmark] + // [Benchmark] public void UseVector8() { ref Block8x8F s = ref this.block; - ref float origin = ref this.destArea.GetReferenceToOrigin(); - int stride = this.destArea.Stride; + ref float origin = ref Unsafe.AsRef(this.bufferPtr); + int stride = Width; ref Vector d0 = ref Unsafe.As>(ref origin); ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); @@ -93,12 +118,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations d7 = row7; } - [Benchmark] + // [Benchmark] public void UseVector8_V2() { ref Block8x8F s = ref this.block; - ref float origin = ref this.destArea.GetReferenceToOrigin(); - int stride = this.destArea.Stride; + ref float origin = ref Unsafe.AsRef(this.bufferPtr); + int stride = Width; ref Vector d0 = ref Unsafe.As>(ref origin); ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); @@ -119,15 +144,247 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations d7 = Unsafe.As>(ref s.V7L); } - // RESULTS: + [Benchmark] + public void UseVector8_V3() + { + int stride = 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, (IntPtr)1); + Vector v2 = Unsafe.AddByteOffset(ref s, (IntPtr)2); + Vector v3 = Unsafe.AddByteOffset(ref s, (IntPtr)3); + + Unsafe.As>(ref d) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)stride)) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 2))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 3))) = v3; + + v0 = Unsafe.AddByteOffset(ref s, (IntPtr)4); + v1 = Unsafe.AddByteOffset(ref s, (IntPtr)5); + v2 = Unsafe.AddByteOffset(ref s, (IntPtr)6); + v3 = Unsafe.AddByteOffset(ref s, (IntPtr)7); + + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 4))) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 5))) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 6))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 7))) = v3; + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [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() + { + 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)); + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + + 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() + { + int stride = 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; + } + + [Benchmark] + public void UseVector256_Avx2_Variant3_RefCast_Mod() + { + int stride = 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, (IntPtr)1); + Vector256 v2 = Unsafe.AddByteOffset(ref s, (IntPtr)2); + Vector256 v3 = Unsafe.AddByteOffset(ref s, (IntPtr)3); + + Unsafe.As>(ref d) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)stride)) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 2))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 3))) = v3; + + v0 = Unsafe.AddByteOffset(ref s, (IntPtr)4); + v1 = Unsafe.AddByteOffset(ref s, (IntPtr)5); + v2 = Unsafe.AddByteOffset(ref s, (IntPtr)6); + v3 = Unsafe.AddByteOffset(ref s, (IntPtr)7); + + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 4))) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 5))) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 6))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 7))) = v3; + } + + // [Benchmark] + public void UseVector256_Avx2_Variant3_WithLocalPinning() + { + int stride = Width; + fixed (float* d = this.unpinnedBuffer) + fixed (Block8x8F* ss = &this.block) + { + var 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); + + 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_sbyte() + { + int stride = Width * 4; + var d = (sbyte*)this.bufferPtr; + var s = (sbyte*)this.blockPtr; + + 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); + + 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); + } +#endif + + // *** 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 | 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 + // | 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 65176af5bb..d915cbef0f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -8,8 +8,8 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { public class Block8x8F_CopyTo2x2 @@ -18,20 +18,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations private Buffer2D buffer; - private BufferArea destArea; + private Buffer2DRegion destRegion; [GlobalSetup] public void Setup() { this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); - this.destArea = this.buffer.GetArea(200, 100, 128, 128); + this.destRegion = this.buffer.GetRegion(200, 100, 128, 128); } [Benchmark(Baseline = true)] public void Original() { - ref float destBase = ref this.destArea.GetReferenceToOrigin(); - int destStride = this.destArea.Stride; + ref float destBase = ref this.destRegion.GetReferenceToOrigin(); + int destStride = this.destRegion.Stride; ref Block8x8F src = ref this.block; @@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations [Benchmark] public void Original_V2() { - ref float destBase = ref this.destArea.GetReferenceToOrigin(); - int destStride = this.destArea.Stride; + ref float destBase = ref this.destRegion.GetReferenceToOrigin(); + int destStride = this.destRegion.Stride; ref Block8x8F src = ref this.block; @@ -160,8 +160,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations [Benchmark] public void UseVector2() { - ref Vector2 destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride / 2; + ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; ref Block8x8F src = ref this.block; @@ -220,8 +220,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations [Benchmark] public void UseVector4() { - ref Vector2 destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride / 2; + ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; ref Block8x8F src = ref this.block; @@ -280,8 +280,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations [Benchmark] public void UseVector4_SafeRightCorner() { - ref Vector2 destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride / 2; + ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; ref Block8x8F src = ref this.block; @@ -335,12 +335,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations 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 Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; ref Block8x8F src = ref this.block; @@ -364,22 +363,30 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations 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; - + 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; @@ -401,4 +408,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations // 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 5502475d43..574a080001 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,7 +9,6 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { /// @@ -25,15 +24,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations private static readonly Vector4 Half = new Vector4(0.5f); private Block8x8F inputDividend; - private Block8x8F inputDivisior; + private Block8x8F inputDivisor; [GlobalSetup] public void Setup() { for (int i = 0; i < Block8x8F.Size; i++) { - this.inputDividend[i] = i*44.8f; - this.inputDivisior[i] = 100 - i; + this.inputDividend[i] = i * 44.8f; + this.inputDivisor[i] = 100 - i; } } @@ -43,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations int sum = 0; Block8x8F b1 = this.inputDividend; - Block8x8F b2 = this.inputDivisior; + Block8x8F b2 = this.inputDivisor; float* pDividend = (float*)&b1; float* pDivisor = (float*)&b2; @@ -54,10 +53,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations sum = 0; for (int i = 0; i < Block8x8F.Size; i++) { - int a = (int) pDividend[i]; - int b = (int) pDivisor; + 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]; @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations int sum = 0; Block8x8F b1 = this.inputDividend; - Block8x8F b2 = this.inputDivisior; + Block8x8F b2 = this.inputDivisor; float* pDividend = (float*)&b1; float* pDivisor = (float*)&b2; @@ -83,13 +83,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations for (int i = 0; i < Block8x8F.Size; i++) { double value = pDividend[i] / pDivisor[i]; - pDividend[i] = (float) System.Math.Round(value); + pDividend[i] = (float)System.Math.Round(value); } + for (int i = 0; i < Block8x8F.Size; i++) { - sum += (int) pDividend[i]; + sum += (int)pDividend[i]; } } + return sum; } @@ -99,7 +101,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations int sum = 0; Block8x8F bDividend = this.inputDividend; - Block8x8F bDivisor = this.inputDivisior; + Block8x8F bDivisor = this.inputDivisor; float* pDividend = (float*)&bDividend; for (int cnt = 0; cnt < ExecutionCount; cnt++) @@ -111,6 +113,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations sum += (int)pDividend[i]; } } + return sum; } @@ -138,10 +141,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { - Vector4 sign = Vector4.Min(dividend, Vector4.One); + var sign = Vector4.Min(dividend, Vector4.One); sign = Vector4.Max(sign, MinusOne); - return dividend / divisor + sign * Half; + return (dividend / divisor) + (sign * Half); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -155,4 +158,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations 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 29ee402a00..167e936910 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Numerics; @@ -10,6 +8,7 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { public class Block8x8F_LoadFromInt16 @@ -50,4 +49,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations // 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_Round.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs index c7b5802c4f..0a6a1d97e0 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs @@ -1,21 +1,30 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - public class Block8x8F_Round + public unsafe class Block8x8F_Round { - private Block8x8F block = default(Block8x8F); + private Block8x8F block; + + private readonly byte[] blockBuffer = new byte[512]; + private GCHandle blockHandle; + private float* alignedPtr; [GlobalSetup] public void Setup() @@ -25,13 +34,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations throw new NotSupportedException("Vector.Count != 8"); } - for (int i = 0; i < Block8x8F.Size; i++) + this.blockHandle = GCHandle.Alloc(this.blockBuffer, GCHandleType.Pinned); + ulong ptr = (ulong)this.blockHandle.AddrOfPinnedObject(); + ptr += 16; + ptr -= ptr % 16; + + if (ptr % 16 != 0) { - this.block[i] = i * 44.8f; + throw new Exception("ptr is unaligned"); } + + this.alignedPtr = (float*)ptr; } - [Benchmark(Baseline = true)] + [GlobalCleanup] + public void Cleanup() + { + this.blockHandle.Free(); + this.alignedPtr = null; + } + + [Benchmark] public void ScalarRound() { ref float b = ref Unsafe.As(ref this.block); @@ -43,8 +66,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations } } - [Benchmark] - public void SimdRound() + [Benchmark(Baseline = true)] + public void SimdUtils_FastRound_Vector8() { ref Block8x8F b = ref this.block; @@ -65,5 +88,411 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations ref Vector row7 = ref Unsafe.As>(ref b.V7L); row7 = SimdUtils.FastRound(row7); } + + [Benchmark] + public void SimdUtils_FastRound_Vector8_ForceAligned() + { + ref Block8x8F b = ref Unsafe.AsRef(this.alignedPtr); + + 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 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 = SimdUtils.FastRound(row0); + row1 = SimdUtils.FastRound(row1); + row2 = SimdUtils.FastRound(row2); + row3 = SimdUtils.FastRound(row3); + + 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 = SimdUtils.FastRound(row0); + row1 = SimdUtils.FastRound(row1); + row2 = SimdUtils.FastRound(row2); + row3 = SimdUtils.FastRound(row3); + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [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 unsafe void Sse41_V2() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + p = Sse41.RoundToNearestInteger(p); + var offset = (IntPtr)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 unsafe void Sse41_V3() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + p = Sse41.RoundToNearestInteger(p); + var offset = (IntPtr)sizeof(Vector128); + + for (int i = 0; i < 15; i++) + { + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + } + } + + [Benchmark] + public unsafe void Sse41_V4() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + var offset = (IntPtr)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 unsafe 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 unsafe 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); + } +#endif } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 5958b9f79d..0e68af87a9 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -1,18 +1,20 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using BenchmarkDotNet.Attributes; -using System.Drawing; using System.IO; +using BenchmarkDotNet.Attributes; + using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Tests; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { [Config(typeof(Config.ShortClr))] public class DecodeJpegParseStreamOnly { - [Params(TestImages.Jpeg.Baseline.Jpeg420Exif)] + [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] public string TestImage { get; set; } private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); @@ -26,26 +28,35 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark(Baseline = true, Description = "System.Drawing FULL")] - public Size JpegSystemDrawing() + public SDSize JpegSystemDrawing() { - using (var memoryStream = new MemoryStream(this.jpegBytes)) - { - using (var image = System.Drawing.Image.FromStream(memoryStream)) - { - return image.Size; - } - } + using var memoryStream = new MemoryStream(this.jpegBytes); + using var image = System.Drawing.Image.FromStream(memoryStream); + return image.Size; } [Benchmark(Description = "JpegDecoderCore.ParseStream")] - public void ParseStreamPdfJs() + public void ParseStream() { - using (var memoryStream = new MemoryStream(this.jpegBytes)) - { - var decoder = new JpegDecoderCore(Configuration.Default, new Formats.Jpeg.JpegDecoder() { IgnoreMetadata = true }); - decoder.ParseStream(memoryStream); - decoder.Dispose(); - } + using var memoryStream = new MemoryStream(this.jpegBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); + + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true }); + decoder.ParseStream(bufferedStream); + decoder.Dispose(); } } -} \ No newline at end of file + + /* + | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | + |---------------------------- |----------- |-------------- |--------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:| + | 'System.Drawing FULL' | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.828 ms | 0.9885 ms | 0.0542 ms | 1.00 | 46.8750 | - | - | 211566 B | + | JpegDecoderCore.ParseStream | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.833 ms | 0.2923 ms | 0.0160 ms | 1.00 | - | - | - | 12416 B | + | | | | | | | | | | | | | + | 'System.Drawing FULL' | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 6.018 ms | 2.1374 ms | 0.1172 ms | 1.00 | 46.8750 | - | - | 210768 B | + | JpegDecoderCore.ParseStream | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 4.382 ms | 0.9009 ms | 0.0494 ms | 0.73 | - | - | - | 12360 B | + | | | | | | | | | | | | | + | 'System.Drawing FULL' | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 5.714 ms | 0.4078 ms | 0.0224 ms | 1.00 | - | - | - | 176 B | + | JpegDecoderCore.ParseStream | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 4.239 ms | 1.0943 ms | 0.0600 ms | 0.74 | - | - | - | 12406 B | + */ +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs index f8a7556ca5..b92a66ebd2 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -10,8 +10,8 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { /// @@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg 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 fe112042ef..4b1ee81a4c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -1,19 +1,20 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Drawing; using System.IO; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; 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; -// ReSharper disable InconsistentNaming +using SDSize = System.Drawing.Size; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { /// @@ -26,18 +27,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { public Config() { - // Uncomment if you want to use any of the diagnoser - this.Add(new BenchmarkDotNet.Diagnosers.MemoryDiagnoser()); + this.Add(MemoryDiagnoser.Default); } public class ShortClr : Benchmarks.Config { public ShortClr() { - this.Add( - //Job.Clr.WithLaunchCount(1).WithWarmupCount(2).WithTargetCount(3), - Job.Core.WithLaunchCount(1).WithWarmupCount(2).WithTargetCount(3) - ); + // Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), + this.Add(Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3)); } } } @@ -45,7 +43,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private byte[] jpegBytes; private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - + +#pragma warning disable SA1115 [Params( TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, @@ -53,14 +52,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // 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)] - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr - )] public string TestImage { get; set; } - [GlobalSetup] public void ReadImages() { @@ -71,7 +67,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark(Baseline = true, Description = "Decode Jpeg - System.Drawing")] - public Size JpegSystemDrawing() + public SDSize JpegSystemDrawing() { using (var memoryStream = new MemoryStream(this.jpegBytes)) { @@ -83,37 +79,28 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark(Description = "Decode Jpeg - ImageSharp")] - public CoreSize JpegImageSharp() + public Size JpegImageSharp() { using (var memoryStream = new MemoryStream(this.jpegBytes)) { - using (var image = Image.Load(memoryStream, new JpegDecoder(){ IgnoreMetadata = true})) + using (var image = Image.Load(memoryStream, new JpegDecoder { IgnoreMetadata = true })) { - return new CoreSize(image.Width, image.Height); + return new Size(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 | + /* + | 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 | + */ } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs deleted file mode 100644 index d4cbe81e16..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs +++ /dev/null @@ -1,114 +0,0 @@ -// 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; - DoubleBufferedStreamReader reader1; - 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; - } - - private static byte[] CreateTestBytes() - { - byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3]; - var random = new Random(); - random.NextBytes(buffer); - - return buffer; - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index d934a1d6de..4c326fb3ac 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -1,7 +1,7 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// + +using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; @@ -10,9 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg using System.Drawing; using System.Drawing.Imaging; using System.IO; - - using BenchmarkDotNet.Attributes; - + using SixLabors.ImageSharp.Tests; using CoreImage = SixLabors.ImageSharp.Image; public class EncodeJpeg : BenchmarkBase @@ -27,7 +25,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { if (this.bmpStream == null) { - this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"); + const string TestImage = TestImages.Bmp.Car; + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); this.bmpCore = CoreImage.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = Image.FromStream(this.bmpStream); @@ -45,19 +44,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] public void JpegSystemDrawing() { - using (var memoryStream = new MemoryStream()) + using (var stream = new MemoryStream()) { - this.bmpDrawing.Save(memoryStream, ImageFormat.Jpeg); + this.bmpDrawing.Save(stream, ImageFormat.Jpeg); } } [Benchmark(Description = "ImageSharp Jpeg")] public void JpegCore() { - using (var memoryStream = new MemoryStream()) + using (var stream = new MemoryStream()) { - this.bmpCore.SaveAsJpeg(memoryStream); + this.bmpCore.SaveAsJpeg(stream); } } } } + +/* +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.959 (1909/November2018Update/19H2) +Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +.NET Core SDK=3.1.302 + [Host] : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT + DefaultJob : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT + + +| Method | Mean | Error | StdDev | Ratio | RatioSD | +|---------------------- |---------:|----------:|----------:|------:|--------:| +| 'System.Drawing Jpeg' | 4.297 ms | 0.0244 ms | 0.0228 ms | 1.00 | 0.00 | +| 'ImageSharp Jpeg' | 5.286 ms | 0.1034 ms | 0.0967 ms | 1.23 | 0.02 | +*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs index afa2ad325a..14b240339e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -18,13 +18,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] public void EncodeJpegImageSharp() { - this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new JpegEncoder()); return null; }); + 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; }); + 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 ae32167a9f..43d4ccf07b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs index e39cfa6ba2..2edc3e7afc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -13,8 +13,8 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { [Config(typeof(MultiImageBenchmarkBase.Config))] @@ -93,4 +93,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg }); } } -} \ 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 index 1834f77eaf..b46e747779 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs @@ -1,18 +1,20 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 System.IO; +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; + using SDImage = System.Drawing.Image; -using SixLabors.ImageSharp.Formats.Jpeg; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { [Config(typeof(Config.ShortClr))] @@ -29,9 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Params( TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, - - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr - )] + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] public string TestImage { get; set; } [Params(false, true)] @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // [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 | @@ -104,4 +104,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // 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 index 8417b32f27..7b47cf94a9 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } - [Benchmark(Baseline = true)] + [Benchmark] public void Scalar() { var values = new JpegColorConverter.ComponentValues(this.input, 0); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output, 255F, 128F); } - [Benchmark] + [Benchmark(Baseline = true)] public void SimdVector4() { var values = new JpegColorConverter.ComponentValues(this.input, 0); @@ -53,11 +53,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark] - public void SimdAvx2() + public void SimdVector8() { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output, 255F, 128F); + JpegColorConverter.FromYCbCrSimdVector8.ConvertCore(values, this.output, 255F, 128F); } private static Buffer2D[] CreateRandomValues( @@ -67,14 +67,14 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg float maxVal = 255f) { var rnd = new Random(42); - Buffer2D[] buffers = new Buffer2D[componentCount]; + var buffers = new Buffer2D[componentCount]; for (int i = 0; i < componentCount; i++) { - float[] values = new float[inputBufferLength]; + var values = new float[inputBufferLength]; for (int j = 0; j < inputBufferLength; j++) { - values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } // no need to dispose when buffer is not array owner @@ -84,4 +84,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg 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 446c038596..96fb2f4e6b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; @@ -18,10 +16,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs using System.Numerics; using BenchmarkDotNet.Attributes; - + using BenchmarkDotNet.Diagnosers; + using BenchmarkDotNet.Environments; using SixLabors.ImageSharp.Tests; - using CoreImage = ImageSharp.Image; + using CoreImage = SixLabors.ImageSharp.Image; public abstract class MultiImageBenchmarkBase { @@ -30,34 +29,40 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public Config() { // Uncomment if you want to use any of the diagnoser - this.Add(new BenchmarkDotNet.Diagnosers.MemoryDiagnoser()); + this.Add(MemoryDiagnoser.Default); } public class ShortClr : Benchmarks.Config { public ShortClr() { - this.Add( - Job.Core.WithLaunchCount(1).WithWarmupCount(1).WithTargetCount(2) - ); + this.Add(Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2)); } } } - protected Dictionary FileNamesToBytes = new Dictionary(); - - protected Dictionary> FileNamesToImageSharpImages = new Dictionary>(); - protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); + protected Dictionary fileNamesToBytes = new Dictionary(); + protected Dictionary> fileNamesToImageSharpImages = new Dictionary>(); + protected Dictionary fileNamesToSystemDrawingImages = new Dictionary(); /// - /// The values of this enum separate input files into categories + /// The values of this enum separate input files into categories. /// public enum InputImageCategory { + /// + /// Use all images. + /// AllImages, + /// + /// Use small images only. + /// SmallImagesOnly, + /// + /// Use large images only. + /// LargeImagesOnly } @@ -74,12 +79,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected virtual IEnumerable ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" }; /// - /// Enumerates folders containing files OR files to be processed by the benchmark. + /// Gets folders containing files OR files to be processed by the benchmark. /// protected IEnumerable AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); /// - /// The images sized above this threshold will be included in + /// Gets the large image threshold. + /// The images sized above this threshold will be included in. /// protected virtual int LargeImageThresholdInBytes => 100000; @@ -103,7 +109,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected IEnumerable> FileNames2Bytes => this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToBytes, + this.fileNamesToBytes, arr => arr.Length < this.LargeImageThresholdInBytes); protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } @@ -115,6 +121,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); } + // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); this.ReadFilesImpl(); } @@ -125,7 +132,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { if (File.Exists(path)) { - this.FileNamesToBytes[path] = File.ReadAllBytes(path); + this.fileNamesToBytes[path] = File.ReadAllBytes(path); continue; } @@ -139,26 +146,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs foreach (string fn in allFiles) { - this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); + this.fileNamesToBytes[fn] = File.ReadAllBytes(fn); } } } /// - /// Execute code for each image stream. If the returned object of the opearation is it will be disposed. + /// 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) { - using (MemoryStream memoryStream = new MemoryStream(kv.Value)) + using (var memoryStream = new MemoryStream(kv.Value)) { try { object obj = operation(memoryStream); (obj as IDisposable)?.Dispose(); - } catch (Exception ex) { @@ -174,31 +180,30 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { base.ReadFilesImpl(); - foreach (KeyValuePair kv in this.FileNamesToBytes) + foreach (KeyValuePair kv in this.fileNamesToBytes) { byte[] bytes = kv.Value; string fn = kv.Key; - using (MemoryStream ms1 = new MemoryStream(bytes)) + using (var ms1 = new MemoryStream(bytes)) { - this.FileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); - + 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, + this.fileNamesToImageSharpImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected IEnumerable> FileNames2SystemDrawingImages => this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToSystemDrawingImages, + this.fileNamesToSystemDrawingImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected virtual int LargeImageThresholdInPixels => 700000; @@ -211,27 +216,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { 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 MemoryStream()) + using (var workStream = new MemoryStream()) { - this.ForEachImageSharpImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); + // ReSharper restore AccessToDisposedClosure return result; }); @@ -256,21 +259,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected void ForEachSystemDrawingImage(Func operation) { - using (MemoryStream workStream = new MemoryStream()) + using (var workStream = new MemoryStream()) { - 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/Color/Bulk/FromRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs index b964221764..8f862de545 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs @@ -1,20 +1,18 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - -using System.Buffers; 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 FromRgba32Bytes - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private IMemoryOwner destination; @@ -23,7 +21,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk private Configuration configuration; [Params( - 128, + 128, 1024, 2048)] public int Count { get; set; } @@ -43,12 +41,12 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.source.Dispose(); } - //[Benchmark] + // [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; @@ -89,4 +87,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk // 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 index 2b9573ed73..da15da24c7 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs @@ -1,24 +1,29 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { [Config(typeof(Config.ShortClr))] public abstract class FromVector4 - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { protected IMemoryOwner source; @@ -26,10 +31,8 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk protected Configuration Configuration => Configuration.Default; - [Params( - 64, - 2048 - )] + // [Params(64, 2048)] + [Params(1024)] public int Count { get; set; } [GlobalSetup] @@ -46,12 +49,11 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.source.Dispose(); } - //[Benchmark] + // [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)); @@ -61,13 +63,13 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk [Benchmark] public void PixelOperations_Base() { - new PixelOperations().FromVector4(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); + new PixelOperations().FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); } [Benchmark] public void PixelOperations_Specialized() { - PixelOperations.Instance.FromVector4(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); + PixelOperations.Instance.FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); } } @@ -79,52 +81,105 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(sBytes, dFloats); } - [Benchmark(Baseline = true)] + [Benchmark] public void BasicIntrinsics256() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.BasicIntrinsics256.NormalizedFloatToByteSaturate(sBytes, dFloats); } - [Benchmark] + [Benchmark(Baseline = true)] public void ExtendedIntrinsic() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(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! +#if SUPPORTS_RUNTIME_INTRINSICS + [Benchmark] + public void UseAvx2() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.Avx2Intrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); + } + + private static ReadOnlySpan PermuteMaskDeinterleave8x32 => new byte[] { 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()); + + int n = dest.Length / 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); + + var maxBytes = Vector256.Create(255f); + + for (int 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; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale) + { + vf = Avx.Multiply(scale, vf); + return Avx.ConvertToVector256Int32(vf); + } +#endif + + // *** RESULTS 2020 March: *** + // 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 + // Job-IUZXZT : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT + // + // | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |---------------------------- |------ |-----------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:| + // | FallbackIntrinsics128 | 1024 | 2,952.6 ns | 1,680.77 ns | 92.13 ns | 3.32 | 0.16 | - | - | - | - | + // | BasicIntrinsics256 | 1024 | 1,664.5 ns | 928.11 ns | 50.87 ns | 1.87 | 0.09 | - | - | - | - | + // | ExtendedIntrinsic | 1024 | 890.6 ns | 375.48 ns | 20.58 ns | 1.00 | 0.00 | - | - | - | - | + // | UseAvx2 | 1024 | 299.0 ns | 30.47 ns | 1.67 ns | 0.34 | 0.01 | - | - | - | - | + // | UseAvx2_Grouped | 1024 | 318.1 ns | 48.19 ns | 2.64 ns | 0.36 | 0.01 | - | - | - | - | + // | PixelOperations_Base | 1024 | 8,136.9 ns | 1,834.82 ns | 100.57 ns | 9.14 | 0.26 | - | - | - | 24 B | + // | PixelOperations_Specialized | 1024 | 951.1 ns | 123.93 ns | 6.79 ns | 1.07 | 0.03 | - | - | - | - | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs index 294baa9d51..c66acd64a4 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs @@ -1,17 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class Rgb24Bytes - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private IMemoryOwner source; @@ -57,4 +56,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk 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 index 7f4b2bc41d..9a0801a6e9 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,11 +9,10 @@ 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 + where TPixel : unmanaged, IPixel { private IMemoryOwner source; @@ -39,7 +38,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.destination.Dispose(); } - //[Benchmark] + // [Benchmark] public void Naive() { Span s = this.source.GetSpan(); diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 70de8f4e27..8a54450016 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -1,21 +1,19 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - -using System.Buffers; using System; +using System.Buffers; using System.Numerics; - using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class ToVector4 - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { protected IMemoryOwner source; @@ -23,12 +21,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk protected Configuration Configuration => Configuration.Default; - [Params( - 64, - 256, - //512, - //1024, - 2048)] + [Params(64, 256, 2048)] // 512, 1024 public int Count { get; set; } [GlobalSetup] @@ -45,7 +38,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.destination.Dispose(); } - //[Benchmark] + // [Benchmark] public void Naive() { Span s = this.source.GetSpan(); @@ -56,7 +49,6 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk d[i] = s[i].ToVector4(); } } - [Benchmark] public void PixelOperations_Specialized() @@ -67,4 +59,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk 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 index 39702d5253..b090c7dc21 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; @@ -38,4 +41,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk // 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 index ab05a14073..145bf9889b 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -19,7 +22,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(sBytes, dFloats); } [Benchmark] @@ -37,7 +40,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.BasicIntrinsics256.ByteToNormalizedFloat(sBytes, dFloats); } [Benchmark] @@ -46,10 +49,10 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.ExtendedIntrinsics.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(sBytes, dFloats); } - //[Benchmark] + // [Benchmark] public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_2Loops() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); @@ -91,7 +94,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk } } - //[Benchmark] + // [Benchmark] public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_ConvertInSameLoop() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); @@ -127,38 +130,39 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector ConvertToNormalizedSingle(Vector u, Vector scale) { - Vector vi = Vector.AsVectorInt32(u); - Vector v = Vector.ConvertToSingle(vi); + var vi = Vector.AsVectorInt32(u); + var 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! + /*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 02017cbb7d..0c0a63bfbe 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs @@ -1,14 +1,12 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// -namespace SixLabors.ImageSharp.Benchmarks -{ - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats; +namespace SixLabors.ImageSharp.Benchmarks +{ using SystemColor = System.Drawing.Color; public class ColorEquality diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs index cc3472e222..914041e5b5 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs @@ -1,13 +1,16 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; - using Colourful; - using Colourful.Conversion; +using Colourful; +using Colourful.Conversion; - using SixLabors.ImageSharp.ColorSpaces; - using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +{ public class ColorspaceCieXyzToCieLabConvert { private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); @@ -18,7 +21,6 @@ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { @@ -31,4 +33,4 @@ 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 d109995184..c6f4c04711 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs @@ -1,13 +1,16 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; - using Colourful; - using Colourful.Conversion; +using Colourful; +using Colourful.Conversion; - using SixLabors.ImageSharp.ColorSpaces; - using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +{ public class ColorspaceCieXyzToHunterLabConvert { private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); @@ -18,7 +21,6 @@ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { @@ -31,4 +33,4 @@ 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 da7b9c3dd3..c7f78bb08f 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs @@ -1,13 +1,16 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; - using Colourful; - using Colourful.Conversion; +using Colourful; +using Colourful.Conversion; - using SixLabors.ImageSharp.ColorSpaces; - using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +{ public class ColorspaceCieXyzToLmsConvert { private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); @@ -18,7 +21,6 @@ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs index 2a5754ab0e..18494f3f67 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs @@ -1,13 +1,16 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; - using Colourful; - using Colourful.Conversion; +using Colourful; +using Colourful.Conversion; - using SixLabors.ImageSharp.ColorSpaces; - using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +{ public class ColorspaceCieXyzToRgbConvert { private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); @@ -18,7 +21,6 @@ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { @@ -31,4 +33,4 @@ 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 index 335ecf4789..4b046b3c4f 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Benchmarks +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Benchmarks { public partial class RgbToYCbCr { @@ -234,4 +237,4 @@ }; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs index 07ae17d754..f4f9443334 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -85,7 +85,6 @@ namespace SixLabors.ImageSharp.Benchmarks } } - // Waiting for C# 7 stackalloc keyword patiently ... private static class OnStackInputCache { public unsafe struct Byte @@ -94,11 +93,12 @@ namespace SixLabors.ImageSharp.Benchmarks public static Byte Create(byte[] data) { - Byte result = default(Byte); + Byte result = default; for (int i = 0; i < data.Length; i++) { result.Data[i] = data[i]; } + return result; } } @@ -113,9 +113,9 @@ namespace SixLabors.ImageSharp.Benchmarks // 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 byte[] inputSourceRGB; - private int[] inputSourceRGBAsInteger = null; + private int[] inputSourceRGBAsInteger; [GlobalSetup] public void Setup() @@ -126,6 +126,7 @@ namespace SixLabors.ImageSharp.Benchmarks { this.inputSourceRGB[i] = (byte)(42 + i); } + this.inputSourceRGBAsInteger = new int[InputByteCount + Vector.Count]; // Filling this should be part of the measured operation } @@ -133,14 +134,13 @@ namespace SixLabors.ImageSharp.Benchmarks public unsafe void RgbaToYcbCrScalarFloat() { // Copy the input to the stack: - OnStackInputCache.Byte input = OnStackInputCache.Byte.Create(this.inputSourceRGB); + var 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 :) + Result result = default; + var yPtr = (float*)&result.Y; + var cbPtr = (float*)&result.Cb; + var crPtr = (float*)&result.Cr; for (int i = 0; i < InputColorCount; i++) { @@ -159,24 +159,22 @@ namespace SixLabors.ImageSharp.Benchmarks public unsafe void RgbaToYcbCrSimdFloat() { // Copy the input to the stack: - OnStackInputCache.Byte input = OnStackInputCache.Byte.Create(this.inputSourceRGB); + var 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 :) + Result result = default; + var yPtr = (float*)&result.Y; + var cbPtr = (float*)&result.Cb; + var crPtr = (float*)&result.Cr; for (int i = 0; i < InputColorCount; i++) { int i3 = i * 3; - Vector3 vectorRgb = new Vector3( + var vectorRgb = new Vector3( input.Data[i3 + 0], input.Data[i3 + 1], - input.Data[i3 + 2] - ); + input.Data[i3 + 2]); Vector3 vectorY = VectorY * vectorRgb; Vector3 vectorCb = VectorCb * vectorRgb; @@ -194,15 +192,14 @@ namespace SixLabors.ImageSharp.Benchmarks // Copy the input to the stack: // 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 :) + Result result = default; + var yPtr = (float*)&result.Y; + var cbPtr = (float*)&result.Cb; + var crPtr = (float*)&result.Cr; - Vector yCoeffs = new Vector(ScaledCoeffs.Y); - Vector cbCoeffs = new Vector(ScaledCoeffs.Cb); - Vector crCoeffs = new Vector(ScaledCoeffs.Cr); + 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++) { @@ -211,7 +208,7 @@ namespace SixLabors.ImageSharp.Benchmarks for (int i = 0; i < InputColorCount; i += 2) { - Vector rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); + var rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); Vector y = yCoeffs * rgb; Vector cb = cbCoeffs * rgb; @@ -228,7 +225,7 @@ namespace SixLabors.ImageSharp.Benchmarks } /// - /// This should perform better. Coreclr emmitted Vector.Dot() code lacks the vectorization even with IsHardwareAccelerated == true. + /// This should perform better. Coreclr emitted 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/ @@ -240,23 +237,22 @@ namespace SixLabors.ImageSharp.Benchmarks // Copy the input to the stack: // On-stack output: - Result result = default(Result); + Result result = default; float* yPtr = (float*)&result.Y; float* cbPtr = (float*)&result.Cb; float* crPtr = (float*)&result.Cr; - // end of code-bloat block :) - Vector yCoeffs = new Vector(ScaledCoeffs.Y); - Vector cbCoeffs = new Vector(ScaledCoeffs.Cb); - Vector crCoeffs = new Vector(ScaledCoeffs.Cr); + var yCoeffs = new Vector(ScaledCoeffs.Y); + var cbCoeffs = new Vector(ScaledCoeffs.Cb); + var crCoeffs = new Vector(ScaledCoeffs.Cr); - Vector leftY = new Vector(ScaledCoeffs.SelectLeft.Y); - Vector leftCb = new Vector(ScaledCoeffs.SelectLeft.Cb); - Vector leftCr = new Vector(ScaledCoeffs.SelectLeft.Cr); + var leftY = new Vector(ScaledCoeffs.SelectLeft.Y); + var leftCb = new Vector(ScaledCoeffs.SelectLeft.Cb); + var leftCr = new Vector(ScaledCoeffs.SelectLeft.Cr); - Vector rightY = new Vector(ScaledCoeffs.SelectRight.Y); - Vector rightCb = new Vector(ScaledCoeffs.SelectRight.Cb); - Vector rightCr = new Vector(ScaledCoeffs.SelectRight.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++) { @@ -265,7 +261,7 @@ namespace SixLabors.ImageSharp.Benchmarks for (int i = 0; i < InputColorCount; i += 2) { - Vector rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); + var rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); Vector y = yCoeffs * rgb; Vector cb = cbCoeffs * rgb; @@ -300,14 +296,13 @@ namespace SixLabors.ImageSharp.Benchmarks public unsafe void RgbaToYcbCrScaledInteger() { // Copy the input to the stack: - OnStackInputCache.Byte input = OnStackInputCache.Byte.Create(this.inputSourceRGB); + var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); // On-stack output: - Result result = default(Result); + 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++) { @@ -339,14 +334,13 @@ namespace SixLabors.ImageSharp.Benchmarks public unsafe void RgbaToYcbCrScaledIntegerLut() { // Copy the input to the stack: - OnStackInputCache.Byte input = OnStackInputCache.Byte.Create(this.inputSourceRGB); + var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); // On-stack output: - Result result = default(Result); + 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++) { diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs index 92008f6e20..21cf10bb78 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -1,13 +1,16 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; - using Colourful; - using Colourful.Conversion; +using Colourful; +using Colourful.Conversion; - using SixLabors.ImageSharp.ColorSpaces; - using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +{ public class RgbWorkingSpaceAdapt { private static readonly Rgb Rgb = new Rgb(0.206162F, 0.260277F, 0.746717F, RgbWorkingSpaces.WideGamutRgb); @@ -18,7 +21,6 @@ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter { TargetRGBWorkingSpace = RGBWorkingSpaces.sRGB }; - [Benchmark(Baseline = true, Description = "Colourful Adapt")] public RGBColor ColourfulConvert() { diff --git a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs index 2e3307d298..c962886d1c 100644 --- a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs +++ b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Benchmarks +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Benchmarks { using System.Numerics; diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index b467579425..f9240779b9 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -1,20 +1,30 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// +#if Windows_NT +using System.Security.Principal; +using BenchmarkDotNet.Diagnostics.Windows; +#endif using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; namespace SixLabors.ImageSharp.Benchmarks { - using BenchmarkDotNet.Jobs; - public class Config : ManualConfig { public Config() { - // Uncomment if you want to use any of the diagnoser - this.Add(new BenchmarkDotNet.Diagnosers.MemoryDiagnoser()); + this.Add(MemoryDiagnoser.Default); + +#if Windows_NT + if (this.IsElevated) + { + this.Add(new NativeMemoryProfiler()); + } +#endif + } public class ShortClr : Config @@ -22,10 +32,28 @@ namespace SixLabors.ImageSharp.Benchmarks public ShortClr() { this.Add( - Job.Clr.WithLaunchCount(1).WithWarmupCount(3).WithTargetCount(3), - Job.Core.WithLaunchCount(1).WithWarmupCount(3).WithTargetCount(3) - ); + Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + } + } + + public class ShortCore31 : Config + { + public ShortCore31() + { + this.Add(Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + } + } + +#if Windows_NT + private bool IsElevated + { + get + { + return 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 deleted file mode 100644 index edbbceb628..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) James Jackson-South 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 (Bitmap destination = new Bitmap(800, 800)) - { - - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - Pen pen = new Pen(System.Drawing.Color.HotPink, 10); - graphics.DrawBeziers(pen, new[] { - new PointF(10, 500), - new PointF(30, 10), - new PointF(240, 30), - new PointF(300, 500) - }); - } - - using (MemoryStream ms = new MemoryStream()) - { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Draw Beziers")] - public void DrawLinesCore() - { - using (Image 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 (MemoryStream ms = new MemoryStream()) - { - image.SaveAsBmp(ms); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs deleted file mode 100644 index 8946835993..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace SixLabors.ImageSharp.Benchmarks -{ - using System.Drawing; - using System.Drawing.Drawing2D; - using System.IO; - using System.Numerics; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Processing; - - 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; - var pen = new Pen(System.Drawing.Color.HotPink, 10); - graphics.DrawLines(pen, new[] { - new PointF(10, 10), - new PointF(550, 50), - new PointF(200, 400) - }); - } - - using (var ms = new MemoryStream()) - { - destination.Save(ms, 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 ms = new MemoryStream()) - { - image.SaveAsBmp(ms); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs deleted file mode 100644 index 5fbd9f1123..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) James Jackson-South 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 (Bitmap destination = new Bitmap(800, 800)) - { - - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - Pen pen = new Pen(System.Drawing.Color.HotPink, 10); - graphics.DrawPolygon(pen, new[] { - new PointF(10, 10), - new PointF(550, 50), - new PointF(200, 400) - }); - } - - using (MemoryStream ms = new MemoryStream()) - { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Draw Polygon")] - public void DrawPolygonCore() - { - using (Image 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 (MemoryStream 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 deleted file mode 100644 index 624b54278a..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright (c) James Jackson-South 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 (Bitmap destination = new Bitmap(800, 800)) - { - - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - Pen pen = new Pen(System.Drawing.Color.HotPink, 10); - 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 (Image 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 (Image 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 deleted file mode 100644 index ba6d055e37..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) James Jackson-South 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 (Bitmap destination = new Bitmap(800, 800)) - { - - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - Pen pen = new Pen(System.Drawing.Color.HotPink, 10); - var font = new Font("Arial", 12, GraphicsUnit.Point); - 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 (Image 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 (Image 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 deleted file mode 100644 index 8aadb85bf3..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs +++ /dev/null @@ -1,89 +0,0 @@ -// -// Copyright (c) James Jackson-South 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 (Bitmap destination = new Bitmap(800, 800)) - { - - using (Graphics 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 (MemoryStream ms = new MemoryStream()) - { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Fill Polygon")] - public void DrawSolidPolygonCore() - { - using (Image 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 (MemoryStream ms = new MemoryStream()) - { - image.SaveAsBmp(ms); - } - } - } - - [Benchmark(Description = "ImageSharp Fill Polygon - cached shape")] - public void DrawSolidPolygonCoreCahced() - { - using (Image image = new Image(800, 800)) - { - image.Mutate(x => x.Fill( - Rgba32.HotPink, - this.shape)); - - using (MemoryStream ms = new MemoryStream()) - { - image.SaveAsBmp(ms); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs deleted file mode 100644 index 643e4ac9a1..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) James Jackson-South 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 (Bitmap destination = new Bitmap(800, 800)) - { - - using (Graphics 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 (Image 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 (Image 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 deleted file mode 100644 index 5f8f2ff064..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (c) James Jackson-South 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 (Bitmap destination = new Bitmap(800, 800)) - { - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.SmoothingMode = SmoothingMode.AntiAlias; - HatchBrush 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 (MemoryStream ms = new MemoryStream()) - { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Fill with Pattern")] - public void DrawPatternPolygon3Core() - { - using (Image image = new Image(800, 800)) - { - image.Mutate(x => x.Fill(CoreBrushes.BackwardDiagonal(Rgba32.HotPink))); - - using (MemoryStream ms = new MemoryStream()) - { - image.SaveAsBmp(ms); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs new file mode 100644 index 0000000000..eba4bcbb43 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png.Zlib; +using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; + +namespace SixLabors.ImageSharp.Benchmarks.General +{ + [Config(typeof(Config.ShortClr))] + public class Adler32Benchmark + { + private byte[] data; + private readonly SharpAdler32 adler = new SharpAdler32(); + + [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 60d89847fd..16cbb59918 100644 --- a/tests/ImageSharp.Benchmarks/General/Array2D.cs +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -1,18 +1,16 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; +using System; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp; - /** - * Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | +namespace SixLabors.ImageSharp.Benchmarks.General +{ + /* + 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 | @@ -21,7 +19,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General * */ - public class Array2D { private float[] flatArray; @@ -36,6 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General public int Count { get; set; } public int Min { get; private set; } + public int Max { get; private set; } [GlobalSetup] @@ -67,11 +65,12 @@ namespace SixLabors.ImageSharp.Benchmarks.General { for (int j = this.Min; j < this.Max; j++) { - ref float v = ref a[count * i + j]; + ref float v = ref a[(count * i) + j]; v = i * j; s += v; } } + return s; } @@ -89,6 +88,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } @@ -106,6 +106,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } @@ -123,7 +124,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs deleted file mode 100644 index ac6b3f93c7..0000000000 --- a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - using BenchmarkDotNet.Attributes; - - [Config(typeof(Config.ShortClr))] - public class ArrayCopy - { - [Params(10, 100, 1000, 10000)] - public int Count { get; set; } - - byte[] source; - - byte[] destination; - - [GlobalSetup] - public void SetUp() - { - this.source = new byte[this.Count]; - this.destination = new byte[this.Count]; - } - - [Benchmark(Baseline = true, Description = "Copy using Array.Copy()")] - public void CopyArray() - { - Array.Copy(this.source, this.destination, this.Count); - } - - [Benchmark(Description = "Copy using Unsafe")] - public unsafe void CopyUnsafe() - { - fixed (byte* pinnedDestination = this.destination) - fixed (byte* pinnedSource = this.source) - { - Unsafe.CopyBlock(pinnedSource, pinnedDestination, (uint)this.Count); - } - } - - [Benchmark(Description = "Copy using Buffer.BlockCopy()")] - public void CopyUsingBufferBlockCopy() - { - Buffer.BlockCopy(this.source, 0, this.destination, 0, this.Count); - } - - [Benchmark(Description = "Copy using Buffer.MemoryCopy")] - public unsafe void CopyUsingBufferMemoryCopy() - { - fixed (byte* pinnedDestination = this.destination) - fixed (byte* pinnedSource = this.source) - { - Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); - } - } - - [Benchmark(Description = "Copy using Marshal.Copy")] - public unsafe void CopyUsingMarshalCopy() - { - fixed (byte* pinnedDestination = this.destination) - { - Marshal.Copy(this.source, 0, (IntPtr)pinnedDestination, this.Count); - } - } - - /***************************************************************************************************************** - *************** RESULTS on i7-4810MQ 2.80GHz + Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1085.0 ******************** - ***************************************************************************************************************** - * - * Method | Count | Mean | StdErr | StdDev | Scaled | Scaled-StdDev | - * ---------------------------------- |------ |------------ |----------- |----------- |------- |-------------- | - * 'Copy using Array.Copy()' | 10 | 20.3074 ns | 0.1194 ns | 0.2068 ns | 1.00 | 0.00 | - * 'Copy using Unsafe' | 10 | 6.1002 ns | 0.1981 ns | 0.3432 ns | 0.30 | 0.01 | - * 'Copy using Buffer.BlockCopy()' | 10 | 10.7879 ns | 0.0984 ns | 0.1705 ns | 0.53 | 0.01 | - * 'Copy using Buffer.MemoryCopy' | 10 | 4.9625 ns | 0.0200 ns | 0.0347 ns | 0.24 | 0.00 | - * 'Copy using Marshal.Copy' | 10 | 16.1782 ns | 0.0919 ns | 0.1592 ns | 0.80 | 0.01 | - * - * 'Copy using Array.Copy()' | 100 | 31.5945 ns | 0.2908 ns | 0.5037 ns | 1.00 | 0.00 | - * 'Copy using Unsafe' | 100 | 10.2722 ns | 0.5202 ns | 0.9010 ns | 0.33 | 0.02 | - * 'Copy using Buffer.BlockCopy()' | 100 | 22.0322 ns | 0.0284 ns | 0.0493 ns | 0.70 | 0.01 | - * 'Copy using Buffer.MemoryCopy' | 100 | 10.2472 ns | 0.0359 ns | 0.0622 ns | 0.32 | 0.00 | - * 'Copy using Marshal.Copy' | 100 | 34.3820 ns | 1.1868 ns | 2.0555 ns | 1.09 | 0.05 | - * - * 'Copy using Array.Copy()' | 1000 | 40.9743 ns | 0.0521 ns | 0.0902 ns | 1.00 | 0.00 | - * 'Copy using Unsafe' | 1000 | 42.7840 ns | 2.0139 ns | 3.4882 ns | 1.04 | 0.07 | - * 'Copy using Buffer.BlockCopy()' | 1000 | 33.7361 ns | 0.0751 ns | 0.1300 ns | 0.82 | 0.00 | - * 'Copy using Buffer.MemoryCopy' | 1000 | 35.7541 ns | 0.0480 ns | 0.0832 ns | 0.87 | 0.00 | - * 'Copy using Marshal.Copy' | 1000 | 42.2028 ns | 0.2769 ns | 0.4795 ns | 1.03 | 0.01 | - * - * 'Copy using Array.Copy()' | 10000 | 200.0438 ns | 0.2251 ns | 0.3899 ns | 1.00 | 0.00 | - * 'Copy using Unsafe' | 10000 | 389.6957 ns | 13.2770 ns | 22.9964 ns | 1.95 | 0.09 | - * 'Copy using Buffer.BlockCopy()' | 10000 | 191.3478 ns | 0.1557 ns | 0.2697 ns | 0.96 | 0.00 | - * 'Copy using Buffer.MemoryCopy' | 10000 | 196.4679 ns | 0.2731 ns | 0.4730 ns | 0.98 | 0.00 | - * 'Copy using Marshal.Copy' | 10000 | 202.5392 ns | 0.5561 ns | 0.9631 ns | 1.01 | 0.00 | - * - */ - } -} diff --git a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs index 45a8519a9c..cd3fc5a069 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs @@ -1,22 +1,20 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; +using System; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General +{ public class ArrayReverse { [Params(4, 16, 32)] public int Count { get; set; } - byte[] source; + private byte[] source; - byte[] destination; + private byte[] destination; [GlobalSetup] public void SetUp() @@ -36,12 +34,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General { 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; - //} + /* + 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) diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs index ea53959b6a..b6cdcf5f55 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs index 3b7dea0955..516c187e34 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { @@ -38,7 +40,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath return acc; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float ClampUsingMathF(float x, float min, float max) { @@ -67,4 +68,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath // 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 6ce82ba115..1c58636df0 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// using System; using System.Runtime.CompilerServices; @@ -78,8 +76,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath public byte ClampBitwise() { int x = this.Value; - int absmax = byte.MaxValue - x; - x = (x + byte.MaxValue - AbsBitwiseVer(ref absmax)) >> 1; + int absMax = byte.MaxValue - x; + x = (x + byte.MaxValue - AbsBitwiseVer(ref absMax)) >> 1; x = (x + byte.MinValue + AbsBitwiseVer(ref x)) >> 1; return (byte)x; diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs new file mode 100644 index 0000000000..0b5f31ee4b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 94349b20b6..55e26372b0 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { @@ -19,4 +22,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath 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 d5683673fe..9da7b9fdf3 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { @@ -28,4 +31,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath // 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 0f256fc781..ad8f8746c7 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; @@ -9,7 +12,6 @@ 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() { diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs new file mode 100644 index 0000000000..986276774d --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +{ + public class Round + { + private const float Input = .51F; + + [Benchmark] + public int ConvertTo() => Convert.ToInt32(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 | + } +} diff --git a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs new file mode 100644 index 0000000000..96a95942cb --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs @@ -0,0 +1,229 @@ +// Copyright (c) Six Labors. +// 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 +{ + /// + /// 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 + { + 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 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 | - | - | - | - | + } +} diff --git a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs new file mode 100644 index 0000000000..2dcf036278 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png.Zlib; +using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32; + +namespace SixLabors.ImageSharp.Benchmarks.General +{ + [Config(typeof(Config.ShortClr))] + public class Crc32Benchmark + { + private byte[] data; + private readonly SharpCrc32 crc = new SharpCrc32(); + + [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.crc.Reset(); + this.crc.Update(this.data); + return this.crc.Value; + } + + [Benchmark] + public long SixLaborsCalculate() + { + return Crc32.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 | 2,797.77 ns | 278.697 ns | 15.276 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET 4.7.2 | 1024 | 2,275.56 ns | 216.100 ns | 11.845 ns | 0.81 | 0.01 | - | - | - | - | + // | | | | | | | | | | | | | + // | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 2,923.43 ns | 2,656.882 ns | 145.633 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET Core 2.1 | 1024 | 2,257.79 ns | 75.081 ns | 4.115 ns | 0.77 | 0.04 | - | - | - | - | + // | | | | | | | | | | | | | + // | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 2,764.14 ns | 86.281 ns | 4.729 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET Core 3.1 | 1024 | 49.32 ns | 1.813 ns | 0.099 ns | 0.02 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | + // | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 5,603.71 ns | 427.240 ns | 23.418 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET 4.7.2 | 2048 | 4,525.02 ns | 33.931 ns | 1.860 ns | 0.81 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | + // | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 5,563.32 ns | 49.337 ns | 2.704 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET Core 2.1 | 2048 | 4,519.61 ns | 29.837 ns | 1.635 ns | 0.81 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | + // | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 5,543.37 ns | 518.551 ns | 28.424 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET Core 3.1 | 2048 | 89.07 ns | 3.312 ns | 0.182 ns | 0.02 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | + // | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 11,396.95 ns | 373.450 ns | 20.470 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET 4.7.2 | 4096 | 9,070.35 ns | 271.083 ns | 14.859 ns | 0.80 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | + // | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 11,127.81 ns | 239.177 ns | 13.110 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET Core 2.1 | 4096 | 9,050.46 ns | 230.916 ns | 12.657 ns | 0.81 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | + // | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 11,098.62 ns | 687.978 ns | 37.710 ns | 1.00 | 0.00 | - | - | - | - | + // | SixLaborsCalculate | .NET Core 3.1 | 4096 | 168.11 ns | 3.633 ns | 0.199 ns | 0.02 | 0.00 | - | - | - | - | +} diff --git a/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs new file mode 100644 index 0000000000..baabb4784b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs @@ -0,0 +1,279 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +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 new file mode 100644 index 0000000000..be232c78d6 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs @@ -0,0 +1,206 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.IO; + +namespace SixLabors.ImageSharp.Benchmarks.IO +{ + [Config(typeof(Config.ShortClr))] + 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 BufferedReadStream bufferedStream1; + private BufferedReadStream bufferedStream2; + 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.bufferedStream1 = new BufferedReadStream(Configuration.Default, this.stream3); + this.bufferedStream2 = new BufferedReadStream(Configuration.Default, this.stream4); + this.bufferedStreamWrap1 = new BufferedReadStreamWrapper(this.stream5); + this.bufferedStreamWrap2 = new BufferedReadStreamWrapper(this.stream6); + } + + [GlobalCleanup] + public void DestroyStreams() + { + this.bufferedStream1?.Dispose(); + this.bufferedStream2?.Dispose(); + this.bufferedStreamWrap1?.Dispose(); + this.bufferedStreamWrap2?.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; + 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 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 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; + Stream 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 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() + { + var buffer = new byte[Configuration.Default.StreamProcessingBufferSize * 3]; + var random = new Random(); + random.NextBytes(buffer); + + return buffer; + } + } + + /* + BenchmarkDotNet=v0.12.0, OS=Windows 10.0.19041 + Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores + .NET Core SDK=3.1.301 + [Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT + Job-LKLBOT : .NET Framework 4.8 (4.8.4180.0), X64 RyuJIT + Job-RSTMKF : .NET Core 2.1.19 (CoreCLR 4.6.28928.01, CoreFX 4.6.28928.04), X64 RyuJIT + Job-PZIHIV : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT + + IterationCount=3 LaunchCount=1 WarmupCount=3 + +| Method | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | +|------------------------------- |-------------- |----------:|------------:|-----------:|------:|--------:|------:|------:|------:|----------:| +| StandardStreamRead | .NET 4.7.2 | 63.238 us | 49.7827 us | 2.7288 us | 0.66 | 0.13 | - | - | - | - | +| BufferedReadStreamRead | .NET 4.7.2 | 66.092 us | 0.4273 us | 0.0234 us | 0.69 | 0.11 | - | - | - | - | +| BufferedReadStreamWrapRead | .NET 4.7.2 | 26.216 us | 3.0527 us | 0.1673 us | 0.27 | 0.04 | - | - | - | - | +| StandardStreamReadByte | .NET 4.7.2 | 97.900 us | 261.7204 us | 14.3458 us | 1.00 | 0.00 | - | - | - | - | +| BufferedReadStreamReadByte | .NET 4.7.2 | 97.260 us | 1.2979 us | 0.0711 us | 1.01 | 0.15 | - | - | - | - | +| BufferedReadStreamWrapReadByte | .NET 4.7.2 | 19.170 us | 2.2296 us | 0.1222 us | 0.20 | 0.03 | - | - | - | - | +| ArrayReadByte | .NET 4.7.2 | 12.878 us | 11.1292 us | 0.6100 us | 0.13 | 0.02 | - | - | - | - | +| | | | | | | | | | | | +| StandardStreamRead | .NET Core 2.1 | 60.618 us | 131.7038 us | 7.2191 us | 0.78 | 0.10 | - | - | - | - | +| BufferedReadStreamRead | .NET Core 2.1 | 30.006 us | 25.2499 us | 1.3840 us | 0.38 | 0.02 | - | - | - | - | +| BufferedReadStreamWrapRead | .NET Core 2.1 | 29.241 us | 6.5020 us | 0.3564 us | 0.37 | 0.01 | - | - | - | - | +| StandardStreamReadByte | .NET Core 2.1 | 78.074 us | 15.8463 us | 0.8686 us | 1.00 | 0.00 | - | - | - | - | +| BufferedReadStreamReadByte | .NET Core 2.1 | 14.737 us | 20.1510 us | 1.1045 us | 0.19 | 0.01 | - | - | - | - | +| BufferedReadStreamWrapReadByte | .NET Core 2.1 | 13.234 us | 1.4711 us | 0.0806 us | 0.17 | 0.00 | - | - | - | - | +| ArrayReadByte | .NET Core 2.1 | 9.373 us | 0.6108 us | 0.0335 us | 0.12 | 0.00 | - | - | - | - | +| | | | | | | | | | | | +| StandardStreamRead | .NET Core 3.1 | 52.151 us | 19.9456 us | 1.0933 us | 0.65 | 0.03 | - | - | - | - | +| BufferedReadStreamRead | .NET Core 3.1 | 29.217 us | 0.2490 us | 0.0136 us | 0.36 | 0.01 | - | - | - | - | +| BufferedReadStreamWrapRead | .NET Core 3.1 | 32.962 us | 7.1382 us | 0.3913 us | 0.41 | 0.02 | - | - | - | - | +| StandardStreamReadByte | .NET Core 3.1 | 80.310 us | 45.0350 us | 2.4685 us | 1.00 | 0.00 | - | - | - | - | +| BufferedReadStreamReadByte | .NET Core 3.1 | 13.092 us | 0.6268 us | 0.0344 us | 0.16 | 0.00 | - | - | - | - | +| BufferedReadStreamWrapReadByte | .NET Core 3.1 | 13.282 us | 3.8689 us | 0.2121 us | 0.17 | 0.01 | - | - | - | - | +| ArrayReadByte | .NET Core 3.1 | 9.349 us | 2.9860 us | 0.1637 us | 0.12 | 0.00 | - | - | - | - | + */ +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs index b5f339fb37..12ebbcf4b7 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs @@ -1,10 +1,13 @@ -using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - interface ITestPixel + public interface ITestPixel where T : struct, ITestPixel { void FromRgba32(Rgba32 source); @@ -25,4 +28,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion 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 9f1b2721b4..7d6c2efedf 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; @@ -9,6 +10,7 @@ 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 @@ -16,23 +18,23 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion internal struct ConversionRunner where T : struct, ITestPixel { - public readonly T[] dest; + public readonly T[] Dest; - public readonly Rgba32[] source; + public readonly Rgba32[] Source; public ConversionRunner(int count) { - this.dest = new T[count]; - this.source = new Rgba32[count]; + this.Dest = new T[count]; + this.Source = new Rgba32[count]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunByRefConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -43,10 +45,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunByValConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -57,10 +59,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunFromBytesConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -69,22 +71,19 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } } - - internal ConversionRunner compatibleMemLayoutRunner; - internal ConversionRunner permutedRunnerRgbaToArgb; + internal ConversionRunner CompatibleMemLayoutRunner; - [Params( - 256, - 2048 - )] + internal ConversionRunner PermutedRunnerRgbaToArgb; + + [Params(256, 2048)] public int Count { get; set; } [GlobalSetup] public void Setup() { - this.compatibleMemLayoutRunner = new ConversionRunner(this.Count); - this.permutedRunnerRgbaToArgb = new ConversionRunner(this.Count); + this.CompatibleMemLayoutRunner = new ConversionRunner(this.Count); + this.PermutedRunnerRgbaToArgb = new ConversionRunner(this.Count); } } @@ -93,26 +92,26 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark(Baseline = true)] public void ByRef() { - this.compatibleMemLayoutRunner.RunByRefConversion(); + this.CompatibleMemLayoutRunner.RunByRefConversion(); } [Benchmark] public void ByVal() { - this.compatibleMemLayoutRunner.RunByValConversion(); + this.CompatibleMemLayoutRunner.RunByValConversion(); } [Benchmark] public void FromBytes() { - this.compatibleMemLayoutRunner.RunFromBytesConversion(); + 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]); + ref Rgba32 sBase = ref this.CompatibleMemLayoutRunner.Source[0]; + ref Rgba32 dBase = ref Unsafe.As(ref this.CompatibleMemLayoutRunner.Dest[0]); for (int i = 0; i < this.Count; i++) { @@ -120,12 +119,12 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // 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 | + /* 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 | */ } public class PixelConversion_ConvertFromRgba32_Permuted_RgbaToArgb : PixelConversion_ConvertFromRgba32 @@ -133,26 +132,26 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark(Baseline = true)] public void ByRef() { - this.permutedRunnerRgbaToArgb.RunByRefConversion(); + this.PermutedRunnerRgbaToArgb.RunByRefConversion(); } [Benchmark] public void ByVal() { - this.permutedRunnerRgbaToArgb.RunByValConversion(); + this.PermutedRunnerRgbaToArgb.RunByValConversion(); } [Benchmark] public void FromBytes() { - this.permutedRunnerRgbaToArgb.RunFromBytesConversion(); + this.PermutedRunnerRgbaToArgb.RunFromBytesConversion(); } [Benchmark] public void InlineShuffle() { - ref Rgba32 sBase = ref this.permutedRunnerRgbaToArgb.source[0]; - ref TestArgb dBase = ref this.permutedRunnerRgbaToArgb.dest[0]; + ref Rgba32 sBase = ref this.PermutedRunnerRgbaToArgb.Source[0]; + ref TestArgb dBase = ref this.PermutedRunnerRgbaToArgb.Dest[0]; for (int i = 0; i < this.Count; i++) { @@ -169,8 +168,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [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]); + ref uint sBase = ref Unsafe.As(ref this.PermutedRunnerRgbaToArgb.Source[0]); + ref uint dBase = ref Unsafe.As(ref this.PermutedRunnerRgbaToArgb.Dest[0]); for (int i = 0; i < this.Count; i++) { @@ -182,8 +181,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark] public void PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer() { - Span source = MemoryMarshal.Cast(this.permutedRunnerRgbaToArgb.source); - Span dest = MemoryMarshal.Cast(this.permutedRunnerRgbaToArgb.dest); + Span source = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Source); + Span dest = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Dest); source.CopyTo(dest); ref uint dBase = ref MemoryMarshal.GetReference(dest); @@ -195,21 +194,23 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // 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 |// + /* + 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 d0c8a3045c..6bb3f38be3 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; @@ -8,12 +9,13 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertFromVector4 { [StructLayout(LayoutKind.Sequential)] - struct TestRgbaVector : ITestPixel + private struct TestRgbaVector : ITestPixel { private Vector4 v; @@ -39,13 +41,17 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } 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(); } - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] dest; @@ -100,7 +106,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion this.nonVectorRunner = new ConversionRunner(this.Count); this.vectorRunner = new ConversionRunner(this.Count); } - + [Benchmark(Baseline = true)] public void VectorByRef() { @@ -124,7 +130,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { this.nonVectorRunner.RunByValConversion(); } - } /* @@ -135,8 +140,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion * 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 d205e1e63e..f922559f77 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs @@ -1,7 +1,7 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; @@ -11,14 +11,14 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { /// /// When implementing TPixel --> Rgba32 style conversions on IPixel, should which API should we prefer? - /// 1. Rgba32 ToRgba32(); + /// 1. Rgba32 ToRgba32(); /// OR /// 2. void CopyToRgba32(ref Rgba32 dest); /// ? /// public class PixelConversion_ConvertToRgba32 { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /* * Results: - * + * * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | * --------------- |------ |------------ |---------- |------- |-------------- | * CompatibleRetval | 128 | 89.7358 ns | 2.2389 ns | 1.00 | 0.00 | @@ -109,4 +109,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion * 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 fff9ae9bc7..1a228e3bf8 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -8,7 +11,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -110,4 +113,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // 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 2bc3ee9716..dc7dea504c 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs @@ -1,15 +1,16 @@ -using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToVector4 { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -80,4 +81,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // 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 c6daf0f1e2..c1c4d6e0d4 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -7,7 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToVector4_AsPartOfCompositeOperation { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -92,4 +95,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // 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_Rgba32_To_Argb32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs index 40893914e1..7c51e05476 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -39,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.NoInlining)] private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -74,7 +77,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - [Benchmark] public void Default_Group4() { @@ -98,7 +100,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d2, 1).FromRgba32(s3); } } - + [Benchmark] public void BitOps() { @@ -137,6 +139,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The argb value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToArgb32(uint packedRgba) { @@ -148,6 +151,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The bgra value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToBgra32(uint packedRgba) { @@ -173,4 +177,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // 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 3b288260c5..8cb9fb9845 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -10,8 +13,8 @@ using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - //[MonoJob] - //[RyuJitX64Job] + // [MonoJob] + // [RyuJitX64Job] public class PixelConversion_Rgba32_To_Bgra32 { private Rgba32[] source; @@ -19,19 +22,22 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion private Bgra32[] dest; [StructLayout(LayoutKind.Sequential)] - struct Tuple4OfUInt32 + private struct Tuple4OfUInt32 { - public uint V0, V1, V2, V3; + private uint v0; + private uint v1; + private uint v2; + private uint 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); + 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; } @@ -57,7 +63,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.NoInlining)] private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -81,7 +87,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion ref Rgba32 sBase = ref this.source[0]; ref Bgra32 dBase = ref this.dest[0]; - for (int i = 0; i < this.Count; i+=2) + 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); @@ -115,10 +121,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d2, 1).FromRgba32(s3); } } - + [MethodImpl(MethodImplOptions.NoInlining)] private static void Group4GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -141,13 +147,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void Default_Group4_Generic() { Group4GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); } - //[Benchmark] + // [Benchmark] public void Default_Group8() { ref Rgba32 sBase = ref this.source[0]; @@ -174,7 +180,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion 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); @@ -214,7 +219,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void Bitops_SingleTuple() { ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); @@ -225,11 +230,11 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [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]); + ref Octet sBase = ref Unsafe.As>(ref this.source[0]); + ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); for (int i = 0; i < this.Count / 8; i++) { @@ -237,24 +242,26 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } +#pragma warning disable SA1132 // Do not combine fields [StructLayout(LayoutKind.Sequential)] - struct B + private struct B { - public uint tmp2, tmp5, tmp8, tmp11, tmp14, tmp17, tmp20, tmp23; + public uint Tmp2, Tmp5, Tmp8, Tmp11, Tmp14, Tmp17, Tmp20, Tmp23; } [StructLayout(LayoutKind.Sequential)] - struct C + private struct C { - public uint tmp3, tmp6, tmp9, tmp12, tmp15, tmp18, tmp21, tmp24; + public uint Tmp3, Tmp6, Tmp9, Tmp12, Tmp15, Tmp18, Tmp21, Tmp24; } +#pragma warning restore SA1132 // Do not combine fields [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BitopsSimdImpl(ref Octet.OfUInt32 s, ref Octet.OfUInt32 d) + private static void BitopsSimdImpl(ref Octet s, ref Octet d) { - Vector sVec = Unsafe.As>(ref s); - Vector aMask = new Vector(0xFF00FF00); - Vector bMask = new Vector(0x00FF00FF); + Vector sVec = Unsafe.As, Vector>(ref s); + var aMask = new Vector(0xFF00FF00); + var bMask = new Vector(0x00FF00FF); Vector aa = sVec & aMask; Vector bb = sVec & bMask; @@ -263,22 +270,22 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion C c = default; - 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); + 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 cc = Unsafe.As>(ref c); Vector dd = aa + cc; - d = Unsafe.As, Octet.OfUInt32>(ref dd); + d = Unsafe.As, Octet>(ref dd); } - //[Benchmark] + // [Benchmark] public void BitOps_Group2() { ref uint sBase = ref Unsafe.As(ref this.source[0]); @@ -294,7 +301,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d0, 1) = FromRgba32.ToBgra32(s1); } } - + [Benchmark] public void BitOps_GroupAsULong() { @@ -315,7 +322,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void BitOps_GroupAsULong_V2() { ref ulong sBase = ref Unsafe.As(ref this.source[0]); @@ -350,6 +357,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The argb value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToArgb32(uint packedRgba) { @@ -361,6 +369,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The bgra value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToBgra32(uint packedRgba) { @@ -376,7 +385,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // RESULTS: // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | // -------------------- |------ |---------:|----------:|----------:|-------:|---------:| @@ -388,4 +396,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // 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 76de794eca..958495c3cb 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,9 +10,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { [StructLayout(LayoutKind.Sequential)] - struct TestArgb : ITestPixel + public struct TestArgb : ITestPixel { - public byte A, R, G, B; + public byte A; + public byte R; + public byte G; + public byte B; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FromRgba32(Rgba32 p) @@ -86,4 +92,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion 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 36d5f3e5b9..ff11585e72 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,9 +10,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { [StructLayout(LayoutKind.Sequential)] - struct TestRgba : ITestPixel + public struct TestRgba : ITestPixel { - public byte R, G, B, A; + public byte R; + public byte G; + public byte B; + public byte A; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FromRgba32(Rgba32 source) @@ -57,7 +63,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { - return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); + return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -68,4 +74,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion dest = tmp; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/StructCasting.cs b/tests/ImageSharp.Benchmarks/General/StructCasting.cs index bed68b54a1..92f16e16c1 100644 --- a/tests/ImageSharp.Benchmarks/General/StructCasting.cs +++ b/tests/ImageSharp.Benchmarks/General/StructCasting.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General diff --git a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs index ae11806f32..ef1b3c98d2 100644 --- a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs +++ b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs @@ -1,10 +1,13 @@ -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; - using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General +{ /// /// 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! @@ -16,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General private static readonly Vector4 C = new Vector4(5.6f); private static readonly Vector4 D = new Vector4(7.8f); - private Random random = null; + private Random random; private Vector4 parameter; @@ -28,8 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General this.GetRandomFloat(), this.GetRandomFloat(), this.GetRandomFloat(), - this.GetRandomFloat() - ); + this.GetRandomFloat()); } [Benchmark(Baseline = true)] @@ -37,10 +39,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General { 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); + Vector4 x = (p * A / B) + (p * C / D); + Vector4 y = (p / A * B) + (p / C * D); + var z = Vector4.Min(p, A); + var w = Vector4.Max(p, B); return x + y + z + w; } @@ -49,13 +51,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General { 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)); + 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)); + var z = Vector4.Min(p, new Vector4(1.2f)); + var 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 d189411b5d..651ca51ba0 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -1,9 +1,12 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class BitwiseOrUInt32 { private uint[] input; @@ -24,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization for (int i = 0; i < this.InputSize; i++) { - this.input[i] = (uint) i; + this.input[i] = (uint)i; } } @@ -41,14 +44,14 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark] public void Simd() { - Vector v = new Vector(this.testValue); + var v = new Vector(this.testValue); - for (int i = 0; i < this.input.Length; i+=Vector.Count) + for (int i = 0; i < this.input.Length; i += Vector.Count) { - Vector a = new Vector(this.input, i); + var 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 6378467478..4c981bf5c7 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -1,9 +1,12 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class DivFloat { private float[] input; @@ -41,14 +44,14 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark] public void Simd() { - Vector v = new Vector(this.testValue); + var v = new Vector(this.testValue); for (int i = 0; i < this.input.Length; i += Vector.Count) { - Vector a = new Vector(this.input, i); + 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 49ada2f368..36a45a4828 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -1,9 +1,12 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. - using BenchmarkDotNet.Attributes; +using System.Numerics; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class DivUInt32 { private uint[] input; @@ -32,6 +35,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization public void Standard() { uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { this.result[i] = this.input[i] / v; @@ -41,14 +45,15 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark] public void Simd() { - Vector v = new Vector(this.testValue); + var v = new Vector(this.testValue); for (int i = 0; i < this.input.Length; i += Vector.Count) { - Vector a = new Vector(this.input, i); + 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/Divide.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs index b384295570..09d14963b4 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs @@ -1,11 +1,15 @@ -namespace ImageSharp.Benchmarks.General.Vectorization -{ - using System; - using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace ImageSharp.Benchmarks.General.Vectorization +{ +#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 { protected override float GetTestValue() => 42; @@ -54,7 +58,7 @@ namespace ImageSharp.Benchmarks.General.Vectorization { protected override short GetTestValue() => 42; - 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}); + 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() @@ -66,4 +70,4 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } -} \ 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 8c5f568181..595df8a59a 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -1,9 +1,12 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class MulFloat { private float[] input; @@ -41,11 +44,11 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark] public void SimdMultiplyByVector() { - Vector v = new Vector(this.testValue); + var v = new Vector(this.testValue); for (int i = 0; i < this.input.Length; i += Vector.Count) { - Vector a = new Vector(this.input, i); + var a = new Vector(this.input, i); a = a * v; a.CopyTo(this.result, i); } @@ -58,10 +61,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization for (int i = 0; i < this.input.Length; i += Vector.Count) { - Vector a = new Vector(this.input, i); + 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/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs index 913d4ce3c7..a405f0953f 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -1,9 +1,12 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class MulUInt32 { private uint[] input; @@ -41,14 +44,14 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark] public void Simd() { - Vector v = new Vector(this.testValue); + var v = new Vector(this.testValue); for (int i = 0; i < this.input.Length; i += Vector.Count) { - Vector a = new Vector(this.input, i); + 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/Multiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs index d1b70f21b3..ebdbddd54d 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs @@ -1,9 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using BenchmarkDotNet.Attributes; + namespace ImageSharp.Benchmarks.General.Vectorization { - using System.Numerics; - using BenchmarkDotNet.Attributes; - +#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 { protected override uint GetTestValue() => 42u; @@ -47,4 +52,4 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } -} \ 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 23f13c89b7..3f5a2ce139 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -56,4 +59,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization 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 f3853a8b1f..cdf8ad04fa 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -1,10 +1,13 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; - using System.Runtime.InteropServices; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.InteropServices; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class ReinterpretUInt32AsFloat { private uint[] input; @@ -15,22 +18,20 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization public int InputSize { get; set; } [StructLayout(LayoutKind.Explicit)] - struct UIntFloatUnion + private struct UIntFloatUnion { [FieldOffset(0)] - public float f; + public float F; [FieldOffset(0)] - public uint i; + 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++) { this.input[i] = (uint)i; @@ -40,11 +41,11 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark(Baseline = true)] public void Standard() { - UIntFloatUnion u = default(UIntFloatUnion); + UIntFloatUnion u = default; for (int i = 0; i < this.input.Length; i++) { - u.i = this.input[i]; - this.result[i] = u.f; + u.I = this.input[i]; + this.result[i] = u.F; } } @@ -53,10 +54,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { for (int i = 0; i < this.input.Length; i += Vector.Count) { - Vector a = new Vector(this.input, i); - Vector b = Vector.AsVectorSingle(a); + var a = new Vector(this.input, i); + var 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 76987dbd21..8a61f49c4e 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs @@ -1,10 +1,13 @@ -namespace ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; - using System.Runtime.CompilerServices; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. - using BenchmarkDotNet.Attributes; +using System.Numerics; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; + +namespace ImageSharp.Benchmarks.General.Vectorization +{ public abstract class SIMDBenchmarkBase where T : struct { @@ -16,14 +19,13 @@ namespace ImageSharp.Benchmarks.General.Vectorization protected Vector testVector; - protected virtual T GetTestValue() => default(T); + protected virtual T GetTestValue() => default; protected virtual Vector GetTestVector() => new Vector(this.GetTestValue()); - [Params(32)] public int InputSize { get; set; } - + [GlobalSetup] public virtual void Setup() { @@ -64,7 +66,5 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } - - } -} \ 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 ca85a350cc..15c4b8c054 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -27,15 +30,11 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization 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 magicInt = new Vector(1191182336); // reinterpreted value of 32768.0f var mask = new Vector(255); 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); var vi = Vector.AsVectorUInt32(df); @@ -67,7 +66,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization Unsafe.Add(ref bf, i) = v; } } - + [Benchmark] public void StandardSimdFromInt() { @@ -87,15 +86,12 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } - [Benchmark] public void StandardSimdFromInt_RefCast() { int n = Count / Vector.Count; 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++) @@ -110,4 +106,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ 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 017f58ef74..d9edc91f99 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { using System; @@ -5,7 +8,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; - using SixLabors.Memory; /// /// This benchmark compares different methods for fetching memory data into @@ -19,13 +21,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Params(64)] public int InputSize { get; set; } - + [GlobalSetup] public void Setup() { this.data = new float[this.InputSize]; this.testValue = 42; - + for (int i = 0; i < this.InputSize; i++) { this.data[i] = i; @@ -45,11 +47,11 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark] public void FetchWithVectorConstructor() { - Vector v = new Vector(this.testValue); + var v = new Vector(this.testValue); for (int i = 0; i < this.data.Length; i += Vector.Count) { - Vector a = new Vector(this.data, i); + var a = new Vector(this.data, i); a = a * v; a.CopyTo(this.data, i); } @@ -58,7 +60,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark] public void FetchWithUnsafeCast() { - Vector v = new Vector(this.testValue); + var v = new Vector(this.testValue); ref Vector start = ref Unsafe.As>(ref this.data[0]); int n = this.InputSize / Vector.Count; @@ -77,7 +79,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Benchmark] public void FetchWithUnsafeCastNoTempVector() { - Vector v = new Vector(this.testValue); + var v = new Vector(this.testValue); ref Vector start = ref Unsafe.As>(ref this.data[0]); int n = this.InputSize / Vector.Count; @@ -107,4 +109,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ 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 2bc3af4c98..a0049f9848 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -28,8 +31,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { const int N = Count / 8; - ref Octet.OfByte sBase = ref Unsafe.As(ref this.source[0]); - ref Octet.OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); + ref Octet sBase = ref Unsafe.As>(ref this.source[0]); + ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); for (int i = 0; i < N; i++) { @@ -61,4 +64,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index f941203db1..eaab162ff2 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -1,28 +1,48 @@ - + + + - netcoreapp2.1;net461 + ImageSharp.Benchmarks Exe - True SixLabors.ImageSharp.Benchmarks - ImageSharp.Benchmarks - 7.3 - - - win7-x64 - false + netcoreapp3.1;netcoreapp2.1;net472 + false + + false + + + + - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + + diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index cdd56fa074..2b6657a22c 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,7 +13,7 @@ using SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.Benchmarks { - using CoreSize = SixLabors.Primitives.Size; + using CoreSize = SixLabors.ImageSharp.Size; public class PorterDuffBulkVsPixel : BenchmarkBase { @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks Span background, Span source, Span amount) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Benchmarks destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); } - PixelOperations.Instance.FromVector4(this.Configuration, destinationSpan, destination); + PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); } } @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Benchmarks Span background, Span source, Span amount) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); diff --git a/tests/ImageSharp.Benchmarks/Program.cs b/tests/ImageSharp.Benchmarks/Program.cs index 4dd63067ac..8080825d9f 100644 --- a/tests/ImageSharp.Benchmarks/Program.cs +++ b/tests/ImageSharp.Benchmarks/Program.cs @@ -1,15 +1,12 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// + +using System.Reflection; + +using BenchmarkDotNet.Running; namespace SixLabors.ImageSharp.Benchmarks { - using BenchmarkDotNet.Running; - - using SixLabors.ImageSharp.Formats; - using System.Reflection; - public class Program { /// diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs index 240a277cf0..a62b68557c 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs @@ -1,49 +1,44 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // 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 SDRectangle = System.Drawing.Rectangle; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks { - using System.Drawing; - using System.Drawing.Drawing2D; - - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Processing; - - using CoreSize = SixLabors.Primitives.Size; - + [Config(typeof(Config.ShortClr))] public class Crop : BenchmarkBase { [Benchmark(Baseline = true, Description = "System.Drawing Crop")] - public Size CropSystemDrawing() + public SDSize CropSystemDrawing() { - using (Bitmap source = new Bitmap(800, 800)) + using (var source = new Bitmap(800, 800)) + using (var destination = new Bitmap(100, 100)) + using (var graphics = Graphics.FromImage(destination)) { - using (Bitmap destination = new Bitmap(100, 100)) - { - using (Graphics 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); - } + 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; - } + return destination.Size; } } [Benchmark(Description = "ImageSharp Crop")] - public CoreSize CropResizeCore() + public Size CropResizeCore() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { image.Mutate(x => x.Crop(100, 100)); - return new CoreSize(image.Width, image.Height); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index 006d1b6391..ce2fa988c7 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// using SixLabors.ImageSharp.PixelFormats; @@ -12,8 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Processing; - - using CoreImage = ImageSharp.Image; + using CoreImage = SixLabors.ImageSharp.Image; public class DetectEdges : BenchmarkBase { @@ -40,17 +37,17 @@ namespace SixLabors.ImageSharp.Benchmarks [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)); + 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/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs new file mode 100644 index 0000000000..354d105e6e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks.Samplers +{ + [Config(typeof(Config.ShortClr))] + public class Diffuse + { + [Benchmark] + public Size DoDiffuse() + { + using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) + { + image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); + + return image.Size(); + } + } + + [Benchmark] + public Size DoDither() + { + using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) + { + 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/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs new file mode 100644 index 0000000000..62d5806037 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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, Color.White)) + { + image.Mutate(c => c.GaussianBlur()); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs deleted file mode 100644 index 729971548c..0000000000 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ /dev/null @@ -1,177 +0,0 @@ -// 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 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 (Image 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 (Image 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(TPixel); - 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) - Vector3 inverseW = new Vector3(1 - source.W); - Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); - Vector3 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 index 148b253281..63a85c757d 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Drawing; using System.Drawing.Drawing2D; +using System.Globalization; using BenchmarkDotNet.Attributes; @@ -14,24 +14,31 @@ using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Benchmarks { [Config(typeof(Config.ShortClr))] - public abstract class ResizeBenchmarkBase +#pragma warning disable SA1649 // File name should match first type name + public abstract class ResizeBenchmarkBase +#pragma warning restore SA1649 // File name should match first type name + where TPixel : unmanaged, IPixel { protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); - private Image sourceImage; + private Image sourceImage; private Bitmap sourceBitmap; - [Params(3032)] - public int SourceSize { get; set; } + [Params("3032-400")] + public virtual string SourceToDest { get; set; } - [Params(400)] - public int DestSize { get; set; } + protected int SourceSize { get; private set; } + + protected int DestSize { get; private set; } [GlobalSetup] - public void Setup() + public virtual void Setup() { - this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize); + 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); } @@ -65,83 +72,156 @@ namespace SixLabors.ImageSharp.Benchmarks [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); + public int ImageSharp_P8() => this.RunImageSharpResize(8); + */ protected int RunImageSharpResize(int maxDegreeOfParallelism) { this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; - using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) + using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) { return clone.Width; } } - protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); + 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 : ResizeBenchmarkBase + + public class Resize_Bicubic_Bgra32 : ResizeBenchmarkBase { - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) { ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); } - // RESULTS (2018 October): + // RESULTS (2019 April): // - // BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134 + // 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=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-IGUFBA : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 - // Job-DZFERG : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT + // 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 | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Scaled | ScaledSD | Allocated | - // ----------------------------------------- |-------- |----------- |--------- |----------:|----------:|----------:|-------:|---------:|----------:| - // SystemDrawing | Clr | 3032 | 400 | 101.13 ms | 18.659 ms | 1.0542 ms | 1.00 | 0.00 | 0 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | 3032 | 400 | 122.05 ms | 19.622 ms | 1.1087 ms | 1.21 | 0.01 | 21856 B | - // 'ImageSharp, MaxDegreeOfParallelism = 4' | Clr | 3032 | 400 | 41.34 ms | 54.841 ms | 3.0986 ms | 0.41 | 0.03 | 28000 B | - // 'ImageSharp, MaxDegreeOfParallelism = 8' | Clr | 3032 | 400 | 31.68 ms | 12.782 ms | 0.7222 ms | 0.31 | 0.01 | 28256 B | - // | | | | | | | | | | - // SystemDrawing | Core | 3032 | 400 | 100.37 ms | 18.479 ms | 1.0441 ms | 1.00 | 0.00 | 0 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | 3032 | 400 | 73.03 ms | 10.540 ms | 0.5955 ms | 0.73 | 0.01 | 21368 B | - // 'ImageSharp, MaxDegreeOfParallelism = 4' | Core | 3032 | 400 | 22.59 ms | 4.863 ms | 0.2748 ms | 0.23 | 0.00 | 25220 B | - // 'ImageSharp, MaxDegreeOfParallelism = 8' | Core | 3032 | 400 | 21.10 ms | 23.362 ms | 1.3200 ms | 0.21 | 0.01 | 25539 B | + // 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 : ResizeBenchmarkBase + public class Resize_BicubicCompand_Rgba32 : ResizeBenchmarkBase { - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) { ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); } - // RESULTS (2018 October): + // RESULTS (2019 April): // - // BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134 + // 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=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-IGUFBA : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 - // Job-DZFERG : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT + // 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 | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Scaled | ScaledSD | Allocated | - // ----------------------------------------- |-------- |----------- |--------- |----------:|----------:|----------:|-------:|---------:|----------:| - // SystemDrawing | Clr | 3032 | 400 | 100.63 ms | 13.864 ms | 0.7833 ms | 1.00 | 0.00 | 0 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | 3032 | 400 | 156.83 ms | 28.631 ms | 1.6177 ms | 1.56 | 0.02 | 21856 B | - // 'ImageSharp, MaxDegreeOfParallelism = 4' | Clr | 3032 | 400 | 53.43 ms | 38.493 ms | 2.1749 ms | 0.53 | 0.02 | 28512 B | - // 'ImageSharp, MaxDegreeOfParallelism = 8' | Clr | 3032 | 400 | 38.47 ms | 11.969 ms | 0.6763 ms | 0.38 | 0.01 | 28000 B | - // | | | | | | | | | | - // SystemDrawing | Core | 3032 | 400 | 99.87 ms | 23.459 ms | 1.3255 ms | 1.00 | 0.00 | 0 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | 3032 | 400 | 108.19 ms | 38.562 ms | 2.1788 ms | 1.08 | 0.02 | 21368 B | - // 'ImageSharp, MaxDegreeOfParallelism = 4' | Core | 3032 | 400 | 36.21 ms | 53.802 ms | 3.0399 ms | 0.36 | 0.03 | 25300 B | - // 'ImageSharp, MaxDegreeOfParallelism = 8' | Core | 3032 | 400 | 26.52 ms | 2.173 ms | 0.1228 ms | 0.27 | 0.00 | 25589 B | + // 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 | } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs index f898576af0..94594c7875 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs @@ -1,7 +1,9 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// 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 { @@ -11,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoRotate() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) { image.Mutate(x => x.Rotate(37.5F)); @@ -21,25 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -// 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 +// #### 21th 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-HOGSNT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-FKDHXC : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-ODABAZ : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |--------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:| +// | DoRotate | .NET 4.7.2 | 28.77 ms | 3.304 ms | 0.181 ms | - | - | - | 6.5 KB | +// | DoRotate | .NET Core 2.1 | 16.27 ms | 1.044 ms | 0.057 ms | - | - | - | 5.25 KB | +// | DoRotate | .NET Core 3.1 | 17.12 ms | 4.352 ms | 0.239 ms | - | - | - | 6.57 KB | diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs index 84819750af..2758bed7a5 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs @@ -1,7 +1,9 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors. +// 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 { @@ -11,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoSkew() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) { image.Mutate(x => x.Skew(20, 10)); @@ -21,25 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -// 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 +// #### 21th 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-VKKTMF : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-KTVRKR : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-EONWDB : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:| +// | DoSkew | .NET 4.7.2 | 24.60 ms | 33.971 ms | 1.862 ms | - | - | - | 6.5 KB | +// | DoSkew | .NET Core 2.1 | 12.13 ms | 2.256 ms | 0.124 ms | - | - | - | 5.21 KB | +// | DoSkew | .NET Core 3.1 | 12.83 ms | 1.442 ms | 0.079 ms | - | - | - | 6.57 KB | diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj deleted file mode 100644 index cb286cc286..0000000000 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - Exe - net461 - 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 deleted file mode 100644 index 02d4f80c55..0000000000 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ /dev/null @@ -1,74 +0,0 @@ -// -// 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(2000, 2000); - } - - 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 deleted file mode 100644 index a10fc12fe7..0000000000 --- a/tests/ImageSharp.Sandbox46/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index b05afb8538..0000000000 --- a/tests/ImageSharp.Sandbox46/README.md +++ /dev/null @@ -1,24 +0,0 @@ -## 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/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj new file mode 100644 index 0000000000..7c80316930 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -0,0 +1,31 @@ + + + + + ImageSharp.Tests.ProfilingSandbox + A cross-platform library for processing of image files written in C# + Exe + false + SixLabors.ImageSharp.Tests.ProfilingSandbox + win7-x64 + netcoreapp3.1;netcoreapp2.1;net472 + SixLabors.ImageSharp.Tests.ProfilingSandbox.Program + + false + + + + + + + + + + + + + + + + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs new file mode 100644 index 0000000000..8155c6a584 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Tests.Formats.Jpg; +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) + { + // 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]; + int executionCount = (int)data[1]; + benchmarks.DecodeJpeg(fileName, executionCount); + } + + Console.WriteLine("DONE."); + } + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/README.md b/tests/ImageSharp.Tests.ProfilingSandbox/README.md new file mode 100644 index 0000000000..43fdab9ef6 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/README.md @@ -0,0 +1,2 @@ +## 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.Sandbox46/app.config b/tests/ImageSharp.Tests.ProfilingSandbox/app.config similarity index 100% rename from tests/ImageSharp.Sandbox46/app.config rename to tests/ImageSharp.Tests.ProfilingSandbox/app.config diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 974099991d..6031227bd6 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -1,154 +1,163 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; 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 +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Advanced { - using System.Buffers; - - using SixLabors.Memory; - public class AdvancedImageExtensionsTests { - public class GetPixelMemory + public class GetPixelMemoryGroup { [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void WhenMemoryIsOwned(TestImageProvider provider) - where TPixel : struct, IPixel + [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 { - using (Image image0 = provider.GetImage()) - { - var targetBuffer = new TPixel[image0.Width * image0.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - // Act: - Memory memory = image0.GetPixelMemory(); + using Image image = provider.GetImage(); - // Assert: - Assert.Equal(image0.Width * image0.Height, memory.Length); - memory.Span.CopyTo(targetBuffer); + // Act: + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); - using (Image image1 = provider.GetImage()) - { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); - } - } + // Assert: + VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size()); } + [Theory] + [WithBlankImages(16, 16, PixelTypes.Rgba32)] + public void OwnedMemory_DestructiveMutate_ShouldInvalidateMemoryGroup(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); + Memory memory = memoryGroup.Single(); + + image.Mutate(c => c.Resize(8, 8)); + + Assert.False(memoryGroup.IsValid); + Assert.ThrowsAny(() => _ = memoryGroup.First()); + Assert.ThrowsAny(() => _ = memory.Span); + } [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 + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Bgr24)] + public void ConsumedMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image0 = provider.GetImage()) - { - var targetBuffer = new TPixel[image0.Width * image0.Height]; - image0.GetPixelSpan().CopyTo(targetBuffer); + using Image image0 = provider.GetImage(); + var targetBuffer = new TPixel[image0.Width * image0.Height]; - var managerOfExeternalMemory = new TestMemoryManager(targetBuffer); + Assert.True(image0.TryGetSinglePixelSpan(out Span sourceBuffer)); - Memory externalMemory = managerOfExeternalMemory.Memory; + sourceBuffer.CopyTo(targetBuffer); - 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])); + var managerOfExternalMemory = new TestMemoryManager(targetBuffer); - image0.ComparePixelBufferTo(internalMemory.Span); - } + Memory externalMemory = managerOfExternalMemory.Memory; - // Make sure externalMemory works after destruction: - image0.ComparePixelBufferTo(externalMemory.Span); + using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) + { + VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size()); } + + // Make sure externalMemory works after destruction: + VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size()); } - } - [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void GetPixelRowMemory(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) + private static void VerifyMemoryGroupDataMatchesTestPattern( + TestImageProvider provider, + IMemoryGroup memoryGroup, + Size size) + where TPixel : unmanaged, IPixel { - var targetBuffer = new TPixel[image.Width * image.Height]; + Assert.True(memoryGroup.IsValid); + Assert.Equal(size.Width * size.Height, memoryGroup.TotalLength); + Assert.True(memoryGroup.BufferLength % size.Width == 0); - // Act: - for (int y = 0; y < image.Height; y++) + int cnt = 0; + for (MemoryGroupIndex i = memoryGroup.MaxIndex(); i < memoryGroup.MaxIndex(); i += 1, cnt++) { - Memory rowMemory = image.GetPixelRowMemory(y); - rowMemory.Span.CopyTo(targetBuffer.AsSpan(image.Width * y)); - } + int y = cnt / size.Width; + int x = cnt % size.Width; - // Assert: - using (Image image1 = provider.GetImage()) - { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); + TPixel expected = provider.GetExpectedBasicTestPatternPixelAt(x, y); + TPixel actual = memoryGroup.GetElementAt(i); + Assert.Equal(expected, actual); } } } [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void GetPixelRowSpan(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] + public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - var targetBuffer = new TPixel[image.Width * image.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + using Image image = provider.GetImage(); + + for (int y = 0; y < image.Height; y++) + { // Act: - for (int y = 0; y < image.Height; y++) - { - Span rowMemory = image.GetPixelRowSpan(y); - rowMemory.CopyTo(targetBuffer.AsSpan(image.Width * y)); - } + Memory rowMemory = image.GetPixelRowMemory(y); + Span span = rowMemory.Span; // Assert: - using (Image image1 = provider.GetImage()) + for (int x = 0; x < image.Width; x++) { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); + Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), span[x]); } } } - #pragma warning disable 0618 + [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.GetPixelRowMemory(3); + Memory memory10 = image.GetPixelRowMemory(10); + + image.Mutate(c => c.Resize(8, 8)); + + Assert.ThrowsAny(() => _ = memory3.Span); + Assert.ThrowsAny(() => _ = memory10.Span); + } [Theory] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public unsafe void DangerousGetPinnableReference_CopyToBuffer(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + [WithBlankImages(100, 111, PixelTypes.Rgba32)] + [WithBlankImages(400, 600, PixelTypes.Rgba32)] + public void GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - var targetBuffer = new TPixel[image.Width * image.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - 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); - } + using Image image = provider.GetImage(); - image.ComparePixelBufferTo(targetBuffer); - } + Memory memory = image.GetPixelRowMemory(image.Height - 1); + Span span = image.GetPixelRowSpan(image.Height - 1); + + Assert.True(span == memory.Span); } } } diff --git a/tests/ImageSharp.Tests/Advanced/AotCompilerTests.cs b/tests/ImageSharp.Tests/Advanced/AotCompilerTests.cs deleted file mode 100644 index f6397dbd09..0000000000 --- a/tests/ImageSharp.Tests/Advanced/AotCompilerTests.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 deleted file mode 100644 index 7adbefb346..0000000000 --- a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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 new file mode 100644 index 0000000000..38b94f486c --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ColorTests + { + public class CastFrom + { + [Fact] + public void Rgba64() + { + var source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + Color color = source; + + // Assert: + Rgba64 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + var source = new Rgba32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Rgba32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + var source = new Argb32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Argb32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + var source = new Bgra32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Bgra32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + var source = new Rgb24(1, 22, 231); + + // Act: + Color color = source; + + // Assert: + Rgb24 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + var source = new Bgr24(1, 22, 231); + + // Act: + Color color = 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 new file mode 100644 index 0000000000..ee1820de77 --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ColorTests + { + public class CastTo + { + [Fact] + public void Rgba64() + { + var source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + var color = new Color(source); + + // Assert: + Rgba64 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + var source = new Rgba32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Rgba32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + var source = new Argb32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Argb32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + var source = new Bgra32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Bgra32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + var source = new Rgb24(1, 22, 231); + + // Act: + var color = new Color(source); + + // Assert: + Rgb24 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + var source = new Bgr24(1, 22, 231); + + // Act: + var color = new Color(source); + + // Assert: + Bgr24 data = color; + Assert.Equal(source, data); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs new file mode 100644 index 0000000000..89276014b0 --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ColorTests + { + public class ConstructFrom + { + [Fact] + public void Rgba64() + { + var source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + var color = new Color(source); + + // Assert: + Rgba64 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + var source = new Rgba32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Rgba32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + var source = new Argb32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Argb32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + var source = new Bgra32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Bgra32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + var source = new Rgb24(1, 22, 231); + + // Act: + var color = new Color(source); + + // Assert: + Rgb24 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + var source = new Bgr24(1, 22, 231); + + // Act: + var color = new Color(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 new file mode 100644 index 0000000000..808a3ecbbb --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -0,0 +1,221 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ColorTests + { + [Fact] + public void WithAlpha() + { + var c1 = Color.FromRgba(111, 222, 55, 255); + Color c2 = c1.WithAlpha(0.5f); + + var expected = new Rgba32(111, 222, 55, 128); + + Assert.Equal(expected, (Rgba32)c2); + } + + [Fact] + public void Equality_WhenTrue() + { + Color c1 = new Rgba64(100, 2000, 3000, 40000); + Color c2 = new Rgba64(100, 2000, 3000, 40000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1 == c2); + Assert.False(c1 != c2); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + Color c1 = new Rgba64(100, 2000, 3000, 40000); + Color c2 = new Rgba64(101, 2000, 3000, 40000); + Color c3 = new Rgba64(100, 2000, 3000, 40001); + + 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)); + } + + [Fact] + public void ToHex() + { + string expected = "ABCD1234"; + var color = Color.ParseHex(expected); + string actual = color.ToHex(); + + Assert.Equal(expected, actual); + } + + [Fact] + public void WebSafePalette_IsCorrect() + { + Rgba32[] actualPalette = Color.WebSafePalette.ToArray().Select(c => (Rgba32)c).ToArray(); + + for (int i = 0; i < ReferencePalette.WebSafeColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WebSafeColors[i], actualPalette[i]); + } + } + + [Fact] + public void WernerPalette_IsCorrect() + { + Rgba32[] actualPalette = Color.WernerPalette.ToArray().Select(c => (Rgba32)c).ToArray(); + + for (int i = 0; i < ReferencePalette.WernerColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WernerColors[i], actualPalette[i]); + } + } + + public class FromHex + { + [Fact] + public void ShortHex() + { + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("#fff")); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("fff")); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)Color.ParseHex("000f")); + } + + [Fact] + public void TryShortHex() + { + Assert.True(Color.TryParseHex("#fff", out Color actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); + + Assert.True(Color.TryParseHex("fff", out actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); + + Assert.True(Color.TryParseHex("000f", out actual)); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)actual); + } + + [Fact] + public void LeadingPoundIsOptional() + { + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("#008080")); + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("008080")); + } + + [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 FromString + { + [Fact] + public void ColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) + { + Rgba32 expected = ReferencePalette.ColorNames[name]; + Assert.Equal(expected, (Rgba32)Color.Parse(name)); + Assert.Equal(expected, (Rgba32)Color.Parse(name.ToLowerInvariant())); + Assert.Equal(expected, (Rgba32)Color.Parse(expected.ToHex())); + } + } + + [Fact] + public void TryColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) + { + Rgba32 expected = ReferencePalette.ColorNames[name]; + + Assert.True(Color.TryParse(name, out Color actual)); + Assert.Equal(expected, (Rgba32)actual); + + Assert.True(Color.TryParse(name.ToLowerInvariant(), out actual)); + Assert.Equal(expected, (Rgba32)actual); + + Assert.True(Color.TryParse(expected.ToHex(), out actual)); + Assert.Equal(expected, (Rgba32)actual); + } + } + + [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 new file mode 100644 index 0000000000..8e74ab59c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ReferencePalette.cs @@ -0,0 +1,432 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +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 Dictionary(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/Colorspaces/CieLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs index dbc07b916e..90d804b411 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -32,15 +32,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces 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.True(new CieLab(1, 0, 1) != default(CieLab)); + Assert.False(new CieLab(1, 0, 1) == default(CieLab)); 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(new CieLab(1, 0, 1) == default(CieLab)); 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 index 90c2c22446..f0e97f614f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs index a6a5fa32ad..d69eac50e5 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs index dbf64cb1d0..c76626a0e8 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs index 42ace9dbed..31ad79b3d9 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; namespace SixLabors.ImageSharp.Tests.Colorspaces @@ -29,15 +29,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces 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.True(new CieXyChromaticityCoordinates(1, 0) != default(CieXyChromaticityCoordinates)); + Assert.False(new CieXyChromaticityCoordinates(1, 0) == default(CieXyChromaticityCoordinates)); 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(new CieXyChromaticityCoordinates(1, 0) == default(CieXyChromaticityCoordinates)); 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 index 88196034bf..a2549e41ec 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs index 3c77f132e3..d5166cc83a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs b/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs index dbf3fe6d8a..76f5bb5487 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs index 91cacfe3f0..cff562e813 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; using System.Numerics; using SixLabors.ImageSharp.ColorSpaces.Companding; using Xunit; @@ -10,9 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding { /// - /// Tests various companding algorithms. Numbers are hand calculated from formulas online. - /// TODO: Oddly the formula for converting to/from Rec 2020 and 709 from Wikipedia seems to cause the value to - /// fail a round trip. They're large spaces so this is a surprise. More reading required!! + /// Tests various companding algorithms. Expanded numbers are hand calculated from formulas online. /// public class CompandingTests { @@ -24,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding const float input = .667F; float e = Rec2020Companding.Expand(input); float c = Rec2020Companding.Compress(e); - CompandingIsCorrectImpl(e, c, .4484759F, .3937096F); + CompandingIsCorrectImpl(e, c, .4484759F, input); } [Fact] @@ -33,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding const float input = .667F; float e = Rec709Companding.Expand(input); float c = Rec709Companding.Compress(e); - CompandingIsCorrectImpl(e, c, .4483577F, .3937451F); + CompandingIsCorrectImpl(e, c, .4483577F, input); } [Fact] @@ -42,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding const float input = .667F; float e = SRgbCompanding.Expand(input); float c = SRgbCompanding.Compress(e); - CompandingIsCorrectImpl(e, c, .40242353F, .667F); + CompandingIsCorrectImpl(e, c, .40242353F, input); } [Theory] @@ -53,7 +50,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => SRgbCompanding.Expand(v)).ToArray(); + 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); @@ -68,7 +73,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => SRgbCompanding.Compress(v)).ToArray(); + 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); @@ -82,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding const float input = .667F; float e = GammaCompanding.Expand(input, gamma); float c = GammaCompanding.Compress(e, gamma); - CompandingIsCorrectImpl(e, c, .41027668F, .667F); + CompandingIsCorrectImpl(e, c, .41027668F, input); } [Fact] @@ -91,7 +104,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding const float input = .667F; float e = LCompanding.Expand(input); float c = LCompanding.Compress(e); - CompandingIsCorrectImpl(e, c, .36236193F, .58908917F); + CompandingIsCorrectImpl(e, c, .36236193F, input); } private static void CompandingIsCorrectImpl(float e, float c, float expanded, float compressed) diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs index 57da2ff170..f0a6923627 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using SixLabors.ImageSharp.ColorSpaces.Conversion; namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { @@ -28,15 +28,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion IEqualityComparer, IEqualityComparer, IEqualityComparer, - IEqualityComparer + IEqualityComparer { - private readonly float Epsilon; + 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 ApproximateColorSpaceComparer(float epsilon = 1F) => this.epsilon = epsilon; /// public bool Equals(Rgb x, Rgb y) @@ -222,19 +222,19 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion public int GetHashCode(GammaWorkingSpace obj) => obj.GetHashCode(); /// - public bool Equals(RgbWorkingSpaceBase x, RgbWorkingSpaceBase y) + public bool Equals(RgbWorkingSpace x, RgbWorkingSpace y) { return this.Equals(x.WhitePoint, y.WhitePoint) && this.Equals(x.ChromaticityCoordinates, y.ChromaticityCoordinates); } /// - public int GetHashCode(RgbWorkingSpaceBase obj) => obj.GetHashCode(); + public int GetHashCode(RgbWorkingSpace obj) => obj.GetHashCode(); private bool Equals(float x, float y) { float d = x - y; - return d >= -this.Epsilon && d <= this.Epsilon; + 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 index 38c0c21bc9..6e68155bd1 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -92,4 +92,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs index 96628977fe..0c5341fd6e 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -80,4 +80,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs index 39011bb292..956a249f75 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs index f7dc365b81..9ecd7873d0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -76,4 +76,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs index 43300ab88c..f6a25d07da 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs index 4ab309fe14..4cda3a8f28 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs index e7ff34f494..7269475b56 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs index 844cda4760..ab4a0f44f0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs index 74ed180f38..7038843d38 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs index a3db00e804..afce3e4135 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs index fc202ccc96..5c7db62100 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs index 3e481d4f64..c9fe56d301 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs index 078ba44daf..9cf79e6a36 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs index a65f618835..087d39323b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -69,11 +69,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index 49990fb908..3b9678b403 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs index 924b45b4a0..19a200af0e 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs index 0991657310..2b0338d2f5 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs index a7a819d1f7..a1749097bc 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs index b83b861be8..fa90e59855 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs index 932fdc4105..667e3d7a7d 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs index 4d04418d99..7c08da633f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs index 3cdaa42792..1844026b0c 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs index 6829c62b50..ddbb09e85d 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -87,11 +87,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index 0c62ffcc31..715b282d05 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs index 3b41204f7c..b486c9614f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index bfc0d2ecf1..b866b6c2cf 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index f11b17fff3..681b295181 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index de2329c2ec..aa3d0424a1 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index 3a1bd10c41..798b4612bf 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index f3881f10f7..0e46c912c5 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index 644f4577bf..26417b7399 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index 41b9dba091..07372aca87 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index 5b36beaab9..818443ea50 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy 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 index da77378759..18728d0c7d 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index 96d14c98a6..015f95b656 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy 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 index 0339730945..11f5fd516f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index fb0e06e6bb..82a8478934 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy 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 index 5bbcd90875..4249205939 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy 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 index 1ee84ef2e5..2c0a9a6025 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy 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 index 49b99b7052..40a60e47c6 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs index 77f0c69699..5bf463562f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -63,17 +63,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz 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 index 24e134d732..ae5df51304 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -63,17 +63,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz 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 index 761b9851e3..336d5a5082 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs index 2b0350cea1..134a9529f1 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs index cd1c9f2c3e..447856c637 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // 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 index 8112f6a198..0591f60d2a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz 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 index 2fed3e9c55..1ad329eab6 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs index 75634eb51e..5f6a3030ba 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs index 9ea890f101..a255b9e242 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz 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 index dbb0c6e200..dcbaaf7e63 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs index 5fcc59090b..cdb6c67bf6 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs index 7ff80c170b..54505428e4 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs index 8017302059..de8ca44093 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs index 3464fdbbde..61f698a1ad 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs index 26af5ddd30..b5d97f4425 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs index dc40ee518e..eaceae2297 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs index 00569ced2e..fabfea7e26 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs index 326777f3c6..466b2d3c33 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 @@ -178,4 +177,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion 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 index 8a2cd1159e..d0b5cf99da 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs index b01e3a854c..fed76101d1 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs index 502df84133..279c57510c 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -65,13 +65,13 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion [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 var input = new Rgb(r, g, b); var expected = new Hsl(h, s, l); - Span inputSpan = new Rgb[5]; inputSpan.Fill(input); diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs index 9adc94af7c..abd190262a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs index 94879eee7a..f2d1f49724 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs index b1427f4d5f..ae7711e55a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,8 +13,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); public static readonly TheoryData WhitePoints = new TheoryData { - {CieLuv.DefaultWhitePoint, CieLab.DefaultWhitePoint}, - {CieLuv.DefaultWhitePoint, CieLuv.DefaultWhitePoint} + { CieLuv.DefaultWhitePoint, CieLab.DefaultWhitePoint }, + { CieLuv.DefaultWhitePoint, CieLuv.DefaultWhitePoint } }; [Theory] diff --git a/tests/ImageSharp.Tests/Colorspaces/HslTests.cs b/tests/ImageSharp.Tests/Colorspaces/HslTests.cs index 60cfa9761a..84fca1ac29 100644 --- a/tests/ImageSharp.Tests/Colorspaces/HslTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/HslTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs b/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs index d1d1d15c8a..6bb07867e7 100644 --- a/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs index 95261e1d98..2f97207db0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -32,8 +32,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces 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.True(new HunterLab(1, 0, 1) != default(HunterLab)); + Assert.False(new HunterLab(1, 0, 1) == default(HunterLab)); 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)); @@ -42,4 +42,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces 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 index ef42e68bcc..4639195b6d 100644 --- a/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs index 1b0939dc5c..a1c3081c6b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces const float l = 75F; const float m = -64F; const float s = 87F; - var Lms = new Lms(l, m, s); + var lms = new Lms(l, m, s); - Assert.Equal(l, Lms.L); - Assert.Equal(m, Lms.M); - Assert.Equal(s, Lms.S); + Assert.Equal(l, lms.L); + Assert.Equal(m, lms.M); + Assert.Equal(s, lms.S); } [Fact] @@ -32,8 +32,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces 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.True(new Lms(1, 0, 1) != default(Lms)); + Assert.False(new Lms(1, 0, 1) == default(Lms)); 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)); @@ -42,4 +42,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces 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 index 7987fbe9f2..788976fbff 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs index 5249b709b1..ca50e4a74f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs @@ -1,66 +1,65 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; 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); + 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)" }, - }; + { 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 index f3e6f88f49..d6a87a7311 100644 --- a/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; diff --git a/tests/ImageSharp.Tests/Common/ConstantsTests.cs b/tests/ImageSharp.Tests/Common/ConstantsTests.cs index 38d754d604..8180814cd7 100644 --- a/tests/ImageSharp.Tests/Common/ConstantsTests.cs +++ b/tests/ImageSharp.Tests/Common/ConstantsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using Xunit; diff --git a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs index e1b4fc790c..3e7fce3161 100644 --- a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void GetString_EmptyBuffer_ReturnsEmptyString() { - var buffer = new ReadOnlySpan(); + var buffer = default(ReadOnlySpan); string result = Encoding.UTF8.GetString(buffer); diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 4f8a2cdaf7..6dce489353 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Common private static Vector CreateExactTestVector1() { - float[] data = new float[Vector.Count]; + var data = new float[Vector.Count]; data[0] = 0.1f; data[1] = 0.4f; @@ -55,18 +55,19 @@ namespace SixLabors.ImageSharp.Tests.Common { data[i] = data[i - 4] + 100f; } + return new Vector(data); } private static Vector CreateRandomTestVector(int seed, float min, float max) { - float[] data = new float[Vector.Count]; + var 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; + float v = ((float)rnd.NextDouble() * (max - min)) + min; data[i] = v; } @@ -104,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Common private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null) { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName); return true; @@ -128,11 +129,11 @@ namespace SixLabors.ImageSharp.Tests.Common float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, 0, 256); float[] normalized = orig.Select(f => f / 255f).ToArray(); - byte[] dest = new byte[count]; + var dest = new byte[count]; SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(normalized, dest); - byte[] expected = orig.Select(f => (byte)(f)).ToArray(); + byte[] expected = orig.Select(f => (byte)f).ToArray(); Assert.Equal(expected, dest); } @@ -151,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Common float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f); - byte[] dest = new byte[count]; + var dest = new byte[count]; SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(source, dest); @@ -177,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Common { TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(s.Span, d.Span)); } [Theory] @@ -191,16 +192,16 @@ namespace SixLabors.ImageSharp.Tests.Common TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.BasicIntrinsics256.ByteToNormalizedFloat(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)); + (s, d) => SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); } [Theory] @@ -209,7 +210,7 @@ namespace SixLabors.ImageSharp.Tests.Common { TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); } private static void TestImpl_BulkConvertByteToNormalizedFloat( @@ -217,7 +218,7 @@ namespace SixLabors.ImageSharp.Tests.Common Action, Memory> convert) { byte[] source = new Random(count).GenerateRandomByteArray(count); - float[] result = new float[count]; + var result = new float[count]; float[] expected = source.Select(b => (float)b / 255f).ToArray(); convert(source, result); @@ -229,9 +230,9 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(ArraySizesDivisibleBy4))] public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] @@ -243,18 +244,16 @@ namespace SixLabors.ImageSharp.Tests.Common return; } - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.BasicIntrinsics256.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] [MemberData(nameof(ArraySizesDivisibleBy32))] public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] @@ -278,22 +277,38 @@ namespace SixLabors.ImageSharp.Tests.Common Assert.Equal(expected2, actual2); } +#if SUPPORTS_RUNTIME_INTRINSICS + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void Avx2_BulkConvertNormalizedFloatToByteClampOverflows(int count) + { + if (!System.Runtime.Intrinsics.X86.Avx2.IsSupported) + { + return; + } + + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.Avx2Intrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); + } + +#endif + [Theory] [MemberData(nameof(ArbitraryArraySizes))] public void BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span)); - // for small values, let's stress test the implementation a bit: + // 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), + (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span), i + 42); } } @@ -301,19 +316,21 @@ namespace SixLabors.ImageSharp.Tests.Common private static void TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( int count, - Action, Memory> convert, int seed = -1) + 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]; + var actual = new byte[count]; convert(source, actual); Assert.Equal(expected, actual); } - private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, f * 255f + 0.5f)); + private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, (f * 255f) + 0.5f)); [Theory] [InlineData(0)] @@ -325,7 +342,7 @@ namespace SixLabors.ImageSharp.Tests.Common private void MagicConvertToByte(float value) { byte actual = MagicConvert(value / 256f); - byte expected = (byte)value; + var expected = (byte)value; Assert.Equal(expected, actual); } @@ -362,7 +379,7 @@ namespace SixLabors.ImageSharp.Tests.Common { var magick = new Vector(32768.0f); - Vector scale = new Vector(255f) / new Vector(256f); + var scale = new Vector(255f) / new Vector(256f); Vector x = MemoryMarshal.Cast>(source)[0]; @@ -392,4 +409,4 @@ namespace SixLabors.ImageSharp.Tests.Common } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs index 8b2c65b07b..890a14faa0 100644 --- a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -67,7 +67,10 @@ namespace SixLabors.ImageSharp.Tests.Common public long Offset; public SeekOrigin Loc; - public SeekableStream(int capacity) : base(capacity) { } + public SeekableStream(int capacity) + : base(capacity) + { + } public override long Seek(long offset, SeekOrigin loc) { @@ -83,7 +86,10 @@ namespace SixLabors.ImageSharp.Tests.Common public List Counts = new List(); - public NonSeekableStream() : base(4) { } + public NonSeekableStream() + : base(4) + { + } public override int Read(byte[] buffer, int offset, int count) { @@ -97,7 +103,10 @@ namespace SixLabors.ImageSharp.Tests.Common { public override bool CanSeek => false; - public EofStream(int capacity) : base(capacity) { } + public EofStream(int capacity) + : base(capacity) + { + } public override int Read(byte[] buffer, int offset, int count) { diff --git a/tests/ImageSharp.Tests/Common/Tuple8.cs b/tests/ImageSharp.Tests/Common/Tuple8.cs index 3335e6e377..957312da02 100644 --- a/tests/ImageSharp.Tests/Common/Tuple8.cs +++ b/tests/ImageSharp.Tests/Common/Tuple8.cs @@ -1,4 +1,7 @@ -using System.Runtime.InteropServices; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Common.Tuples { @@ -95,4 +98,4 @@ namespace SixLabors.ImageSharp.Common.Tuples } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 208387e6d1..655e98c7f6 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,8 +8,8 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { /// @@ -18,8 +18,11 @@ namespace SixLabors.ImageSharp.Tests public class ConfigurationTests { public Configuration ConfigurationEmpty { get; } + public Configuration DefaultConfiguration { get; } + private readonly int expectedDefaultConfigurationCount = 5; + public ConfigurationTests() { // the shallow copy of configuration should behave exactly like the default configuration, @@ -39,13 +42,13 @@ namespace SixLabors.ImageSharp.Tests /// Test that the default configuration is not null. /// [Fact] - public void TestDefaultConfigurationIsNotNull() => Assert.True(Configuration.Default != null); + 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(Configuration.Default.ReadOrigin == ReadOrigin.Current); + public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(this.DefaultConfiguration.ReadOrigin == ReadOrigin.Current); /// /// Test that the default configuration parallel options max degrees of parallelism matches the @@ -54,22 +57,34 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void TestDefaultConfigurationMaxDegreeOfParallelism() { - Assert.True(Configuration.Default.MaxDegreeOfParallelism == Environment.ProcessorCount); + Assert.True(this.DefaultConfiguration.MaxDegreeOfParallelism == Environment.ProcessorCount); var cfg = new Configuration(); Assert.True(cfg.MaxDegreeOfParallelism == Environment.ProcessorCount); } [Theory] - [InlineData(0)] - [InlineData(-42)] - public void Set_MaxDegreeOfParallelism_ToNonPositiveValue_Throws(int value) + [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) { var cfg = new Configuration(); - Assert.Throws(() => cfg.MaxDegreeOfParallelism = value); + if (throws) + { + Assert.Throws( + () => cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism); + } + else + { + cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism; + Assert.Equal(maxDegreeOfParallelism, cfg.MaxDegreeOfParallelism); + } } - [Fact] public void ConstructorCallConfigureOnFormatProvider() { @@ -92,22 +107,44 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ConfigurationCannotAddDuplicates() { - const int count = 4; - Configuration config = Configuration.Default; + Configuration config = this.DefaultConfiguration; - Assert.Equal(count, config.ImageFormats.Count()); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance); - Assert.Equal(count, config.ImageFormats.Count()); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); } [Fact] public void DefaultConfigurationHasCorrectFormatCount() { - Configuration config = Configuration.Default; + var config = Configuration.CreateDefaultInstance(); + + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); + } + + [Fact] + public void WorkingBufferSizeHint_DefaultIsCorrect() + { + Configuration config = this.DefaultConfiguration; + Assert.True(config.WorkingBufferSizeHintInBytes > 1024); + } + + [Fact] + public void StreamBufferSize_DefaultIsCorrect() + { + Configuration config = this.DefaultConfiguration; + Assert.True(config.StreamProcessingBufferSize == 8096); + } + + [Fact] + public void StreamBufferSize_CannotGoBelowMinimum() + { + var config = new Configuration(); - Assert.Equal(4, config.ImageFormats.Count()); + Assert.Throws( + () => config.StreamProcessingBufferSize = 0); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs deleted file mode 100644 index 69b2098dc9..0000000000 --- a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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 new file mode 100644 index 0000000000..320a8bba8e --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using SixLabors.ImageSharp.Tests.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest + { + [Fact] + public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext() + { + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + + this.operations.DrawImage(null, 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 easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + + this.operations.DrawImage(null, 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 easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + + this.operations.DrawImage(null, 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 easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + + this.operations.DrawImage(null, 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 deleted file mode 100644 index 374454afba..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ /dev/null @@ -1,155 +0,0 @@ -// 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"); - } - } - - 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 new file mode 100644 index 0000000000..b426f44046 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -0,0 +1,201 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class DrawImageTests + { + public static readonly TheoryData BlendingModes = new TheoryData + { + 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 (var source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes)) + { + background.Mutate(x => x.DrawImage(source, mode, 1F)); + background.DebugSave( + provider, + new { mode = mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + var comparer = ImageComparer.TolerantPercentage(0.01F); + background.CompareToReferenceOutput( + comparer, + provider, + new { mode = 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 (var blend = Image.Load(TestFile.Create(brushImage).Bytes)) + { + var size = new Size(image.Width * 3 / 4, image.Height * 3 / 4); + var position = new Point(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 = $"{System.IO.Path.GetFileNameWithoutExtension(brushImage)}-{mode}-{opacity}"; + + var encoder = new PngEncoder(); + + if (provider.PixelType == PixelTypes.Rgba64) + { + encoder.BitDepth = PngBitDepth.Bit16; + } + + 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)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 (var overlay = new Image(50, 50)) + { + Assert.True(overlay.TryGetSinglePixelSpan(out Span overlaySpan)); + overlaySpan.Fill(Color.Black); + + 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] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + public void DrawTransformed(TestImageProvider provider) + where TPixel : unmanaged, 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)); + 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 + var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); + image.Mutate(x => x.DrawImage(blend, position, .75F)); + + image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.002f), + provider, + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); + } + } + + [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, Color.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), new GraphicsOptions())); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs deleted file mode 100644 index 0d791fbd23..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -// 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 deleted file mode 100644 index fa4d4a709f..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ /dev/null @@ -1,147 +0,0 @@ -// 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/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs deleted file mode 100644 index 556ec9c9ca..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ /dev/null @@ -1,354 +0,0 @@ -// 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; - - // check first and last pixel, these are known: - Assert.Equal(red, image[startX, startY]); - Assert.Equal(yellow, image[endX, endY]); - - 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); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs deleted file mode 100644 index b310c7afc6..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ /dev/null @@ -1,278 +0,0 @@ -// 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 deleted file mode 100644 index 7461347de1..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -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 deleted file mode 100644 index dc7da35433..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -// 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 deleted file mode 100644 index 639b3fe81a..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ /dev/null @@ -1,203 +0,0 @@ -// 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 deleted file mode 100644 index d827975c72..0000000000 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ /dev/null @@ -1,198 +0,0 @@ -// 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 deleted file mode 100644 index 43dec547eb..0000000000 --- a/tests/ImageSharp.Tests/Drawing/LineTests.cs +++ /dev/null @@ -1,192 +0,0 @@ -// 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 deleted file mode 100644 index 326517a4e1..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs +++ /dev/null @@ -1,115 +0,0 @@ -// 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 deleted file mode 100644 index e72fbbdf24..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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 deleted file mode 100644 index ec7a5a20c8..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs +++ /dev/null @@ -1,119 +0,0 @@ -// 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 deleted file mode 100644 index d8927a4683..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs +++ /dev/null @@ -1,91 +0,0 @@ -// 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 deleted file mode 100644 index 8f648e425f..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs +++ /dev/null @@ -1,93 +0,0 @@ -// 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 deleted file mode 100644 index b474f6e47c..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs +++ /dev/null @@ -1,10 +0,0 @@ -// 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 deleted file mode 100644 index 40c5f950da..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -// 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 deleted file mode 100644 index 6ea9c647f2..0000000000 --- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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 deleted file mode 100644 index 2dcd8b3d34..0000000000 --- a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 deleted file mode 100644 index 23acc1a44f..0000000000 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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 deleted file mode 100644 index 2c9628e842..0000000000 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -// 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 deleted file mode 100644 index 94e12f8581..0000000000 --- a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs +++ /dev/null @@ -1,179 +0,0 @@ -// 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 deleted file mode 100644 index d7fb0a3d37..0000000000 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ /dev/null @@ -1,237 +0,0 @@ -// 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 deleted file mode 100644 index 76f40e0aca..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs +++ /dev/null @@ -1,163 +0,0 @@ -// 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 deleted file mode 100644 index 20ccb25a54..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs +++ /dev/null @@ -1,246 +0,0 @@ -// 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 deleted file mode 100644 index 0885611c67..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 deleted file mode 100644 index 6660cd87af..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 deleted file mode 100644 index ff4014e616..0000000000 --- a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 index efd6c3b40e..93024197b3 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -1,6 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; namespace SixLabors.ImageSharp.Tests @@ -8,6 +9,7 @@ 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 { /// @@ -26,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests /// /// A collection of all the bmp test images /// - public static IEnumerable AllBmpFiles = TestImages.Bmp.All; + public static IEnumerable AllBmpFiles = TestImages.Bmp.Benchmark; /// /// A collection of all the jpeg test images @@ -69,22 +71,23 @@ namespace SixLabors.ImageSharp.Tests /// 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), +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + 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.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 @@ -102,10 +105,11 @@ namespace SixLabors.ImageSharp.Tests // 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.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 }; +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } -} \ 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 5d7d35dd58..3f767620a6 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -1,71 +1,320 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; // ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - using SixLabors.ImageSharp.MetaData; + using SixLabors.ImageSharp.Metadata; using static TestImages.Bmp; public class BmpDecoderTests { public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - public static readonly string[] AllBmpFiles = All; + public static readonly string[] MiscBmpFiles = Miscellaneous; public static readonly string[] BitfieldsBmpFiles = BitFields; + private static BmpDecoder BmpDecoder => new BmpDecoder(); + public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Bmp.RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } }; [Theory] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)] - public void DecodeBmp(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, false)] + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - image.DebugSave(provider); + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + } + + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); if (TestEnvironment.IsWindows) { image.CompareToOriginal(provider); } } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceDiscontiguousBuffers ? "Disco" : string.Empty) + .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)); + Assert.IsType(ex.InnerException); } [Theory] [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(BmpDecoder)) + { + 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)) + { + 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)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); + } + } + + [Theory] + [WithFile(Bit4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(BmpDecoder)) + { + image.DebugSave(provider); + + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. + if (TestEnvironment.IsWindows) + { + 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)) + { + 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)) + { + 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(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { 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)) + { + 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 + { + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) + { + image.DebugSave(provider); + + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + } + + [Theory] + [WithFile(RLE4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) + { + image.DebugSave(provider); + + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. + if (TestEnvironment.IsWindows) + { + 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 + { + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) + { + image.DebugSave(provider); + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); + } + } + } + + [Theory] + [WithFile(RLE8Cut, PixelTypes.Rgba32)] + [WithFile(RLE8Delta, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [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) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + } + + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [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) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + } + + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) + { + image.DebugSave(provider); + + // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. + // image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(BmpDecoder)) + { + image.DebugSave(provider); + + // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. + // image.CompareToOriginal(provider); + } + } + [Theory] [WithFile(Bit32Rgba, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -75,9 +324,9 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(Rgba321010102, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -85,17 +334,18 @@ namespace SixLabors.ImageSharp.Tests // 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()); + // 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()); } } [Theory] [WithFile(WinBmpv2, PixelTypes.Rgba32)] + [WithFile(CoreHeader, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -105,9 +355,9 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(WinBmpv3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -115,11 +365,78 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [WithFile(Rgba32bf56, PixelTypes.Rgba32)] + [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(BmpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [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)) + { + image.DebugSave(provider); + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + } + + [Theory] + [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] + public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Assert.Throws(() => + { + using (provider.GetImage(BmpDecoder)) + { + } + }); + } + + [Theory] + [WithFile(Rgb24jpeg, PixelTypes.Rgba32)] + [WithFile(Rgb24png, PixelTypes.Rgba32)] + public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Assert.Throws(() => + { + using (provider.GetImage(BmpDecoder)) + { + } + }); + } + + [Theory] + [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(Rgba32bf56AdobeV3, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -129,9 +446,9 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(WinBmpv4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -140,10 +457,23 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(WinBmpv5, PixelTypes.Rgba32)] + [WithFile(V5Header, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) + { + 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)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -153,9 +483,9 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(F, CommonNonDefaultPixelTypes)] public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -165,9 +495,9 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(Bit8Palette4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -175,17 +505,47 @@ namespace SixLabors.ImageSharp.Tests } [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)] - public void Identify(string imagePath, int expectedPixelSize) + [InlineData(Bit4, 4)] + [InlineData(Bit1, 1)] + [InlineData(Bit1Pal1, 1)] + public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel); + } + } + + [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) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); } } @@ -199,7 +559,7 @@ namespace SixLabors.ImageSharp.Tests var decoder = new BmpDecoder(); using (Image image = decoder.Decode(Configuration.Default, stream)) { - ImageMetaData meta = image.MetaData; + ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits); @@ -210,14 +570,13 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(Os2v2Short, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); - // TODO: Neither System.Drawing not MagickReferenceDecoder - // can correctly decode this file. + // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. // image.CompareToOriginal(provider); } } @@ -225,9 +584,9 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(Os2v2, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -237,5 +596,27 @@ namespace SixLabors.ImageSharp.Tests // image.CompareToOriginal(provider, new MagickReferenceDecoder()); } } + + [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)) + { + image.DebugSave(provider); + + // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. + // image.CompareToOriginal(provider); + } + } } -} \ 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 f818be8a91..b05486e356 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -1,17 +1,25 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; + +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.MetaData; +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 Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - public class BmpEncoderTests : FileTestBase + using static TestImages.Bmp; + + public class BmpEncoderTests { public static readonly TheoryData BitsPerPixel = new TheoryData @@ -23,16 +31,16 @@ namespace SixLabors.ImageSharp.Tests public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Bmp.RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } }; public static readonly TheoryData BmpBitsPerPixelFiles = new TheoryData { - { TestImages.Bmp.Car, BmpBitsPerPixel.Pixel24 }, - { TestImages.Bmp.Bit32Rgb, BmpBitsPerPixel.Pixel32 } + { Car, BmpBitsPerPixel.Pixel24 }, + { Bit32Rgb, BmpBitsPerPixel.Pixel32 } }; public BmpEncoderTests(ITestOutputHelper output) => this.Output = output; @@ -46,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests var options = new BmpEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { @@ -55,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests memStream.Position = 0; using (var output = Image.Load(memStream)) { - ImageMetaData meta = output.MetaData; + ImageMetadata meta = output.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits); @@ -71,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests var options = new BmpEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { @@ -80,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests memStream.Position = 0; using (var output = Image.Load(memStream)) { - BmpMetaData meta = output.MetaData.GetFormatMetaData(BmpFormat.Instance); + BmpMetadata meta = output.Metadata.GetBmpMetadata(); Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); } @@ -91,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests [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); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); [Theory] [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] @@ -100,21 +108,168 @@ namespace SixLabors.ImageSharp.Tests [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); + where TPixel : unmanaged, 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)] + 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.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)] + 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.Pixel24)] // WinBmpv3 is a 24 bits per pixel image. + [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + 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.Pixel24)] + [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + 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.Pixel16)] + [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + 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.Pixel16)] + [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + 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.Pixel8)] + [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] + 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.Pixel8)] + [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] + 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.Pixel8)] + public void Encode_8BitGray_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: false); + + [Theory] + [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] + 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; + } - private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel + using (Image image = provider.GetImage()) + { + var encoder = new BmpEncoder + { + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new WuQuantizer() + }; + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) + { + referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false); + } + } + } + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) + where TPixel : unmanaged, IPixel { + if (!TestEnvironment.Is64BitProcess) + { + return; + } + using (Image image = provider.GetImage()) { - // there is no alpha in bmp! - image.Mutate(c => c.MakeOpaque()); + var encoder = new BmpEncoder + { + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new OctreeQuantizer() + }; + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) + { + referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false); + } + } + } + + [Theory] + [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + [WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + [WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(100); + TestBmpEncoderCore(provider, bitsPerPixel); + } + + private static void TestBmpEncoderCore( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel, + bool supportTransparency = true, + ImageComparer customComparer = null) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque. + if (bitsPerPixel != BmpBitsPerPixel.Pixel32) + { + image.Mutate(c => c.MakeOpaque()); + } - var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel }; + var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency }; // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder); + 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 8ad227cfdc..125e40194f 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.Formats.Bmp; using Xunit; @@ -11,11 +14,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { var header = new BmpFileHeader(1, 2, 3, 4); - byte[] buffer = new byte[14]; + var buffer = new byte[14]; header.WriteTo(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 deleted file mode 100644 index 991768a11a..0000000000 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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 new file mode 100644 index 0000000000..b149563793 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats.Bmp; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Bmp +{ + using static TestImages.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)); + } + + [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) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + BmpMetadata bitmapMetadata = imageInfo.Metadata.GetBmpMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedInfoHeaderType, bitmapMetadata.InfoHeaderType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs new file mode 100644 index 0000000000..5428ddbdca --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +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 (var image = new Image(10, 10)) + { + image.SaveAsBmp(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsBmpAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsBmpAsync_Path.bmp"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsBmpAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsBmp_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsBmp_Path_Encoder.bmp"); + + using (var image = new Image(10, 10)) + { + image.SaveAsBmp(file, new BmpEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsBmpAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsBmpAsync_Path_Encoder.bmp"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsBmpAsync(file, new BmpEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsBmp_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsBmp(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsBmpAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsBmpAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsBmp_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsBmp(memoryStream, new BmpEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsBmpAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsBmpAsync(memoryStream, new BmpEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index aed1edfbf0..c10cd8d298 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -1,34 +1,33 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using System.Linq; +using System.Reflection; + 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.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats { - using System; - using System.Reflection; - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Quantization; - using SixLabors.Memory; - public class GeneralFormatTests : FileTestBase { [Theory] [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] public void ResolutionShouldChange(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - image.MetaData.VerticalResolution = 150; - image.MetaData.HorizontalResolution = 150; + image.Metadata.VerticalResolution = 150; + image.Metadata.HorizontalResolution = 150; image.DebugSave(provider); } } @@ -40,9 +39,9 @@ namespace SixLabors.ImageSharp.Tests foreach (TestFile file in Files) { - using (Image image = file.CreateImage()) + using (Image image = file.CreateRgba32Image()) { - string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; + string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt"); File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); } } @@ -55,9 +54,9 @@ namespace SixLabors.ImageSharp.Tests foreach (TestFile file in Files) { - using (Image image = file.CreateImage()) + using (Image image = file.CreateRgba32Image()) { - image.Save($"{path}/{file.FileName}"); + image.Save(Path.Combine(path, file.FileName)); } } } @@ -75,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests [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 + where TPixel : unmanaged, IPixel { provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); @@ -83,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests using (Image image = provider.GetImage()) { - image.DebugSave(provider, new PngEncoder() { ColorType = PngColorType.Palette, Quantizer = quantizer }, testOutputDetails: quantizerName); + image.DebugSave(provider, new PngEncoder { ColorType = PngColorType.Palette, Quantizer = quantizer }, testOutputDetails: quantizerName); } provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); @@ -92,7 +91,7 @@ namespace SixLabors.ImageSharp.Tests private static IQuantizer GetQuantizer(string name) { PropertyInfo property = typeof(KnownQuantizers).GetTypeInfo().GetProperty(name); - return (IQuantizer)property.GetMethod.Invoke(null, new object[0]); + return (IQuantizer)property.GetMethod.Invoke(null, Array.Empty()); } [Fact] @@ -102,27 +101,32 @@ namespace SixLabors.ImageSharp.Tests foreach (TestFile file in Files) { - using (Image image = file.CreateImage()) + using (Image image = file.CreateRgba32Image()) { - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) { image.SaveAsBmp(output); } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) { image.SaveAsJpeg(output); } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) { image.SaveAsPng(output); } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) { image.SaveAsGif(output); } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) + { + image.SaveAsTga(output); + } } } } @@ -167,38 +171,49 @@ namespace SixLabors.ImageSharp.Tests [InlineData(100, 100, "jpg")] [InlineData(100, 10, "jpg")] [InlineData(10, 100, "jpg")] - public void CanIdentifyImageLoadedFromBytes(int width, int height, string format) + [InlineData(100, 100, "tga")] + [InlineData(100, 10, "tga")] + [InlineData(10, 100, "tga")] + public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) { using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) { using (var memoryStream = new MemoryStream()) { - image.Save(memoryStream, GetEncoder(format)); + IImageFormat format = GetFormat(extension); + image.Save(memoryStream, format); memoryStream.Position = 0; IImageInfo imageInfo = Image.Identify(memoryStream); Assert.Equal(imageInfo.Width, width); Assert.Equal(imageInfo.Height, height); + memoryStream.Position = 0; + + imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat); + + Assert.Equal(format, detectedFormat); } } } - private static IImageEncoder GetEncoder(string format) + [Fact] + public void IdentifyReturnsNullWithInvalidStream() { - switch (format) + byte[] invalid = new byte[10]; + + using (var memoryStream = new MemoryStream(invalid)) { - 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); + IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format); + + Assert.Null(imageInfo); + Assert.Null(format); } } + + private static IImageFormat GetFormat(string format) + { + return 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 5bfbb058be..63aae5c559 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -1,15 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.MetaData; +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 @@ -19,49 +21,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + private static GifDecoder GifDecoder => new GifDecoder(); + public static readonly string[] MultiFrameTestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Kumin }; - public static readonly string[] BasicVerificationFiles = - { - TestImages.Gif.Cheers, - TestImages.Gif.Rings, - - // previously DecodeBadApplicationExtensionLength: - TestImages.Gif.Issues.BadAppExtLength, - TestImages.Gif.Issues.BadAppExtLength_2, - - // previously DecodeBadDescriptorDimensionsLength: - TestImages.Gif.Issues.BadDescriptorWidth - }; - - 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 } - }; - - private static readonly Dictionary BasicVerificationFrameCount = - new Dictionary - { - [TestImages.Gif.Cheers] = 93, - [TestImages.Gif.Issues.BadDescriptorWidth] = 36, - }; - - public static readonly string[] BadAppExtFiles = - { - TestImages.Gif.Issues.BadAppExtLength, - TestImages.Gif.Issues.BadAppExtLength_2 - }; - [Theory] [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] public void Decode_VerifyAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -81,9 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { using (var stream = new UnmanagedMemoryStream(data, length)) { - var decoder = new GifDecoder(); - - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = GifDecoder.Decode(Configuration.Default, stream)) { Assert.Equal((200, 200), (image.Width, image.Height)); } @@ -91,44 +59,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } - [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 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); - } - } - } - - [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.Trans, TestPixelTypes)] public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -138,15 +72,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } [Theory] - [WithFileCollection(nameof(BasicVerificationFiles), PixelTypes.Rgba32)] - public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 93)] + [WithFile(TestImages.Gif.Rings, PixelTypes.Rgba32, 1)] + [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32, 36)] + public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider, int expectedFrameCount) + where TPixel : unmanaged, IPixel { - if (!BasicVerificationFrameCount.TryGetValue(provider.SourceFileOrDescription, out int expectedFrameCount)) - { - expectedFrameCount = 1; - } - using (Image image = provider.GetImage()) { Assert.Equal(expectedFrameCount, image.Frames.Count); @@ -155,61 +86,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } - [Fact] - public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() - { - var options = new GifDecoder - { - IgnoreMetadata = false - }; - - var testFile = TestFile.Create(TestImages.Gif.Rings); - - 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); - } - } - - [Fact] - public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() - { - var options = new GifDecoder - { - IgnoreMetadata = true - }; - - var testFile = TestFile.Create(TestImages.Gif.Rings); - - using (Image image = testFile.CreateImage(options)) - { - Assert.Equal(0, image.MetaData.Properties.Count); - } - } - - [Fact] - public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() - { - var options = new GifDecoder - { - TextEncoding = Encoding.Unicode - }; - - var testFile = TestFile.Create(TestImages.Gif.Rings); - - using (Image image = testFile.CreateImage(options)) - { - Assert.Equal(1, image.MetaData.Properties.Count); - Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value); - } - } - [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First })) { @@ -220,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void CanDecodeAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All })) { @@ -242,20 +122,102 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } + [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 + { + System.Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(GifDecoder); + }); + 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)) + { + Assert.Equal(expectedWidth, image.Width); + Assert.Equal(expectedHeight, image.Height); + } + } + [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 (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()); + + Assert.True(second.TryGetSinglePixelSpan(out Span secondSpan)); + + first.ComparePixelBufferTo(secondSpan); } } } + + // 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()) + { + 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)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] + public void GifDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(GifDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } -} \ 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 b74a7a85f9..f8efe700f6 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -1,15 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.MetaData; +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 +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { public class GifEncoderTests @@ -20,23 +21,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif 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.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; [Theory] - [WithTestPatternImages(100, 100, TestPixelTypes)] - public void EncodeGeneratedPatterns(TestImageProvider provider) - where TPixel : struct, IPixel + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + public void EncodeGeneratedPatterns(TestImageProvider provider, bool limitAllocationBuffer) + where TPixel : unmanaged, IPixel { + if (limitAllocationBuffer) + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + } + using (Image image = provider.GetImage()) { - var encoder = new GifEncoder() + var encoder = new GifEncoder { - // Use the palette quantizer without dithering to ensure results - // are consistant - Quantizer = new WebSafePaletteQuantizer(false) + // Use the palette quantizer without dithering to ensure results + // are consistent + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -45,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif // Compare encoded result string path = provider.Utility.GetTestOutputFileName("gif", null, true); - using (var encoded = Image.Load(path)) + using (var encoded = Image.Load(path)) { encoded.CompareToReferenceOutput(ValidatorComparer, provider, null, "gif"); } @@ -58,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var options = new GifEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { @@ -67,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif memStream.Position = 0; using (var output = Image.Load(memStream)) { - ImageMetaData meta = output.MetaData; + ImageMetadata meta = output.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits); @@ -83,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { @@ -92,55 +99,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif 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); - } - } - } - } - - [Fact] - public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() - { - var options = new GifEncoder(); - - var testFile = TestFile.Create(TestImages.Gif.Rings); - - using (Image input = testFile.CreateImage()) - { - input.MetaData.Properties.Clear(); - using (var memStream = new MemoryStream()) - { - input.SaveAsGif(memStream, options); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - Assert.Equal(0, output.MetaData.Properties.Count); - } - } - } - } - - [Fact] - public void Encode_WhenCommentIsTooLong_CommentIsTrimmed() - { - using (var input = new Image(1, 1)) - { - 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); + GifMetadata metadata = output.Metadata.GetGifMetadata(); + Assert.Equal(1, metadata.Comments.Count); + Assert.Equal("ImageSharp", metadata.Comments[0]); } } } @@ -149,14 +110,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32)] public void EncodeGlobalPaletteReturnsSmallerFile(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var encoder = new GifEncoder { ColorTableMode = GifColorTableMode.Global, - Quantizer = new OctreeQuantizer(false) + Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -172,6 +133,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } + [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(); + + var encoder = new GifEncoder() + { + ColorTableMode = GifColorTableMode.Global, + GlobalPixelSamplingStrategy = 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.Ge + // ReferenceDecoder(testOutputFile); + // using var encoded = Image.Load(testOutputFile, referenceDecoder); + // ValidatorComparer.VerifySimilarity(image, encoded); + } + [Fact] public void NonMutatingEncodePreservesPaletteCount() { @@ -180,32 +173,32 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { inStream.Position = 0; - var image = Image.Load(inStream); - GifMetaData metaData = image.MetaData.GetFormatMetaData(GifFormat.Instance); - GifFrameMetaData frameMetaData = image.Frames.RootFrame.MetaData.GetFormatMetaData(GifFormat.Instance); + var image = Image.Load(inStream); + GifMetadata metaData = image.Metadata.GetGifMetadata(); + GifFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata(); GifColorTableMode colorMode = metaData.ColorTableMode; - var encoder = new GifEncoder() + var encoder = new GifEncoder { ColorTableMode = colorMode, - Quantizer = new OctreeQuantizer(frameMetaData.ColorTableLength) + Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength }) }; image.Save(outStream, encoder); outStream.Position = 0; outStream.Position = 0; - var clone = Image.Load(outStream); + var clone = Image.Load(outStream); - GifMetaData cloneMetaData = clone.MetaData.GetFormatMetaData(GifFormat.Instance); - Assert.Equal(metaData.ColorTableMode, cloneMetaData.ColorTableMode); + GifMetadata cloneMetadata = clone.Metadata.GetGifMetadata(); + Assert.Equal(metaData.ColorTableMode, cloneMetadata.ColorTableMode); // Gifiddle and Cyotek GifInfo say this image has 64 colors. - Assert.Equal(64, frameMetaData.ColorTableLength); + Assert.Equal(64, frameMetadata.ColorTableLength); for (int i = 0; i < image.Frames.Count; i++) { - GifFrameMetaData ifm = image.Frames[i].MetaData.GetFormatMetaData(GifFormat.Instance); - GifFrameMetaData cifm = clone.Frames[i].MetaData.GetFormatMetaData(GifFormat.Instance); + GifFrameMetadata ifm = image.Frames[i].Metadata.GetGifMetadata(); + GifFrameMetadata cifm = clone.Frames[i].Metadata.GetGifMetadata(); Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength); Assert.Equal(ifm.FrameDelay, cifm.FrameDelay); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs deleted file mode 100644 index a39fc47b40..0000000000 --- a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 new file mode 100644 index 0000000000..61caaad66d --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// 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/GifMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs deleted file mode 100644 index 29db32b4ab..0000000000 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 new file mode 100644 index 0000000000..533b1ed301 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -0,0 +1,194 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifMetadataTests + { + 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 } + }; + + public static readonly TheoryData RepeatFiles = + new TheoryData + { + { TestImages.Gif.Cheers, 0 }, + { TestImages.Gif.Receipt, 1 }, + { TestImages.Gif.Rings, 1 } + }; + + [Fact] + public void CloneIsDeep() + { + var meta = new GifMetadata + { + RepeatCount = 1, + ColorTableMode = GifColorTableMode.Global, + GlobalColorTableLength = 2, + Comments = new List { "Foo" } + }; + + 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)); + Assert.False(meta.Comments.Equals(clone.Comments)); + Assert.True(meta.Comments.SequenceEqual(clone.Comments)); + } + + [Fact] + public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() + { + var options = new GifDecoder + { + IgnoreMetadata = false + }; + + var testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateRgba32Image(options)) + { + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(1, metadata.Comments.Count); + Assert.Equal("ImageSharp", metadata.Comments[0]); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() + { + var options = new GifDecoder + { + IgnoreMetadata = true + }; + + var testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateRgba32Image(options)) + { + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(0, metadata.Comments.Count); + } + } + + [Fact] + public void Decode_CanDecodeLargeTextComment() + { + var options = new GifDecoder(); + var testFile = TestFile.Create(TestImages.Gif.LargeComment); + + using (Image image = testFile.CreateRgba32Image(options)) + { + 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() + { + var decoder = new GifDecoder(); + var testFile = TestFile.Create(TestImages.Gif.LargeComment); + + using (Image input = testFile.CreateRgba32Image(decoder)) + using (var memoryStream = new MemoryStream()) + { + input.Save(memoryStream, new GifEncoder()); + memoryStream.Position = 0; + + using (Image image = decoder.Decode(Configuration.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) + { + 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] + [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 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); + } + } + } + + [Theory] + [MemberData(nameof(RepeatFiles))] + public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); + } + } + + [Theory] + [MemberData(nameof(RepeatFiles))] + public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs new file mode 100644 index 0000000000..50b0fc6bf8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +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 (var image = new Image(10, 10)) + { + image.SaveAsGif(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsGifAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsGifAsync_Path.gif"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsGifAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsGif_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsGif_Path_Encoder.gif"); + + using (var image = new Image(10, 10)) + { + image.SaveAsGif(file, new GifEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsGifAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsGifAsync_Path_Encoder.gif"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsGifAsync(file, new GifEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsGif_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsGif(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsGifAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsGifAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsGif_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsGif(memoryStream, new GifEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsGifAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsGifAsync(memoryStream, new GifEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs index dc0da5e2db..6ec1162c46 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs index 6a90c0c277..db88cf5b3f 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs index c6458d22ff..9773bcd612 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index c2100c302f..98fbac7c04 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -1,146 +1,149 @@ -// 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); - } - } -} +// Copyright (c) Six Labors. +// 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.Formats.Tga; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +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.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.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 8b0e89f59d..d033e17ff7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs index 4b1abf9094..193fead8e2 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs @@ -1,19 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // Uncomment this to turn unit tests into benchmarks: -//#define BENCHMARKING - +// #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 +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class Block8x8FTests @@ -31,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { for (int x = 0; x < 20; x++) { - if (x < subX || x >= subX + 8 * horizontalFactor || y < subY || y >= subY + 8 * verticalFactor) + if (x < subX || x >= subX + (8 * horizontalFactor) || y < subY || y >= subY + (8 * verticalFactor)) { Assert.Equal(0, buffer[x, y]); } @@ -46,8 +43,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(20, 20, AllocationOptions.Clean)) { - BufferArea area = buffer.GetArea(5, 10, 8, 8); - block.Copy1x1Scale(area); + Buffer2DRegion region = buffer.GetRegion(5, 10, 8, 8); + block.Copy1x1Scale(ref region.GetReferenceToOrigin(), region.Stride); Assert.Equal(block[0, 0], buffer[5, 10]); Assert.Equal(block[1, 0], buffer[6, 10]); @@ -74,8 +71,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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); + Buffer2DRegion region = buffer.GetRegion(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); + block.ScaledCopyTo(region, horizontalFactor, verticalFactor); for (int y = 0; y < 8 * verticalFactor; y++) { @@ -85,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int xx = x / horizontalFactor; float expected = block[xx, yy]; - float actual = area[x, y]; + float actual = region[x, y]; Assert.Equal(expected, actual); } @@ -96,4 +93,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ 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 7e7218c9dc..722521f98d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // Uncomment this to turn unit tests into benchmarks: -//#define BENCHMARKING - +// #define BENCHMARKING using System; using System.Diagnostics; @@ -30,11 +29,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private bool SkipOnNonAvx2Runner() { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("AVX2 not supported, skipping!"); return true; } + return false; } @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var block = new Block8x8F(); + var block = default(Block8x8F); for (int i = 0; i < Block8x8F.Size; i++) { @@ -102,9 +102,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var b = new Block8x8F(); + var b = default(Block8x8F); b.LoadFrom(data); - b.CopyTo(mirror); + b.ScaledCopyTo(mirror); }); Assert.Equal(data, mirror); @@ -127,9 +127,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var b = new Block8x8F(); + var b = default(Block8x8F); Block8x8F.LoadFrom(&b, data); - Block8x8F.CopyTo(&b, mirror); + Block8x8F.ScaledCopyTo(&b, mirror); }); Assert.Equal(data, mirror); @@ -152,9 +152,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var v = new Block8x8F(); + var v = default(Block8x8F); v.LoadFrom(data); - v.CopyTo(mirror); + v.ScaledCopyTo(mirror); }); Assert.Equal(data, mirror); @@ -168,14 +168,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] expected = Create8x8FloatData(); ReferenceImplementations.Transpose8x8(expected); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(Create8x8FloatData()); - var dest = new Block8x8F(); + var dest = default(Block8x8F); source.TransposeInto(ref dest); float[] actual = new float[64]; - dest.CopyTo(actual); + dest.ScaledCopyTo(actual); Assert.Equal(expected, actual); } @@ -186,13 +186,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void TranposeInto_Benchmark() + public void TransposeInto_Benchmark() { var source = new BufferHolder(); source.Buffer.LoadFrom(Create8x8FloatData()); var dest = new BufferHolder(); - this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ..."); + this.Output.WriteLine($"TransposeInto_PinningImpl_Benchmark X {Times} ..."); var sw = Stopwatch.StartNew(); for (int i = 0; i < Times; i++) @@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } sw.Stop(); - this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms"); + this.Output.WriteLine($"TransposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms"); } private static float[] Create8x8ColorCropTestData() @@ -211,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = -300 + i * 100 + j * 10; + result[(i * 8) + j] = -300 + (i * 100) + (j * 10); } } @@ -231,7 +231,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg dest.NormalizeColorsInplace(255); float[] array = new float[64]; - dest.CopyTo(array); + dest.ScaledCopyTo(array); this.Output.WriteLine("Result:"); this.PrintLinearData(array); foreach (float val in array) @@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg expected.RoundInplace(); Block8x8F actual = source; - actual.NormalizeColorsAndRoundInplaceAvx2(255); + actual.NormalizeColorsAndRoundInplaceVector8(255); this.Output.WriteLine(expected.ToString()); this.Output.WriteLine(actual.ToString()); @@ -269,10 +269,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public unsafe void Quantize(int seed) { - var block = new Block8x8F(); + var block = default(Block8x8F); block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); - var qt = new Block8x8F(); + var qt = default(Block8x8F); qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); var unzig = ZigZag.CreateUnzigTable(); @@ -282,7 +282,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actualResults = default(Block8x8F); - Block8x8F.Quantize(&block, &actualResults, &qt, unzig.Data); + Block8x8F.Quantize(ref block, ref actualResults, ref qt, ref unzig); for (int i = 0; i < Block8x8F.Size; i++) { @@ -426,7 +426,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int i = 0; i < Block8x8F.Size; i++) { - Assert.Equal((float)data[i], dest[i]); + Assert.Equal(data[i], dest[i]); } } @@ -441,14 +441,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg short[] data = Create8x8ShortData(); var source = new Block8x8(data); - + Block8x8F dest = default; dest.LoadFromInt16ExtendedAvx2(ref source); for (int i = 0; i < Block8x8F.Size; i++) { - Assert.Equal((float)data[i], dest[i]); + Assert.Equal(data[i], dest[i]); } } } -} \ 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 3df927aeb0..c22db3a1ce 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public unsafe void Indexer_GetScalarAt_SetScalarAt() { - int sum = 0; + int sum; var block = default(Block8x8); for (int i = 0; i < Block8x8.Size; i++) @@ -58,10 +58,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { sum += Block8x8.GetScalarAt(&block, i); } + Assert.Equal(sum, 64 * 63 / 2); } - [Fact] public void AsFloatBlock() { @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int i = 0; i < Block8x8F.Size; i++) { - Assert.Equal((float)data[i], dest[i]); + Assert.Equal(data[i], dest[i]); } } @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void IndexerXY() { Block8x8 block = default; - block[8 * 3 + 5] = 42; + block[(8 * 3) + 5] = 42; short value = block[5, 3]; @@ -141,4 +141,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(15, d); } } -} \ 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 92b92eb100..2c1239883d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -1,4 +1,6 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -7,6 +9,7 @@ using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public static class DCTTests @@ -19,22 +22,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void iDCT2D8x4_LeftPart() + public void IDCT2D8x4_LeftPart() { - float[] sourceArray = JpegFixture.Create8x8FloatData(); - float[] expectedDestArray = new float[64]; + float[] sourceArray = Create8x8FloatData(); + var expectedDestArray = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray, expectedDestArray); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray, expectedDestArray); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(sourceArray); - var dest = new Block8x8F(); + var dest = default(Block8x8F); FastFloatingPointDCT.IDCT8x4_LeftPart(ref source, ref dest); - float[] actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); + var actualDestArray = new float[64]; + dest.ScaledCopyTo(actualDestArray); this.Print8x8Data(expectedDestArray); this.Output.WriteLine("**************"); @@ -44,22 +47,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void iDCT2D8x4_RightPart() + public void IDCT2D8x4_RightPart() { - float[] sourceArray = JpegFixture.Create8x8FloatData(); - float[] expectedDestArray = new float[64]; + float[] sourceArray = Create8x8FloatData(); + var expectedDestArray = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4)); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4)); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(sourceArray); - var dest = new Block8x8F(); + var dest = default(Block8x8F); FastFloatingPointDCT.IDCT8x4_RightPart(ref source, ref dest); - float[] actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); + var actualDestArray = new float[64]; + dest.ScaledCopyTo(actualDestArray); this.Print8x8Data(expectedDestArray); this.Output.WriteLine("**************"); @@ -74,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(3)] public void LLM_TransformIDCT_CompareToNonOptimized(int seed) { - float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-1000, 1000, seed); + float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); var source = Block8x8F.Load(sourceArray); @@ -93,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(3)] public void LLM_TransformIDCT_CompareToAccurate(int seed) { - float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-1000, 1000, seed); + float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); var source = Block8x8F.Load(sourceArray); @@ -106,25 +109,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 1f); } - [Theory] [InlineData(1)] [InlineData(2)] public void FDCT8x4_LeftPart(int seed) { - Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); - float[] expectedDest = new float[64]; + var expectedDest = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src, expectedDest); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src, expectedDest); FastFloatingPointDCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); - float[] actualDest = new float[64]; - destBlock.CopyTo(actualDest); + var actualDest = new float[64]; + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } @@ -134,19 +136,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void FDCT8x4_RightPart(int seed) { - Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); - float[] expectedDest = new float[64]; + var expectedDest = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); - float[] actualDest = new float[64]; - destBlock.CopyTo(actualDest); + var actualDest = new float[64]; + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } @@ -156,25 +158,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void TransformFDCT(int seed) { - Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); - float[] expectedDest = new float[64]; - float[] temp1 = new float[64]; - var temp2 = new Block8x8F(); + var expectedDest = new float[64]; + var temp1 = new float[64]; + var temp2 = default(Block8x8F); - 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); - float[] actualDest = new float[64]; - destBlock.CopyTo(actualDest); + var actualDest = new float[64]; + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } - } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs index 341d67f0f1..bb857f1eda 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class GenericBlock8x8Tests { public static Image CreateTestImage() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var image = new Image(10, 10); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { for (int j = 0; j < 10; j++) { - var rgba = new Rgba32((byte)(i + 1), (byte)(j + 1), (byte)200, (byte)255); + var rgba = new Rgba32((byte)(i + 1), (byte)(j + 1), 200, 255); var color = default(TPixel); color.FromRgba32(rgba); @@ -36,12 +36,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image s = provider.GetImage()) { var d = default(GenericBlock8x8); - d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0); + var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 0); + d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, rowOctet); TPixel a = s.Frames.RootFrame[0, 0]; TPixel b = d[0, 0]; @@ -59,13 +60,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] - public unsafe void LoadAndStretchCorners_WithOffset(TestImageProvider provider) - where TPixel : struct, IPixel + public void LoadAndStretchCorners_WithOffset(TestImageProvider provider) + where TPixel : unmanaged, IPixel { using (Image s = provider.GetImage()) { var d = default(GenericBlock8x8); - d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7); + var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 7); + d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, rowOctet); Assert.Equal(s[6, 7], d[0, 0]); Assert.Equal(s[6, 8], d[0, 1]); @@ -121,4 +123,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected77, block[7, 7]); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs new file mode 100644 index 0000000000..9b67bcd1eb --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class ImageExtensionsTest + { + [Fact] + public void SaveAsJpeg_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsJpeg_Path.jpg"); + + using (var image = new Image(10, 10)) + { + image.SaveAsJpeg(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsJpegAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsJpegAsync_Path.jpg"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsJpegAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsJpeg_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsJpeg_Path_Encoder.jpg"); + + using (var image = new Image(10, 10)) + { + image.SaveAsJpeg(file, new JpegEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsJpegAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsJpegAsync_Path_Encoder.jpg"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsJpegAsync(file, new JpegEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsJpeg_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsJpeg(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsJpegAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsJpegAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsJpeg_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsJpeg(memoryStream, new JpegEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsJpegAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsJpegAsync(memoryStream, new JpegEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs index b2dc3534d1..0f25d11d4f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs @@ -1,8 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index caaad73c9f..860f9c396c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,8 +8,8 @@ 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.Colorspaces.Conversion; using Xunit; using Xunit.Abstractions; @@ -99,23 +99,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromYCbCrSimdAvx2(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("No AVX2 present, skipping test!"); return; } - //JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); - + // JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrSimdAvx2(8), + new JpegColorConverter.FromYCbCrSimdVector8(8), 3, inputBufferLength, resultBufferLength, seed); } - [Theory] [MemberData(nameof(CommonConversionData))] public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) @@ -129,9 +127,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } // Benchmark, for local execution only - //[Theory] - //[InlineData(false)] - //[InlineData(true)] + // [Theory] + // [InlineData(false)] + // [InlineData(true)] public void BenchmarkYCbCr(bool simd) { int count = 2053; @@ -289,14 +287,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int j = 0; j < inputBufferLength; j++) { - values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } // no need to dispose when buffer is not array owner var memory = new Memory(values); - var source = new MemorySource(memory); + var source = MemoryGroup.Wrap(memory); buffers[i] = new Buffer2D(source, values.Length, 1); } + return new JpegColorConverter.ComponentValues(buffers, 0); } @@ -308,7 +307,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int seed) { ValidateRgbToYCbCrConversion( - JpegColorConverter.GetConverter(colorSpace,8), + JpegColorConverter.GetConverter(colorSpace, 8), componentCount, inputBufferLength, resultBufferLength, @@ -333,4 +332,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ 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 1c9d207cd1..f0a64e6af5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -1,56 +1,58 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; - +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class JpegDecoderTests { [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Jpeg.Baseline.Turtle420, PixelTypes.Rgba32, true)] + public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - if (SkipTest(provider)) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - // skipping to avoid OutOfMemoryException on CI - return; - } + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InPixels(1000 * 8); + } + + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput( - this.GetImageComparer(provider), + GetImageComparer(provider), provider, appendPixelTypeToFileName: false); } - } - [Theory] - [WithFile(TestImages.Jpeg.Issues.CriticalEOF214, PixelTypes.Rgba32)] - public void DecodeBaselineJpeg_CriticalEOF_ShouldThrow(TestImageProvider provider) - where TPixel : struct, IPixel - { - // TODO: We need a public ImageDecoderException class in ImageSharp! - Assert.ThrowsAny(() => provider.GetImage(JpegDecoder)); - } + string providerDump = BasicSerializer.Serialize(provider); + RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); - [Theory] - [WithFile(TestImages.Jpeg.Issues.InvalidJpegThrowsWrongException797, PixelTypes.Rgba32)] - public void LoadingImage_InvalidTagLength_ShouldThrow(TestImageProvider provider) - where TPixel : struct, IPixel => Assert.Throws(() => provider.GetImage()); + // RemoteExecutor.Invoke( + // RunTest, + // providerDump, + // enforceDiscontiguousBuffers ? "Disco" : string.Empty) + // .Dispose(); + } [Theory] - [WithFile(TestImages.Jpeg.Issues.AccessViolationException798, PixelTypes.Rgba32)] - public void LoadingImage_BadHuffman_ShouldNotThrow(TestImageProvider provider) - where TPixel : struct, IPixel => Assert.NotNull(provider.GetImage()); + [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 03f1826edd..c91aad7e74 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -13,14 +13,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Ycck, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Turtle420, TestImages.Jpeg.Baseline.Testorig420, - // BUG: The following image has a high difference compared to the expected output: - // TestImages.Jpeg.Baseline.Jpeg420Small, - + // BUG: The following image has a high difference compared to the expected output: 1.0096% + TestImages.Jpeg.Baseline.Jpeg420Small, + TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, 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, @@ -29,6 +31,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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, + + // LibJpeg can open this despite incorrect colorspace metadata. + TestImages.Jpeg.Issues.IncorrectColorspace855, + + // LibJpeg can open this despite the invalid subsampling units. + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, // High depth images TestImages.Jpeg.Baseline.Testorig12bit, @@ -53,15 +65,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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.NullReferenceException823, + 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 + }; + 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, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Jpeg420Small] = 1.1f / 100, + [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, + // Progressive: [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, @@ -73,4 +113,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, }; } -} \ 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 4810985f11..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs +++ /dev/null @@ -1,259 +0,0 @@ -// 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 } - }; - - [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 new file mode 100644 index 0000000000..e37b26cdbf --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -0,0 +1,266 @@ +// Copyright (c) Six Labors. +// 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 }, + { true, TestImages.Jpeg.Issues.IdentifyMultiFrame1211, 24, true, true }, + }; + + 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.GetJpegMetadata(); + 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.GetJpegMetadata(); + 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.CreateRgba32Image(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); + }); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 77bc9f5404..e29d8f158b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -1,10 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class JpegDecoderTests @@ -12,26 +14,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; [Theory] - [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32, false)] + [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, true)] + public void DecodeProgressiveJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - if (SkipTest(provider)) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - // skipping to avoid OutOfMemoryException on CI - return; - } + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + } + + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider, nonContiguousBuffersStr); provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput( - this.GetImageComparer(provider), + GetImageComparer(provider), provider, appendPixelTypeToFileName: false); } + + string providerDump = BasicSerializer.Serialize(provider); + + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceDiscontiguousBuffers ? "Disco" : string.Empty) + .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 15f7f92a83..78218aec90 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -1,14 +1,18 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; using System.Linq; - +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.Memory; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -23,10 +27,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector; private const float BaselineTolerance = 0.001F / 100; + private const float ProgressiveTolerance = 0.2F / 100; - private ImageComparer GetImageComparer(TestImageProvider provider) - where TPixel : struct, IPixel + private static ImageComparer GetImageComparer(TestImageProvider provider) + where TPixel : unmanaged, IPixel { string file = provider.SourceFileOrDescription; @@ -69,16 +74,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void ParseStream_BasicPropertiesAreCorrect() { 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); - } + using var ms = new MemoryStream(bytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(bufferedStream); + + // 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 string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; @@ -86,54 +90,93 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - if (SkipTest(provider)) - { - return; - } + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); - // For 32 bit test enviroments: - provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); - - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(ImageComparer.Tolerant(BaselineTolerance), provider, appendPixelTypeToFileName: false); - } + provider.Utility.TestName = DecodeBaselineJpegOutputName; + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + appendPixelTypeToFileName: false); + } - provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); + [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)); + this.Output.WriteLine(ex.Message); + Assert.IsType(ex.InnerException); } - private string GetDifferenceInPercentageString(Image image, TestImageProvider provider) - where TPixel : struct, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] + public async Task DecodeAsnc_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - var reportingComparer = ImageComparer.Tolerant(0, 0); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); + InvalidImageContentException ex = await Assert.ThrowsAsync(() => provider.GetImageAsync(JpegDecoder)); + this.Output.WriteLine(ex.Message); + Assert.IsType(ex.InnerException); + } - ImageSimilarityReport report = image.GetReferenceOutputSimilarityReports( - provider, - reportingComparer, - appendPixelTypeToFileName: false - ).SingleOrDefault(); + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, 0)] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 1)] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 10)] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 30)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 1)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 10)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 30)] + public async Task Decode_IsCancellable(string fileName, int cancellationDelayMs) + { + // Decoding these huge files took 300ms on i7-8650U in 2020. 30ms should be safe for cancellation delay. + string hugeFile = Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + fileName); - if (report?.TotalNormalizedDifference != null) + var cts = new CancellationTokenSource(); + if (cancellationDelayMs == 0) { - return report.DifferencePercentageString; + cts.Cancel(); } + else + { + cts.CancelAfter(cancellationDelayMs); + } + + await Assert.ThrowsAsync(() => Image.LoadAsync(hugeFile, cts.Token)); + } - return "0%"; + [Theory(Skip = "Identify is too fast, doesn't work reliably.")] + [InlineData(TestImages.Jpeg.Baseline.Exif)] + [InlineData(TestImages.Jpeg.Progressive.Bad.ExifUndefType)] + public async Task Identify_IsCancellable(string fileName) + { + string file = Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + fileName); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromTicks(1)); + await Assert.ThrowsAsync(() => Image.IdentifyAsync(file, cts.Token)); } - + // 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, + // [Theory] + // [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] + public void ValidateProgressivePdfJsOutput( + TestImageProvider provider, string pdfJsOriginalResultImage) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm string pdfJsOriginalResultPath = Path.Combine( @@ -147,8 +190,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var comparer = ImageComparer.Tolerant(0, 0); using (Image expectedImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) - using (var pdfJsOriginalResult = Image.Load(pdfJsOriginalResultPath)) - using (var pdfJsPortResult = Image.Load(sourceBytes, JpegDecoder)) + 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); @@ -158,4 +201,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 598d99274a..981270a5fb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,9 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.MetaData; +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.Iptc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -15,31 +23,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class JpegEncoderTests { public static readonly TheoryData QualityFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Calliphora, 80}, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; + new TheoryData + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; public static readonly TheoryData BitsPerPixel_Quality = - new TheoryData - { - { JpegSubsample.Ratio420, 40 }, - { JpegSubsample.Ratio420, 60 }, - { JpegSubsample.Ratio420, 100 }, - - { JpegSubsample.Ratio444, 40 }, - { JpegSubsample.Ratio444, 60 }, - { JpegSubsample.Ratio444, 100 }, - }; + new TheoryData + { + { JpegSubsample.Ratio420, 40 }, + { JpegSubsample.Ratio420, 60 }, + { JpegSubsample.Ratio420, 100 }, + { JpegSubsample.Ratio444, 40 }, + { JpegSubsample.Ratio444, 60 }, + { JpegSubsample.Ratio444, 100 }, + }; 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 } - }; + 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 } + }; [Theory] [MemberData(nameof(QualityFiles))] @@ -48,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var options = new JpegEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { @@ -57,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg memStream.Position = 0; using (var output = Image.Load(memStream)) { - JpegMetaData meta = output.MetaData.GetFormatMetaData(JpegFormat.Instance); + JpegMetadata meta = output.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality); } } @@ -72,13 +79,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality) - where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); + where TPixel : unmanaged, 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); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, subsample, quality); + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegSubsample.Ratio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegSubsample.Ratio444)] + [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegSubsample.Ratio420)] + [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegSubsample.Ratio420)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegSubsample subsample) + where TPixel : unmanaged, IPixel + { + ImageComparer comparer = subsample == JpegSubsample.Ratio444 + ? ImageComparer.TolerantPercentage(0.1f) + : ImageComparer.TolerantPercentage(5f); + + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + TestJpegEncoderCore(provider, subsample, 100, comparer); + } /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation @@ -106,25 +130,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void TestJpegEncoderCore( TestImageProvider provider, JpegSubsample subsample, - int quality = 100) - where TPixel : struct, IPixel + int quality = 100, + ImageComparer comparer = null) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + using Image image = provider.GetImage(); + + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder { - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); + Subsample = subsample, + Quality = quality + }; + string info = $"{subsample}-Q{quality}"; - 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"); - } + comparer ??= GetComparer(quality, subsample); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } [Fact] @@ -137,7 +162,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) using (var memStream0 = new MemoryStream()) using (var memStream1 = new MemoryStream()) { @@ -160,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) using (var memStream0 = new MemoryStream()) using (var memStream1 = new MemoryStream()) { @@ -180,7 +205,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var options = new JpegEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { @@ -189,7 +214,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg memStream.Position = 0; using (var output = Image.Load(memStream)) { - ImageMetaData meta = output.MetaData; + ImageMetadata meta = output.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits); @@ -197,5 +222,95 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } + + [Fact] + public void Encode_PreservesIptcProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IptcProfile = new IptcProfile(); + input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); + var encoder = new JpegEncoder(); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IptcProfile actual = output.Metadata.IptcProfile; + Assert.NotNull(actual); + IEnumerable values = input.Metadata.IptcProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesExifProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.ExifProfile = new ExifProfile(); + input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); + var encoder = new JpegEncoder(); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var 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 var input = new Image(1, 1); + input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); + var encoder = new JpegEncoder(); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IccProfile actual = output.Metadata.IccProfile; + Assert.NotNull(actual); + IccProfile values = input.Metadata.IccProfile; + Assert.Equal(values.Entries, actual.Entries); + } + + [Theory] + [InlineData(JpegSubsample.Ratio420, 0)] + [InlineData(JpegSubsample.Ratio420, 3)] + [InlineData(JpegSubsample.Ratio420, 10)] + [InlineData(JpegSubsample.Ratio444, 0)] + [InlineData(JpegSubsample.Ratio444, 3)] + [InlineData(JpegSubsample.Ratio444, 10)] + public async Task Encode_IsCancellable(JpegSubsample subsample, int cancellationDelayMs) + { + using var image = new Image(5000, 5000); + using MemoryStream stream = new MemoryStream(); + var cts = new CancellationTokenSource(); + if (cancellationDelayMs == 0) + { + cts.Cancel(); + } + else + { + cts.CancelAfter(cancellationDelayMs); + } + + var encoder = new JpegEncoder() { Subsample = subsample }; + await Assert.ThrowsAsync(() => image.SaveAsync(stream, encoder, cts.Token)); + } } -} \ 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 42eea2708b..79e3c448f1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index b3219115db..0dd2abcc1f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg; @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } private static void SaveBuffer(JpegComponentPostProcessor cp, TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = cp.ColorBuffer.ToGrayscaleImage(1f / 255f)) { @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void DoProcessorStep(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string imageFile = provider.SourceFileOrDescription; using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) @@ -60,18 +60,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg SaveBuffer(cp[2], provider); } } - + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] public void PostProcess(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, 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); + pp.PostProcess(image.Frames.RootFrame, default); image.DebugSave(provider); @@ -93,4 +93,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ 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 431de4be31..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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 new file mode 100644 index 0000000000..98558a7265 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// 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)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs index 3d09f4b383..a267d437bc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System.IO; using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void RunDumpJpegCoeffsTool() { - if (!TestEnvironment.IsWindows) return; + if (!TestEnvironment.IsWindows) + { + return; + } string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); @@ -27,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)] public void ExtractSpectralData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.IsWindows) { @@ -49,4 +55,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ 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 3657110c6c..94e3af3a97 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Text; @@ -7,7 +7,6 @@ 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 SixLabors.Primitives; using Xunit; using Xunit.Abstractions; @@ -30,11 +29,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)] public void ColorSpace_IsDeducedCorrectly(string imageFile, object expectedColorSpaceValue) { - var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue; + var expectedColorSpace = (JpegColorSpace)expectedColorSpaceValue; using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) { - Assert.Equal(expecteColorSpace, decoder.ColorSpace); + Assert.Equal(expectedColorSpace, decoder.ColorSpace); } } @@ -77,15 +76,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); } + this.Output.WriteLine(sb.ToString()); } - public static readonly TheoryData ComponentVerificationData = new TheoryData() + 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) }, @@ -131,4 +132,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ 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 c908abc505..0c17ac7dbe 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Text; @@ -19,25 +19,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void ProfileResolverHasCorrectJFifMarker() { - Assert.Equal(JFifMarker, ProfileResolver.JFifMarker); + Assert.Equal(JFifMarker, ProfileResolver.JFifMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectExifMarker() { - Assert.Equal(ExifMarker, ProfileResolver.ExifMarker); + Assert.Equal(ExifMarker, ProfileResolver.ExifMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectIccMarker() { - Assert.Equal(IccMarker, ProfileResolver.IccMarker); + Assert.Equal(IccMarker, ProfileResolver.IccMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectAdobeMarker() { - Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker); + Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker.ToArray()); } [Fact] @@ -76,4 +76,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs index dd2113624e..bfa0966af4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void ForwardThenInverse(int seed) { - float[] data = JpegFixture.Create8x8RandomFloatData(-1000, 1000, seed); + float[] data = Create8x8RandomFloatData(-1000, 1000, seed); var b0 = default(Block8x8F); b0.LoadFrom(data); @@ -36,4 +36,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ 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 ce6f0a744f..770f0cbf71 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming @@ -25,14 +25,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2, 0)] public void LLM_ForwardThenInverse(int seed, int startAt) { - int[] data = JpegFixture.Create8x8RandomIntData(-1000, 1000, seed); + 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); + 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); } @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2, 200)] public void LLM_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) { - float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-range, range, seed); + float[] sourceArray = Create8x8RoundedRandomFloatData(-range, range, seed); var source = Block8x8F.Load(sourceArray); @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void LLM_FDCT_IsEquivalentTo_AccurateImplementation(int seed) { - float[] floatData = JpegFixture.Create8x8RandomFloatData(-1000, 1000); + float[] floatData = Create8x8RandomFloatData(-1000, 1000); Block8x8F source = default; source.LoadFrom(floatData); @@ -107,17 +107,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2, 200)] public void GT_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) { - int[] intData = JpegFixture.Create8x8RandomIntData(-range, range, seed); + 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); + 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 f299807fc7..8bf1d7155c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; @@ -8,6 +9,7 @@ using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class ReferenceImplementationsTests @@ -64,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2, 0)] public void ForwardThenInverse(int seed, int startAt) { - Span original = JpegFixture.Create8x8RandomIntData(-200, 200, seed); + Span original = Create8x8RandomIntData(-200, 200, seed); Span block = original.AddScalarToAllValues(128); @@ -80,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int i = startAt; i < 64; i++) { float expected = original[i]; - float actual = (float)block[i]; + float actual = block[i]; 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 0276e17085..d3077a6e3f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using Xunit.Abstractions; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index d5a1fb7ba0..662ea9e330 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,15 +1,20 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public class SpectralJpegTests @@ -41,25 +46,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory(Skip = "Debug only, enable manually!")] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - using (var ms = new MemoryStream(sourceBytes)) - { - decoder.ParseStream(ms); + using var ms = new MemoryStream(sourceBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + decoder.ParseStream(bufferedStream); - var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - VerifyJpeg.SaveSpectralImage(provider, data); - } + var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + VerifyJpeg.SaveSpectralImage(provider, data); } [Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void VerifySpectralCorrectness(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.IsWindows) { @@ -70,19 +74,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - using (var ms = new MemoryStream(sourceBytes)) - { - decoder.ParseStream(ms); - var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + using var ms = new MemoryStream(sourceBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + decoder.ParseStream(bufferedStream); - this.VerifySpectralCorrectnessImpl(provider, imageSharpData); - } + var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + this.VerifySpectralCorrectnessImpl(provider, imageSharpData); } - + private void VerifySpectralCorrectnessImpl( TestImageProvider provider, LibJpegTools.SpectralData imageSharpData) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); @@ -110,8 +113,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.MemorySource.GetSpan().Length; + tolerance += libJpegComponent.SpectralBlocks.GetSingleSpan().Length; } + averageDifference /= componentCount; tolerance /= 64; // fair enough? @@ -123,4 +127,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.True(totalDifference < tolerance); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index 89fdd5745e..c6f4704f05 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Diagnostics; using System.IO; @@ -10,62 +8,68 @@ using System.Text; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; - +using SixLabors.ImageSharp.IO; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { public class JpegFixture : MeasureFixture { - public JpegFixture(ITestOutputHelper output) : base(output) + public JpegFixture(ITestOutputHelper output) + : base(output) { } // ReSharper disable once InconsistentNaming public static float[] Create8x8FloatData() { - float[] result = new float[64]; + var 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; + result[(i * 8) + j] = (i * 10) + j; } } + return result; } // ReSharper disable once InconsistentNaming public static int[] Create8x8IntData() { - int[] result = new int[64]; + var result = new int[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = i * 10 + j; + result[(i * 8) + j] = (i * 10) + j; } } + return result; } // ReSharper disable once InconsistentNaming public static short[] Create8x8ShortData() { - short[] result = new short[64]; + var result = new short[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { - short val = (short)(i * 10 + j); + 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; } @@ -73,14 +77,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) { var rnd = new Random(seed); - int[] result = new int[64]; + var result = new int[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = rnd.Next(minValue, maxValue); + result[(i * 8) + j] = rnd.Next(minValue, maxValue); } } + return result; } @@ -90,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static float[] Create8x8RandomFloatData(float minValue, float maxValue, int seed = 42) { var rnd = new Random(seed); - float[] result = new float[64]; + var result = new float[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) @@ -99,9 +104,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils val *= maxValue - minValue; val += minValue; - result[i * 8 + j] = (float)val; + result[(i * 8) + j] = (float)val; } } + return result; } @@ -120,8 +126,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - bld.Append($"{data[i * 8 + j],3} "); + bld.Append($"{data[(i * 8) + j],3} "); } + bld.AppendLine(); } @@ -132,13 +139,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal void PrintLinearData(Span data, int count = -1) { - if (count < 0) 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()); } @@ -181,12 +192,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal static JpegDecoderCore ParseJpegStream(string testFileName, bool metaDataOnly = false) { byte[] bytes = TestFile.Create(testFileName).Bytes; - using (var ms = new MemoryStream(bytes)) - { - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); - decoder.ParseStream(ms, metaDataOnly); - return decoder; - } + using var ms = new MemoryStream(bytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(bufferedStream, metaDataOnly); + + 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 e4fcd10c5f..6f6032ee2e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,7 +9,6 @@ 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 { @@ -52,8 +51,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal void MakeBlock(short[] data, int y, int x) { - this.MinVal = Math.Min((short)this.MinVal, data.Min()); - this.MaxVal = Math.Max((short)this.MaxVal, data.Max()); + this.MinVal = Math.Min(this.MinVal, data.Min()); + this.MaxVal = Math.Max(this.MaxVal, data.Max()); this.SpectralBlocks[x, y] = new Block8x8(data); } @@ -62,8 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils var result = new ComponentData( c.WidthInBlocks, c.HeightInBlocks, - index - ); + index); for (int y = 0; y < result.HeightInBlocks; y++) { @@ -89,6 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.WriteToImage(bx, by, result); } } + return result; } @@ -106,8 +105,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Rgba32 color = default; color.FromVector4(v); - int yy = by * 8 + y; - int xx = bx * 8 + x; + int yy = (by * 8) + y; + int xx = (bx * 8) + x; image[xx, yy] = color; } } @@ -115,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal float GetBlockValue(Block8x8 block, int x, int y) { - float d = (this.MaxVal - this.MinVal); + float d = this.MaxVal - this.MinVal; float val = block[y, x]; val -= this.MinVal; val /= d; @@ -136,9 +135,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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; + if (!ok) + { + return false; + } for (int y = 0; y < this.HeightInBlocks; y++) { @@ -146,31 +146,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { Block8x8 a = this.SpectralBlocks[x, y]; Block8x8 b = other.SpectralBlocks[x, y]; - if (!a.Equals(b)) return false; + if (!a.Equals(b)) + { + return false; + } } } + return true; } public override bool Equals(object obj) { - if (obj is null) return false; - if (object.ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) 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); } public override int GetHashCode() { - unchecked - { - 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 HashCode.Combine(this.Index, this.HeightInBlocks, this.WidthInBlocks, this.MinVal, this.MaxVal); } public ref Block8x8 GetBlockReference(int column, int row) @@ -180,13 +188,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static bool operator ==(ComponentData left, ComponentData right) { - return Object.Equals(left, right); + return object.Equals(left, right); } public static bool operator !=(ComponentData left, ComponentData right) { - return !Object.Equals(left, right); + return !object.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 f5618d26d2..6ed7c15aed 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,11 +12,10 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - internal static partial class LibJpegTools { /// - /// Stores spectral jpeg compoent data in libjpeg-compatible style. + /// Stores spectral jpeg component data in libjpeg-compatible style. /// public class SpectralData : IEquatable { @@ -40,7 +39,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public Image TryCreateRGBSpectralImage() { - if (this.ComponentCount != 3) return null; + if (this.ComponentCount != 3) + { + return null; + } LibJpegTools.ComponentData c0 = this.Components[0]; LibJpegTools.ComponentData c1 = this.Components[1]; @@ -60,6 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.WriteToImage(bx, by, result); } } + return result; } @@ -73,9 +76,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Block8x8 block1 = c1.SpectralBlocks[bx, by]; Block8x8 block2 = c2.SpectralBlocks[bx, by]; - float d0 = (c0.MaxVal - c0.MinVal); - float d1 = (c1.MaxVal - c1.MinVal); - float d2 = (c2.MaxVal - c2.MinVal); + float d0 = c0.MaxVal - c0.MinVal; + float d1 = c1.MaxVal - c1.MinVal; + float d2 = c2.MaxVal - c2.MinVal; for (int y = 0; y < 8; y++) { @@ -89,8 +92,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Rgba32 color = default; color.FromVector4(v); - int yy = by * 8 + y; - int xx = bx * 8 + x; + int yy = (by * 8) + y; + int xx = (bx * 8) + x; image[xx, yy] = color; } } @@ -117,8 +120,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { LibJpegTools.ComponentData a = this.Components[i]; LibJpegTools.ComponentData b = other.Components[i]; - if (!a.Equals(b)) return false; + if (!a.Equals(b)) + { + return false; + } } + return true; } @@ -151,4 +158,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ 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 3de4673f5d..60187a860c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -1,8 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + 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; @@ -87,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { TestFile testFile = TestFile.Create(inputFile); - string outDir = TestEnvironment.CreateOutputDirectory(".Temp", $"JpegCoeffs"); + string outDir = TestEnvironment.CreateOutputDirectory(".Temp", "JpegCoeffs"); string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; string coeffFileFullPath = Path.Combine(outDir, fn); @@ -109,7 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils result[i] = resultComponent; } - byte[] buffer = new byte[64 * sizeof(short)]; + var buffer = new byte[64 * sizeof(short)]; for (int i = 0; i < result.Length; i++) { @@ -139,4 +142,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ 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 2712d1933c..e70bdc8cc2 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -14,12 +17,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /* 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 { - private static double[,] CosLut = InitCosLut(); - + private static readonly double[,] CosLut = InitCosLut(); + public static Block8x8 TransformIDCT(ref Block8x8 block) { Block8x8F temp = block.AsFloatBlock(); @@ -29,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static void TransformIDCTInplace(Span span) { - var temp = new Block8x8(); + var temp = default(Block8x8); temp.LoadFrom(span); Block8x8 result = TransformIDCT(ref temp); result.CopyTo(span); @@ -44,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static void TransformFDCTInplace(Span span) { - var temp = new Block8x8(); + var temp = default(Block8x8); temp.LoadFrom(span); Block8x8 result = TransformFDCT(ref temp); result.CopyTo(span); @@ -56,22 +59,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils double tmp, tmp2; Block8x8F res = default; - for (y=0; y<8; y++) { - for (x=0; x<8; x++) { + for (y = 0; y < 8; y++) + { + for (x = 0; x < 8; x++) + { tmp = 0.0; - for (v=0; v<8; v++) { + for (v = 0; v < 8; v++) + { tmp2 = 0.0; - for (u=0; u<8; u++) { - tmp2 += (double) block[v * 8 + u] * CosLut[x, u]; + for (u = 0; u < 8; u++) + { + tmp2 += block[(v * 8) + u] * CosLut[x, u]; } + tmp += CosLut[y, v] * tmp2; } - res[y * 8 + x] = (float)tmp; + + res[(y * 8) + x] = (float)tmp; } } + return res; } - + public static Block8x8F TransformFDCT(ref Block8x8F block) { int x, y, u, v; @@ -88,14 +98,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils tmp2 = 0.0; for (x = 0; x < 8; x++) { - tmp2 += (double)block[y * 8 + x] * CosLut[x,u]; + tmp2 += block[(y * 8) + x] * CosLut[x, u]; } + tmp += CosLut[y, v] * tmp2; } - res[v * 8 + u] = (float) tmp; + + res[(v * 8) + u] = (float)tmp; } } - + return res; } @@ -106,17 +118,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils double tmp; for (a = 0; a < 8; a++) - for (b = 0; b < 8; b++) { - tmp = Math.Cos((double)((a + a + 1) * b) * (3.14159265358979323846 / 16.0)); - if (b == 0) + for (b = 0; b < 8; b++) { - tmp /= Math.Sqrt(2.0); + tmp = Math.Cos((a + a + 1) * b * (3.14159265358979323846 / 16.0)); + if (b == 0) + { + tmp /= Math.Sqrt(2.0); + } + + coslu[a, b] = tmp * 0.5; } - coslu[a, b] = tmp * 0.5; } + 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 3742e45bdc..6bb76ac190 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,7 +1,9 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations @@ -9,24 +11,24 @@ 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 { - public static void idct81d_GT(Span src, Span dst) + public static void Idct81d_GT(Span src, Span dst) { 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 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 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; @@ -41,29 +43,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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[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[7] = (0.25f * (mx09 + mx0b)) - (0.353553390593274f * mx0a); dst = dst.Slice(8); src = src.Slice(8); } } - public static void iDCT8x8GT(Span s, Span d) + public static void IDCT8x8GT(Span s, Span d) { - idct81d_GT(s, d); + Idct81d_GT(s, d); Transpose8x8(d); - idct81d_GT(d, d); + Idct81d_GT(d, 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 46f4fe14dc..3d113ffd09 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,4 +1,6 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -7,6 +9,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations @@ -14,14 +17,14 @@ 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, + /// 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. + /// + /// 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! /// @@ -30,11 +33,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static Block8x8F TransformIDCT(ref Block8x8F source) { float[] s = new float[64]; - source.CopyTo(s); + source.ScaledCopyTo(s); float[] d = new float[64]; float[] temp = new float[64]; - iDCT2D_llm(s, d, temp); + IDCT2D_llm(s, d, temp); Block8x8F result = default; result.LoadFrom(d); return result; @@ -43,11 +46,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) { float[] s = new float[64]; - source.CopyTo(s); + source.ScaledCopyTo(s); float[] d = new float[64]; float[] temp = new float[64]; - fDCT2D_llm(s, d, temp); + FDCT2D_llm(s, d, temp); Block8x8F result = default; result.LoadFrom(d); return result; @@ -64,9 +67,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils float[] r = new float[8]; for (int i = 0; i < 8; i++) { - r[i] = (float)(Cos((double)i / 16.0 * M_PI) * M_SQRT2); + r[i] = (float)(Cos(i / 16.0 * M_PI) * M_SQRT2); output?.WriteLine($"float r{i} = {r[i]:R}f;"); } + return r; } @@ -75,15 +79,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 /// - /// - /// - private static void iDCT1Dllm_32f(Span y, Span x) + 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; @@ -101,19 +102,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils z0 = z0 * (-r3 + r7); z1 = z1 * (-r3 - r1); - z2 = z2 * (-r3 - r5) + z4; - z3 = z3 * (-r3 + r5) + z4; + 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; + 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); + z2 = z4 - (y[6] * (r2 + r6)); + z3 = z4 + (y[2] * (r2 - r6)); a0 = z0 + z3; a3 = z0 - z3; a1 = z1 + z2; @@ -131,25 +132,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// 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" + /// 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) + internal static void IDCT2D_llm(Span s, Span d, Span temp) { int j; for (j = 0; j < 8; j++) { - iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); + 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)); + IDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); @@ -168,27 +166,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Source /// Destination - public static void fDCT2D8x4_32f(Span s, Span d) + 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); + 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); + 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); + 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); + Vector4 t3 = c0 + c1; + Vector4 t4 = c0 - c1; /* c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; @@ -197,19 +195,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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; + 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, 0, c0 + c1); - _mm_store_ps(d, 32, (c0 - c1)); + _mm_store_ps(d, 32, c0 - c1); /*y[0] = c0 + c1; y[4] = c0 - c1;*/ @@ -217,9 +215,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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, 16, (w0 * c2) + (w1 * c3)); - _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); + _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]; @@ -227,8 +225,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils w0 = new Vector4(1.175876f); w1 = new Vector4(0.785695f); - c3 = ((w0 * t4) + (w1 * t7)); - c0 = ((w0 * t7) - (w1 * t4)); + c3 = (w0 * t4) + (w1 * t7); + c0 = (w0 * t7) - (w1 * t4); /* c3 = t4 * r[3] + t7 * r[5]; c0 = t7 * r[3] - t4 * r[5]; @@ -236,78 +234,83 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils w0 = new Vector4(1.387040f); w1 = new Vector4(0.275899f); - c2 = ((w0 * t5) + (w1 * t6)); - c1 = ((w0 * t6) - (w1 * t5)); + 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, 24, c0 - c2); - _mm_store_ps(d, 40, (c3 - c1)); - //y[5] = c3 - c1; y[3] = 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)); + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; - _mm_store_ps(d, 56, (c0 - c3)); - //y[1] = c0 + c3; y[7] = c0 - c3; + // 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; }*/ } - public static void fDCT8x8_llm_sse(Span s, Span d, Span temp) + public static void FDCT8x8_llm_sse(Span s, Span d, Span temp) { - ReferenceImplementations.Transpose8x8(s, temp); + Transpose8x8(s, temp); - fDCT2D8x4_32f(temp, d); + FDCT2D8x4_32f(temp, d); - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - ReferenceImplementations.Transpose8x8(d, temp); + Transpose8x8(d, temp); - fDCT2D8x4_32f(temp, d); + FDCT2D8x4_32f(temp, d); - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); var c = new Vector4(0.1250f); - _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 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 } [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]); } [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; @@ -318,7 +321,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils // 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 Vector4(1.175875602f); private static readonly Vector4 _1_961571 = new Vector4(-1.961570560f); @@ -342,15 +345,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils private static readonly Vector4 _1_847759 = new Vector4(-1.847759065f); private static readonly Vector4 _0_765367 = new Vector4(0.765366865f); +#pragma warning restore SA1309 // Field names should not begin with underscore /// /// 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) + 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; @@ -377,12 +379,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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]; + Vector4 mz4 = (mz0 + mz1) * _1_175876; - mz2 = mz2 * _1_961571 + mz4; - mz3 = mz3 * _0_390181 + mz4; + // 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; @@ -396,10 +398,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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; + 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 @@ -420,8 +422,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils mz0 = my0 + my4; mz1 = my0 - my4; - mz2 = mz4 + my6 * _1_847759; - mz3 = mz4 + my2 * _0_765367; + mz2 = mz4 + (my6 * _1_847759); + mz3 = mz4 + (my2 * _0_765367); my0 = mz0 + mz3; my3 = mz0 - mz3; @@ -462,13 +464,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils */ } - internal static void fDCT1Dllm_32f(Span x, Span y) + 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); } + // 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; @@ -478,9 +480,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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; + 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; @@ -505,13 +507,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y[0] = c0 + c1; y[4] = c0 - c1; - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; + 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]; + 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; @@ -521,7 +523,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y[7] = c0 - c3; } - internal static void fDCT2D_llm( + internal static void FDCT2D_llm( Span s, Span d, Span temp, @@ -532,17 +534,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int j = 0; j < 8; j++) { - fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); + FDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); } - ReferenceImplementations.Transpose8x8(temp, d); + Transpose8x8(temp, d); for (int j = 0; j < 8; j++) { - fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + FDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); } - ReferenceImplementations.Transpose8x8(temp, d); + Transpose8x8(temp, d); if (downscaleBy8) { @@ -554,4 +556,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ 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 18c0bdb50e..45159ba6f4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs @@ -1,26 +1,29 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations { /// /// 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 - /// + /// 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: @@ -34,24 +37,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /* 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 { - 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; + 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 @@ -70,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static Block8x8 Subtract128_TransformFDCT_Upscale8(ref Block8x8 block) { - int[] temp = new int[Block8x8.Size]; + var temp = new int[Block8x8.Size]; block.CopyTo(temp); Subtract128_TransformFDCT_Upscale8_Inplace(temp); var result = default(Block8x8); @@ -81,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils // [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]; + var temp = new int[Block8x8.Size]; block.CopyTo(temp); TransformIDCTInplace(temp); var result = default(Block8x8); @@ -127,25 +130,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; + 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); + 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 = (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; + 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; @@ -177,25 +180,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[x] = (tmp10 + tmp11) >> Pass1Bits; block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; + 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); + 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 = (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; + 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; @@ -204,23 +207,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); } - } - 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) + + 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. @@ -235,7 +238,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// 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) { // Horizontal 1-D IDCT. @@ -270,19 +272,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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); + 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 = W6 * (x3 + x2); + x2 = x1 - (W2pw6 * x2); + x3 = x1 + (W2mw6 * x3); x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; @@ -293,8 +295,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils x8 -= x3; x3 = x0 + x2; x0 -= x2; - x2 = ((r2 * (x4 + x5)) + 128) >> 8; - x4 = ((r2 * (x4 - x5)) + 128) >> 8; + x2 = ((R2 * (x4 + x5)) + 128) >> 8; + x4 = ((R2 * (x4 - x5)) + 128) >> 8; // Stage 4. src[y8 + 0] = (x7 + x1) >> 8; @@ -325,19 +327,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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; + 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 = (W6 * (y3 + y2)) + 4; + y2 = (y1 - (W2pw6 * y2)) >> 3; + y3 = (y1 + (W2mw6 * y3)) >> 3; y1 = y4 + y6; y4 -= y6; y6 = y5 + y7; @@ -348,8 +350,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y8 -= y3; y3 = y0 + y2; y0 -= y2; - y2 = ((r2 * (y4 + y5)) + 128) >> 8; - y4 = ((r2 * (y4 - y5)) + 128) >> 8; + y2 = ((R2 * (y4 + y5)) + 128) >> 8; + y4 = ((R2 * (y4 - y5)) + 128) >> 8; // Stage 4. src[x] = (y7 + y1) >> 14; @@ -364,4 +366,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ 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 f5940e05d4..2c673f30ee 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -1,17 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { /// - /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests + /// 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 @@ -34,7 +33,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Transpose 8x8 block stored linearly in a (inplace) /// - /// internal static void Transpose8x8(Span data) { for (int i = 1; i < 8; i++) @@ -43,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int j = 0; j < i; j++) { float tmp = data[i8 + j]; - data[i8 + j] = data[j * 8 + i]; - data[j * 8 + i] = tmp; + data[i8 + j] = data[(j * 8) + i]; + data[(j * 8) + i] = tmp; } } } @@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils int i8 = i * 8; for (int j = 0; j < 8; j++) { - dest[j * 8 + i] = src[i8 + j]; + dest[(j * 8) + i] = src[i8 + j]; } } } @@ -67,9 +65,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// 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) @@ -128,11 +123,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } /// - /// Rounds a rational number defined as dividend/divisor into an integer + /// Rounds a rational number defined as dividend/divisor into an integer. /// - /// The dividend - /// The divisior - /// + /// The dividend. + /// The divisor. + /// The rounded value. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int RationalRound(int dividend, int divisor) { @@ -144,4 +139,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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 e9527e4c3b..a6d4e917b1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -75,10 +75,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// A new with float values public static float[] ConvertAllToFloat(this int[] src) { - float[] result = new float[src.Length]; + var result = new float[src.Length]; for (int i = 0; i < src.Length; i++) { - result[i] = (float)src[i]; + result[i] = src[i]; } return result; @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// A new instance of public static Span AddScalarToAllValues(this Span src, float scalar) { - float[] result = new float[src.Length]; + var result = new float[src.Length]; for (int i = 0; i < src.Length; i++) { result[i] = src[i] + scalar; @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// A new instance of public static Span AddScalarToAllValues(this Span src, int scalar) { - int[] result = new int[src.Length]; + var result = new int[src.Length]; for (int i = 0; i < src.Length; i++) { result[i] = src[i] + scalar; @@ -118,4 +118,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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 296f424fa5..10717dfcfd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs @@ -1,9 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; @@ -49,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils TestImageProvider provider, LibJpegTools.SpectralData data, ITestOutputHelper output = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (LibJpegTools.ComponentData comp in data.Components) { @@ -72,4 +74,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs new file mode 100644 index 0000000000..cf50ba0b35 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class ZigZagTests + { + [Fact] + public void ZigZagCanHandleAllPossibleCoefficients() + { + // Mimic the behaviour of the huffman scan decoder using all possible byte values + var block = new short[64]; + var zigzag = ZigZag.CreateUnzigTable(); + + 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[zigzag[i++]] = (short)s; + } + else + { + if (r == 0) + { + break; + } + + i += 16; + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs new file mode 100644 index 0000000000..26fbe57c3a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Png.Zlib; +using Xunit; +using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public class Adler32Tests + { + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void 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 MatchesReference(int length) + { + var data = GetBuffer(length); + var adler = new SharpAdler32(); + adler.Update(data); + + long expected = adler.Value; + long actual = Adler32.Calculate(data); + + Assert.Equal(expected, actual); + } + + private static byte[] GetBuffer(int length) + { + var data = new byte[length]; + new Random(1).NextBytes(data); + + return data; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs new file mode 100644 index 0000000000..a9a4ba3182 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Png.Zlib; +using Xunit; +using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public class Crc32Tests + { + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void ReturnsCorrectWhenEmpty(uint input) + { + Assert.Equal(input, Crc32.Calculate(input, default)); + } + + [Theory] + [InlineData(0)] + [InlineData(8)] + [InlineData(215)] + [InlineData(1024)] + [InlineData(1024 + 15)] + [InlineData(2034)] + [InlineData(4096)] + public void MatchesReference(int length) + { + var data = GetBuffer(length); + var crc = new SharpCrc32(); + crc.Update(data); + + long expected = crc.Value; + long actual = Crc32.Calculate(data); + + Assert.Equal(expected, actual); + } + + private static byte[] GetBuffer(int length) + { + var data = new byte[length]; + new Random(1).NextBytes(data); + + return data; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs new file mode 100644 index 0000000000..f0493f1767 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public class ImageExtensionsTest + { + [Fact] + public void SaveAsPng_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPng_Path.png"); + + using (var image = new Image(10, 10)) + { + image.SaveAsPng(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPngAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPngAsync_Path.png"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPngAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPng_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPng_Path_Encoder.png"); + + using (var image = new Image(10, 10)) + { + image.SaveAsPng(file, new PngEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPngAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPngAsync_Path_Encoder.png"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPngAsync(file, new PngEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPng_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsPng(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPngAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPngAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPng_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsPng(memoryStream, new PngEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPngAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPngAsync(memoryStream, new PngEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs index 894d902b78..34014e9777 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System.Buffers.Binary; using System.Text; using SixLabors.ImageSharp.Formats.Png; @@ -11,15 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [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.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")); + 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.SignificantBits, GetType("sBIT")); + Assert.Equal(PngChunkType.Histogram, GetType("hIST")); + Assert.Equal(PngChunkType.SuggestedPalette, GetType("sPLT")); + Assert.Equal(PngChunkType.ProprietaryApple, GetType("CgBI")); } private static PngChunkType GetType(string text) @@ -27,4 +40,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.ASCII.GetBytes(text)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 6a0119f0f1..1ec7e24486 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -1,33 +1,43 @@ -using System.Buffers.Binary; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; using System.IO; using System.Text; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.PixelFormats; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { public partial class PngDecoderTests { + // 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 + // 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 }; @@ -53,8 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [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 + /* [InlineData(PngChunkTypes.Data)] TODO: Figure out how to test this */ public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType) { string chunkName = GetChunkTypeName(chunkType); @@ -68,35 +77,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); ImageFormatException exception = - Assert.Throws(() => decoder.Decode(null, memStream)); + Assert.Throws(() => decoder.Decode(Configuration.Default, memStream)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } } - [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); - - using (var memStream = new MemoryStream()) - { - WriteHeaderChunk(memStream); - WriteChunk(memStream, chunkName); - WriteDataChunk(memStream); - - var decoder = new PngDecoder(); - decoder.Decode(null, memStream); - } - } - private static string GetChunkTypeName(uint value) { - byte[] data = new byte[4]; + var data = new byte[4]; BinaryPrimitives.WriteUInt32BigEndian(data, value); @@ -105,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static void WriteHeaderChunk(MemoryStream memStream) { - // Writes a 1x1 32bit png header chunk containing a single black pixel + // Writes a 1x1 32bit png header chunk containing a single black pixel. memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); } @@ -120,9 +109,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static void WriteDataChunk(MemoryStream memStream) { - // Writes a 1x1 32bit png data chunk containing a single black pixel + // 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 f51f9b6c5c..5b6adfe1af 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,86 +1,72 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - -using System.Buffers.Binary; using System.IO; -using System.Text; +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { public partial class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private static PngDecoder PngDecoder => new PngDecoder(); + public static readonly string[] CommonTestImages = { TestImages.Png.Splash, - TestImages.Png.Indexed, TestImages.Png.FilterVar, - TestImages.Png.Bad.ChunkLength1, - TestImages.Png.Bad.CorruptedChunk, TestImages.Png.VimImage1, + TestImages.Png.VimImage2, 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 - }; - - public static readonly string[] TestImages48Bpp = - { - TestImages.Png.Rgb48Bpp, - TestImages.Png.Rgb48BppInterlaced - }; - public static readonly string[] TestImages64Bpp = -{ - TestImages.Png.Rgba64Bpp, - TestImages.Png.Rgb48BppTrans + TestImages.Png.Bad.ChunkLength1, + TestImages.Png.Bad.ChunkLength2, }; - public static readonly string[] TestImagesGray16Bit = + public static readonly string[] TestImagesIssue1014 = { - TestImages.Png.Gray16Bit, + 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[] TestImagesGrayAlpha16Bit = + public static readonly string[] TestImagesIssue1177 = { - TestImages.Png.GrayAlpha16Bit, - TestImages.Png.GrayTrns16BitInterlaced + TestImages.Png.Issue1177_1, + TestImages.Png.Issue1177_2 }; - public static readonly TheoryData RatioFiles = - new TheoryData + public static readonly string[] CorruptedTestImages = { - { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, - { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, - { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + TestImages.Png.Bad.CorruptedChunk, + TestImages.Png.Bad.ZlibOverflow, + TestImages.Png.Bad.ZlibOverflow2, + TestImages.Png.Bad.ZlibZtxtBadHeader, }; [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -88,11 +74,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] - public void Decode_Interlaced_ImageIsCorrect(TestImageProvider provider) - where TPixel : struct, IPixel + [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(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -100,11 +87,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgb48)] - public void Decode_48Bpp(TestImageProvider provider) - where TPixel : struct, IPixel + [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(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -112,11 +101,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImages64Bpp), PixelTypes.Rgba64)] - public void Decode_64Bpp(TestImageProvider provider) - where TPixel : struct, IPixel + [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(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -124,11 +118,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImagesGray16Bit), PixelTypes.Rgb48)] - public void Decode_Gray16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + [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(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -136,11 +131,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImagesGrayAlpha16Bit), PixelTypes.Rgba64)] - public void Decode_GrayAlpha16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + [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(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -148,65 +144,78 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes)] - public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + [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(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); } } - [Fact] - public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() + [Theory] + [WithFile(TestImages.Png.L16Bit, PixelTypes.Rgb48)] + public void Decode_L16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - var options = new PngDecoder() + using (Image image = provider.GetImage(PngDecoder)) { - IgnoreMetadata = false - }; - - var testFile = TestFile.Create(TestImages.Png.Blur); - - 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); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } } - [Fact] - public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() + [Theory] + [WithFile(TestImages.Png.GrayAlpha16Bit, PixelTypes.Rgba64)] + [WithFile(TestImages.Png.GrayTrns16BitInterlaced, PixelTypes.Rgba64)] + public void Decode_GrayAlpha16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - var options = new PngDecoder() + using (Image image = provider.GetImage(PngDecoder)) { - IgnoreMetadata = true - }; - - var testFile = TestFile.Create(TestImages.Png.Blur); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } - using (Image image = testFile.CreateImage(options)) + [Theory] + [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes)] + public void Decoder_CanDecode_Grey8bitInterlaced_WithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(PngDecoder)) { - Assert.Equal(0, image.MetaData.Properties.Count); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } } - [Fact] - public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + [Theory] + [WithFileCollection(nameof(CorruptedTestImages), PixelTypes.Rgba32)] + public void Decoder_CanDecode_CorruptedImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - var options = new PngDecoder() + using (Image image = provider.GetImage(PngDecoder)) { - TextEncoding = Encoding.Unicode - }; - - var testFile = TestFile.Create(TestImages.Png.Blur); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } - using (Image image = testFile.CreateImage(options)) + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes)] + public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(PngDecoder)) { - Assert.Equal(1, image.MetaData.Properties.Count); - Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } } @@ -228,37 +237,193 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] + public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - 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)) + System.Exception ex = Record.Exception( + () => { - ImageMetaData meta = image.MetaData; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + } + }); + Assert.NotNull(ex); + Assert.Contains("PNG Image does not contain a data chunk", ex.Message); } [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + [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 { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + } + }); + Assert.NotNull(ex); + Assert.Contains("Invalid or unsupported bit depth", ex.Message); + } + + [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 + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + } + }); + Assert.NotNull(ex); + Assert.Contains("Invalid or unsupported color type", ex.Message); + } + + // https://github.com/SixLabors/ImageSharp/issues/1014 + [Theory] + [WithFileCollection(nameof(TestImagesIssue1014), PixelTypes.Rgba32)] + public void Issue1014_DataSplitOverMultipleIDatChunks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + 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 + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + 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 + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + 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 + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + + // 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.Instance); + } + } + }); + Assert.Null(ex); + } + + // 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 + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + + // 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.Instance); + } + } + }); + Assert.NotNull(ex); + Assert.Contains("Proprietary Apple PNG detected!", ex.Message); + } + + [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)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void PngDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - 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); + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + image.CompareToOriginal(provider); } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs new file mode 100644 index 0000000000..31fd676012 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs @@ -0,0 +1,328 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public partial class PngEncoderTests + { + [Fact] + public void HeaderChunk_ComesFirst() + { + // arrange + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, PngEncoder); + + // assert + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.Equal(PngChunkType.Header, type); + } + + [Fact] + public void EndChunk_IsLast() + { + // arrange + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + + // 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.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.False(endChunkFound); + if (type == PngChunkType.End) + { + endChunkFound = true; + } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + } + } + + [Theory] + [InlineData(PngChunkType.Gamma)] + [InlineData(PngChunkType.Chroma)] + [InlineData(PngChunkType.EmbeddedColorProfile)] + [InlineData(PngChunkType.SignificantBits)] + [InlineData(PngChunkType.StandardRgbColourSpace)] + public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj) + { + // arrange + var chunkType = (PngChunkType)chunkTypeObj; + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + + // 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.Slice(0, 4)); + var 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.Slice(4 + 4 + length + 4); + } + } + + [Theory] + [InlineData(PngChunkType.Physical)] + [InlineData(PngChunkType.SuggestedPalette)] + public void Chunk_ComesBeforeIDat(object chunkTypeObj) + { + // arrange + var chunkType = (PngChunkType)chunkTypeObj; + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + + // 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.Slice(0, 4)); + var 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.Slice(4 + 4 + length + 4); + } + } + + [Fact] + public void IgnoreMetadata_WillExcludeAllAncillaryChunks() + { + // arrange + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + var encoder = new PngEncoder() { IgnoreMetadata = true, TextCompressionThreshold = 8 }; + var expectedChunkTypes = new Dictionary() + { + { PngChunkType.Header, false }, + { PngChunkType.Palette, false }, + { PngChunkType.Data, false }, + { PngChunkType.End, false } + }; + var excludedChunkTypes = new List() + { + 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.Slice(0, 4)); + var 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.Slice(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 + var chunkFilter = (PngChunkFilter)filterObj; + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + var encoder = new PngEncoder() { ChunkFilter = chunkFilter, TextCompressionThreshold = 8 }; + var expectedChunkTypes = new Dictionary() + { + { 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 } + }; + var excludedChunkTypes = new List(); + 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.Slice(0, 4)); + var 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.Slice(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 + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + var encoder = new PngEncoder() { ChunkFilter = PngChunkFilter.None, TextCompressionThreshold = 8 }; + var expectedChunkTypes = new List() + { + PngChunkType.Header, + PngChunkType.Gamma, + 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.Slice(0, 4)); + var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.True(expectedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been present"); + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 9079b15fb0..b9f5f16fa5 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -1,23 +1,30 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming +using System.Diagnostics; using System.IO; using System.Linq; - +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics.X86; +#endif +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { - public class PngEncoderTests + public partial class PngEncoderTests { + private static PngEncoder PngEncoder => new PngEncoder(); + public static readonly TheoryData PngBitDepthFiles = new TheoryData { @@ -31,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { 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.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 } @@ -61,9 +68,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// /// All types except Palette /// - public static readonly TheoryData CompressionLevels = new TheoryData + public static readonly TheoryData CompressionLevels + = new TheoryData { - 1, 2, 3, 4, 5, 6, 7, 8, 9 + 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 TheoryData @@ -76,11 +93,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 80, 100, 120, 230 }; + public static readonly PngInterlaceMode[] InterlaceMode = new[] + { + PngInterlaceMode.None, + PngInterlaceMode.Adam7 + }; + public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, - { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; @@ -92,55 +115,68 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [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 + 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 : struct, IPixel + where TPixel : unmanaged, IPixel { - TestPngEncoderCore( + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( provider, pngColorType, PngFilterMethod.Adaptive, PngBitDepth.Bit8, + interlaceMode, appendPixelType: true, appendPngColorType: true); + } } [Theory] [WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)] public void WorksWithAllFilterMethods(TestImageProvider provider, PngFilterMethod pngFilterMethod) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - TestPngEncoderCore( + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( provider, PngColorType.RgbWithAlpha, pngFilterMethod, PngBitDepth.Bit8, + interlaceMode, appendPngFilterMethod: true); + } } [Theory] [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] - public void WorksWithAllCompressionLevels(TestImageProvider provider, int compressionLevel) - where TPixel : struct, IPixel + public void WorksWithAllCompressionLevels(TestImageProvider provider, PngCompressionLevel compressionLevel) + where TPixel : unmanaged, IPixel { - TestPngEncoderCore( + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( provider, PngColorType.RgbWithAlpha, PngFilterMethod.Adaptive, PngBitDepth.Bit8, + interlaceMode, compressionLevel, appendCompressionLevel: true); + } } [Theory] @@ -160,44 +196,152 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [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 + where TPixel : unmanaged, IPixel { - TestPngEncoderCore( - provider, - pngColorType, - PngFilterMethod.Adaptive, - pngBitDepth, - appendPngColorType: true, - appendPixelType: true, - appendPngBitDepth: true); + // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. + if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) + { + return; + } + + foreach (var filterMethod in PngFilterMethods) + { + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( + provider, + pngColorType, + (PngFilterMethod)filterMethod[0], + pngBitDepth, + interlaceMode, + appendPngColorType: true, + appendPixelType: true, + appendPngBitDepth: 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 (var filterMethod in PngFilterMethods) + { + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( + provider, + pngColorType, + (PngFilterMethod)filterMethod[0], + pngBitDepth, + interlaceMode, + appendPngColorType: true, + appendPixelType: true, + appendPngBitDepth: true, + optimizeMethod: PngChunkFilter.ExcludeAll); + } + } + } + + [Theory] + [WithBlankImages(1, 1, PixelTypes.A8, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Argb32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgr565, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgra4444, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Byte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfSingle, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfVector2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfVector4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedByte2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedByte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedShort4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rg32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgba1010102, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.RgbaVector, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Short2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Short4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgb24, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgr24, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgra32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgb48, PngColorType.Rgb, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Bgra5551, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.L8, PngColorType.Grayscale, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.L16, PngColorType.Grayscale, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.La16, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.La32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] + public void InfersColorTypeAndBitDepth(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) + where TPixel : unmanaged, IPixel + { + using (Stream stream = new MemoryStream()) + { + PngEncoder.Encode(provider.GetImage(), stream); + + stream.Seek(0, SeekOrigin.Begin); + + var decoder = new PngDecoder(); + + Image image = decoder.Decode(Configuration.Default, stream); + + PngMetadata metadata = image.Metadata.GetPngMetadata(); + Assert.Equal(pngColorType, metadata.ColorType); + Assert.Equal(pngBitDepth, metadata.BitDepth); + } } [Theory] [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - TestPngEncoderCore( + // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. + if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) + { + return; + } + + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + 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 : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) using (var ms = new MemoryStream()) { - image.Save(ms, new PngEncoder()); + image.Save(ms, PngEncoder); byte[] data = ms.ToArray().Take(8).ToArray(); - byte[] expected = { + byte[] expected = + { 0x89, // Set the high bit. 0x50, // P 0x4E, // N @@ -216,19 +360,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [MemberData(nameof(RatioFiles))] public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - var options = new PngEncoder(); - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, PngEncoder); memStream.Position = 0; using (var output = Image.Load(memStream)) { - ImageMetaData meta = output.MetaData; + ImageMetadata meta = output.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(resolutionUnit, meta.ResolutionUnits); @@ -241,19 +383,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [MemberData(nameof(PngBitDepthFiles))] public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) { - var options = new PngEncoder(); - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, PngEncoder); memStream.Position = 0; using (var output = Image.Load(memStream)) { - PngMetaData meta = output.MetaData.GetFormatMetaData(PngFormat.Instance); + PngMetadata meta = output.Metadata.GetPngMetadata(); Assert.Equal(pngBitDepth, meta.BitDepth); } @@ -261,39 +401,97 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [Theory] + [InlineData(PngColorType.Palette)] + [InlineData(PngColorType.RgbWithAlpha)] + [InlineData(PngColorType.GrayscaleWithAlpha)] + public void Encode_WithPngTransparentColorBehaviorClear_Works(PngColorType colorType) + { + // arrange + var image = new Image(50, 50); + var encoder = new PngEncoder() + { + TransparentColorMode = PngTransparentColorMode.Clear, + ColorType = colorType + }; + Rgba32 rgba32 = Color.Blue; + for (int y = 0; y < image.Height; y++) + { + System.Span rowSpan = image.GetPixelRowSpan(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].FromRgba32(rgba32); + } + } + + // act + using var memStream = new MemoryStream(); + image.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var actual = Image.Load(memStream); + Rgba32 expectedColor = Color.Blue; + if (colorType == PngColorType.Grayscale || colorType == PngColorType.GrayscaleWithAlpha) + { + var luminance = ImageMaths.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B); + expectedColor = new Rgba32(luminance, luminance, luminance); + } + + for (int y = 0; y < actual.Height; y++) + { + System.Span rowSpan = actual.GetPixelRowSpan(y); + + if (y > 25) + { + expectedColor = Color.Transparent; + } + + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(expectedColor, rowSpan[x]); + } + } + } + [Theory] [MemberData(nameof(PngTrnsFiles))] public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) { - var options = new PngEncoder(); - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { - PngMetaData inMeta = input.MetaData.GetFormatMetaData(PngFormat.Instance); - Assert.True(inMeta.HasTrans); + PngMetadata inMeta = input.Metadata.GetPngMetadata(); + Assert.True(inMeta.HasTransparency); using (var memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, PngEncoder); memStream.Position = 0; using (var output = Image.Load(memStream)) { - PngMetaData outMeta = output.MetaData.GetFormatMetaData(PngFormat.Instance); - Assert.True(outMeta.HasTrans); + PngMetadata outMeta = output.Metadata.GetPngMetadata(); + Assert.True(outMeta.HasTransparency); switch (pngColorType) { case PngColorType.Grayscale: if (pngBitDepth.Equals(PngBitDepth.Bit16)) { - Assert.True(outMeta.TransparentGray16.HasValue); - Assert.Equal(inMeta.TransparentGray16, outMeta.TransparentGray16); + Assert.True(outMeta.TransparentL16.HasValue); + Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16); } else { - Assert.True(outMeta.TransparentGray8.HasValue); - Assert.Equal(inMeta.TransparentGray8, outMeta.TransparentGray8); + Assert.True(outMeta.TransparentL8.HasValue); + Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8); } break; @@ -316,20 +514,83 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [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) + { + TestPngEncoderCore( + provider, + PngColorType.Rgb, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); + } + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void EncodeWorksWithoutSsse3Intrinsics(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump) + { + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); +#if SUPPORTS_RUNTIME_INTRINSICS + Assert.False(Ssse3.IsSupported); +#endif + + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( + provider, + PngColorType.Rgb, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); + } + } + + string providerDump = BasicSerializer.Serialize(provider); + + var processStartInfo = new ProcessStartInfo(); + processStartInfo.Environment[TestEnvironment.Features.EnableSSE3] = TestEnvironment.Features.Off; + + RemoteExecutor.Invoke( + RunTest, + providerDump, + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + private static void TestPngEncoderCore( TestImageProvider provider, PngColorType pngColorType, PngFilterMethod pngFilterMethod, PngBitDepth bitDepth, - int compressionLevel = 6, + 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) - where TPixel : struct, IPixel + bool appendPngBitDepth = false, + PngChunkFilter optimizeMethod = PngChunkFilter.None) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -339,7 +600,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png FilterMethod = pngFilterMethod, CompressionLevel = compressionLevel, BitDepth = bitDepth, - Quantizer = new WuQuantizer(paletteSize) + Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), + InterlaceMethod = interlaceMode, + ChunkFilter = optimizeMethod, }; string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; @@ -347,7 +610,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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 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); @@ -355,7 +620,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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. + // occurs 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)) @@ -365,4 +630,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } } -} \ 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 a21bb9acbe..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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 new file mode 100644 index 0000000000..bea116b2bf --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -0,0 +1,295 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public class PngMetadataTests + { + 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 } + }; + + [Fact] + public void CloneIsDeep() + { + var meta = new PngMetadata + { + BitDepth = PngBitDepth.Bit16, + ColorType = PngColorType.GrayscaleWithAlpha, + InterlaceMethod = PngInterlaceMode.Adam7, + Gamma = 2, + TextData = new List { new PngTextData("name", "value", "foo", "bar") } + }; + + var clone = (PngMetadata)meta.DeepClone(); + + clone.BitDepth = PngBitDepth.Bit2; + clone.ColorType = PngColorType.Palette; + clone.InterlaceMethod = PngInterlaceMode.None; + clone.Gamma = 1; + + 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)); + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Decoder_CanReadTextData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) + { + 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 + { + var decoder = new PngDecoder(); + using (Image input = provider.GetImage(decoder)) + using (var memoryStream = new MemoryStream()) + { + input.Save(memoryStream, new PngEncoder()); + + memoryStream.Position = 0; + using (Image image = decoder.Decode(Configuration.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(new PngDecoder())) + { + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("leading space")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("trailing space")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("space")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("empty")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("invalid characters")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("too large")); + } + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Encode_UseCompression_WhenTextIsGreaterThenThreshold_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoder(); + using (Image input = provider.GetImage(decoder)) + using (var memoryStream = new MemoryStream()) + { + // This will be a zTXt chunk. + var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); + + // This will be a iTXt chunk. + var expectedTextNoneLatin = new PngTextData("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 = decoder.Decode(Configuration.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 + { + var decoder = new PngDecoder + { + IgnoreMetadata = false + }; + + using (Image image = provider.GetImage(decoder)) + { + Assert.NotNull(image.Metadata.ExifProfile); + ExifProfile exif = image.Metadata.ExifProfile; + VerifyExifDataIsPresent(exif); + } + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoder + { + IgnoreMetadata = true + }; + + using (Image image = provider.GetImage(decoder)) + { + Assert.Null(image.Metadata.ExifProfile); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead() + { + var options = new PngDecoder + { + IgnoreMetadata = false + }; + + var testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = testFile.CreateRgba32Image(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() + { + var options = new PngDecoder + { + IgnoreMetadata = true + }; + + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + + using (Image image = testFile.CreateRgba32Image(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) + { + 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] + [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); + } + } + + [Theory] + [InlineData(TestImages.Png.PngWithMetadata)] + public void Identify_ReadsTextData(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo 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) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo 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.Equals("Comment") && m.Value.Equals("comment")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") && + m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") && + m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") && + m.LanguageTag.Equals("chinese")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort")); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 81a31e42d3..f1fdd83326 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -1,13 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; +using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { @@ -16,104 +15,104 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void GeneralTest(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - // does saving a file then repoening mean both files are identical??? + // does saving a file then reopening mean both files are identical??? using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) + using (var img2 = Image.Load(ms, new PngDecoder())) { ImageComparer.Tolerant().VerifySimilarity(image, img2); + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); } } } - // 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: 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 : unmanaged, IPixel + { + // does saving a file then reopening 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]); - // } - // } - // } - // } - // } - // } - // } - //} + /* 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 : unmanaged, IPixel + { + // does saving a file then reopening 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]); + } + } + } + } + } + } + } + }*/ [Theory] [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void Resize(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - // does saving a file then repoening mean both files are identical??? + // does saving a file then reopening mean both files are identical??? using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) + using (var 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(provider.Utility.GetTestOutputFileName("png", "resize")); image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) + using (var 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 new file mode 100644 index 0000000000..5080b0b121 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Png; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + /// + /// Tests the class. + /// + public class PngTextDataTests + { + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreEqual() + { + var property1 = new PngTextData("Foo", "Bar", "foo", "bar"); + var property2 = new PngTextData("Foo", "Bar", "foo", "bar"); + + Assert.Equal(property1, property2); + Assert.True(property1 == property2); + } + + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreNotEqual() + { + var property1 = new PngTextData("Foo", "Bar", "foo", "bar"); + var property2 = new PngTextData("Foo", "Foo", string.Empty, string.Empty); + var property3 = new PngTextData("Bar", "Bar", "unit", "test"); + var property4 = new PngTextData("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() + { + var property = new PngTextData("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/Tga/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs new file mode 100644 index 0000000000..3bde32b97f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +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 (var image = new Image(10, 10)) + { + image.SaveAsTga(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsTgaAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsTgaAsync_Path.tga"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsTgaAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsTga_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsTga_Path_Encoder.tga"); + + using (var image = new Image(10, 10)) + { + image.SaveAsTga(file, new TgaEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsTgaAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsTgaAsync_Path_Encoder.tga"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsTgaAsync(file, new TgaEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsTga_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsTga(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsTgaAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsTgaAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsTga_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsTga(memoryStream, new TgaEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsTgaAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsTgaAsync(memoryStream, new TgaEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs new file mode 100644 index 0000000000..5fb15541ec --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -0,0 +1,776 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using Microsoft.DotNet.RemoteExecutor; + +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + using static TestImages.Tga; + + public class TgaDecoderTests + { + private static TgaDecoder TgaDecoder => new TgaDecoder(); + + [Theory] + [WithFile(Gray8BitTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + 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)) + { + 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)) + { + 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)) + { + 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)) + { + 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)) + { + 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)) + { + 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)) + { + 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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + image.DebugSave(provider); + TgaTestUtils.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)) + { + // Using here the reference output instead of the the reference decoder, + // because the reference decoder does not ignore the alpha data here. + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [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)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(TgaDecoder); + 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 new file mode 100644 index 0000000000..22c5d20b31 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + using static TestImages.Tga; + + public class TgaEncoderTests + { + public static readonly TheoryData BitsPerPixel = + new TheoryData + { + TgaBitsPerPixel.Pixel24, + TgaBitsPerPixel.Pixel32 + }; + + public static readonly TheoryData TgaBitsPerPixelFiles = + new TheoryData + { + { Gray8BitBottomLeft, TgaBitsPerPixel.Pixel8 }, + { Bit16BottomLeft, TgaBitsPerPixel.Pixel16 }, + { Bit24BottomLeft, TgaBitsPerPixel.Pixel24 }, + { Bit32BottomLeft, TgaBitsPerPixel.Pixel32 }, + }; + + [Theory] + [MemberData(nameof(TgaBitsPerPixelFiles))] + public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + { + var options = new TgaEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (var 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) + { + var options = new TgaEncoder() + { + Compression = TgaCompression.RunLength + }; + + TestFile testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (var 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.Pixel8) + + // 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.Pixel16) + 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.Pixel24) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit32_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) + 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.Pixel8) + + // 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.Pixel16) + 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.Pixel24) + 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.Pixel32) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)] + public void TgaEncoder_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + } + + 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()) + { + var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression }; + + using (var memStream = new MemoryStream()) + { + image.Save(memStream, encoder); + memStream.Position = 0; + using (var encodedImage = (Image)Image.Load(memStream)) + { + TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs new file mode 100644 index 0000000000..beec043c78 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Tga; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + public class TgaFileHeaderTests + { + [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 var stream = new MemoryStream(data); + + Assert.Throws(() => + { + using (Image.Load(Configuration.Default, stream, out IImageFormat _)) + { + } + }); + } + + [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.Pixel32)] + [InlineData(new byte[] { 26, 1, 9, 0, 0, 0, 1, 16, 0, 0, 0, 0, 128, 0, 128, 0, 8, 0 }, 128, 128, TgaBitsPerPixel.Pixel8)] + [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 220, 0, 16, 0 }, 220, 220, TgaBitsPerPixel.Pixel16)] + [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 24, 32 }, 124, 124, TgaBitsPerPixel.Pixel24)] + public void Identify_WithValidData_Works(byte[] data, int width, int height, TgaBitsPerPixel bitsPerPixel) + { + using var stream = new MemoryStream(data); + + IImageInfo 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/Tga/TgaTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs new file mode 100644 index 0000000000..0f76d99317 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +using ImageMagick; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + public static class TgaTestUtils + { + public static void CompareWithReferenceDecoder( + TestImageProvider provider, + Image image, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + string path = TestImageProvider.GetFilePathOrNull(provider); + if (path == null) + { + throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); + } + + var testFile = TestFile.Create(path); + Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); + if (useExactComparer) + { + ImageComparer.Exact.VerifySimilarity(magickImage, image); + } + else + { + ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); + } + } + + public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) + where TPixel : unmanaged, IPixel + { + using (var magickImage = new MagickImage(fileInfo)) + { + magickImage.AutoOrient(); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + + Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); + + using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); + + PixelOperations.Instance.FromRgba32Bytes( + configuration, + data, + resultPixels, + resultPixels.Length); + } + + return result; + } + } + } +} diff --git a/tests/ImageSharp.Tests/GlobalSuppressions.cs b/tests/ImageSharp.Tests/GlobalSuppressions.cs index 3d161049b9..0411ce883f 100644 --- a/tests/ImageSharp.Tests/GlobalSuppressions.cs +++ b/tests/ImageSharp.Tests/GlobalSuppressions.cs @@ -1,10 +1,12 @@ - -// This file is used by Code Analysis to maintain SuppressMessage +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// 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 +// Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. - +#pragma warning disable SA1404 // Code analysis suppression should have justification [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.")] - +#pragma warning restore SA1404 // Code analysis suppression should have justification diff --git a/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs b/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs new file mode 100644 index 0000000000..21f26cd01c --- /dev/null +++ b/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Threading.Tasks; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class GraphicOptionsDefaultsExtensionsTests + { + [Fact] + public void SetDefaultOptionsOnProcessingContext() + { + var option = new GraphicsOptions(); + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(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() + { + var option = new GraphicsOptions() + { + BlendPercentage = 0.9f + }; + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + context.SetGraphicsOptions(option); + + context.SetGraphicsOptions(o => + { + Assert.Equal(0.9f, o.BlendPercentage); // has origional values + o.BlendPercentage = 0.4f; + }); + + var returnedOption = context.GetGraphicsOptions(); + Assert.Equal(0.4f, returnedOption.BlendPercentage); + Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated + } + + [Fact] + public void SetDefaultOptionsOnConfiguration() + { + var option = new GraphicsOptions(); + var config = new Configuration(); + + config.SetGraphicsOptions(option); + + Assert.Equal(option, config.Properties[typeof(GraphicsOptions)]); + } + + [Fact] + public void UpdateDefaultOptionsOnConfiguration_AlwaysNewInstance() + { + var option = new GraphicsOptions() + { + BlendPercentage = 0.9f + }; + var config = new Configuration(); + config.SetGraphicsOptions(option); + + config.SetGraphicsOptions(o => + { + Assert.Equal(0.9f, o.BlendPercentage); // has origional values + o.BlendPercentage = 0.4f; + }); + + var returnedOption = config.GetGraphicsOptions(); + Assert.Equal(0.4f, returnedOption.BlendPercentage); + Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated + } + + [Fact] + public void GetDefaultOptionsFromConfiguration_SettingNullThenReturnsNewInstance() + { + var config = new Configuration(); + + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + config.SetGraphicsOptions((GraphicsOptions)null); + + var 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() + { + var config = new Configuration(); + + config.Properties[typeof(GraphicsOptions)] = "wronge type"; + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + Assert.IsType(options); + } + + [Fact] + public void GetDefaultOptionsFromConfiguration_AlwaysReturnsInstance() + { + var config = new Configuration(); + + Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + } + + [Fact] + public void GetDefaultOptionsFromConfiguration_AlwaysReturnsSameValue() + { + var config = new Configuration(); + + var options = config.GetGraphicsOptions(); + var options2 = config.GetGraphicsOptions(); + Assert.Equal(options, options2); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstance() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + + var ctxOptions = context.GetGraphicsOptions(); + Assert.NotNull(ctxOptions); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstanceEvenIfSetToNull() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + + context.SetGraphicsOptions((GraphicsOptions)null); + var ctxOptions = context.GetGraphicsOptions(); + Assert.NotNull(ctxOptions); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_FallbackToConfigsInstance() + { + var option = new GraphicsOptions(); + var config = new Configuration(); + config.SetGraphicsOptions(option); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + + var ctxOptions = context.GetGraphicsOptions(); + Assert.Equal(option, ctxOptions); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_IgnoreIncorectlyTypesDictionEntry() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + context.Properties[typeof(GraphicsOptions)] = "wronge type"; + var 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 6ff38626d6..599838d426 100644 --- a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs +++ b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs @@ -1,21 +1,90 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { + private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer(); + private readonly GraphicsOptions newGraphicsOptions = new GraphicsOptions(); + private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); + + [Fact] + public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null); + + [Fact] + public void DefaultGraphicsOptionsAntialias() + { + 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 IsOpaqueColor() + public void DefaultGraphicsOptionsColorBlendingMode() { - 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)); + 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() + { + var expected = new GraphicsOptions + { + 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() + { + var expected = new GraphicsOptions(); + 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); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Helpers/GuardTests.cs b/tests/ImageSharp.Tests/Helpers/GuardTests.cs deleted file mode 100644 index b847e581f5..0000000000 --- a/tests/ImageSharp.Tests/Helpers/GuardTests.cs +++ /dev/null @@ -1,275 +0,0 @@ -// 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 index 018fabd982..27689f6813 100644 --- a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs @@ -1,7 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; + using Xunit; namespace SixLabors.ImageSharp.Tests.Helpers @@ -131,6 +133,23 @@ namespace SixLabors.ImageSharp.Tests.Helpers Assert.Equal(expected, actual); } + [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 + var vector = new Vector4(x, y, z, 0.0f); + + // act + int actual = ImageMaths.GetBT709Luminance(ref vector, luminanceLevels); + + // assert + 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/ParallelExecutionSettingsTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs new file mode 100644 index 0000000000..9166b5ac58 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using Xunit; + +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 + { + var parallelSettings = new ParallelExecutionSettings( + 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 deleted file mode 100644 index ef6b133f75..0000000000 --- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs +++ /dev/null @@ -1,338 +0,0 @@ -// 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.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); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs new file mode 100644 index 0000000000..c93eb41c27 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -0,0 +1,436 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; +using System.Threading; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +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 TheoryData + { + { 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) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(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); + } + + var operation = new TestRowIntervalOperation(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) + { + 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(); + var actualData = new int[maxY]; + + void RowAction(RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(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) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(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); + } + + var operation = new TestRowIntervalOperation(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) + { + 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(); + var actualData = new int[maxY]; + + void RowAction(RowInterval rows, Span buffer) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); + + 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; + + 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); + } + + var operation = new TestRowIntervalOperation(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) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + minimumPixelsProcessedPerTask, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(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); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); + + 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); + + void RowAction(RowInterval rows) + { + this.output.WriteLine(rows.ToString()); + for (int y = rows.Min; y < rows.Max; y++) + { + FillRow(y, actual); + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( + rect, + settings, + in operation); + + // Assert: + TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan()); + } + } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsRequiresValidRectangle(int width, int height) + { + var parallelSettings = default(ParallelExecutionSettings); + + var rect = new Rectangle(0, 0, width, height); + + void RowAction(RowInterval rows) + { + } + + var operation = new TestRowIntervalOperation(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) + { + var parallelSettings = default(ParallelExecutionSettings); + + var rect = new Rectangle(0, 0, width, height); + + void RowAction(RowInterval rows, Span memory) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in operation)); + + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + { + private readonly Action action; + + public TestRowIntervalOperation(Action action) + => this.action = action; + + 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 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 629b3cdeb3..711dd91774 100644 --- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -10,29 +10,53 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class RowIntervalTests { - [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) + [Fact] + public void Slice1() { - using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(width, height)) - { - var rows = new RowInterval(min, max); + var rowInterval = new RowInterval(10, 20); + RowInterval sliced = rowInterval.Slice(5); - Span span = buffer.GetMultiRowSpan(rows); - - ref int expected0 = ref buffer.Span[min * width]; - int expectedLength = (max - min) * width; + Assert.Equal(15, sliced.Min); + Assert.Equal(20, sliced.Max); + } + + [Fact] + public void Slice2() + { + var 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_WhenTrue() + { + var a = new RowInterval(42, 123); + var 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() + { + var a = new RowInterval(42, 123); + var b = new RowInterval(42, 125); + var c = new RowInterval(40, 123); - ref int actual0 = ref span[0]; + Assert.False(a.Equals(b)); + Assert.False(c.Equals(a)); + Assert.False(b.Equals(c)); - Assert.Equal(span.Length, expectedLength); - Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); - } + 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 6c7a1f2752..0dbbaa53f0 100644 --- a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs +++ b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Helpers { public class TolerantMathTests @@ -165,4 +165,4 @@ namespace SixLabors.ImageSharp.Tests.Helpers 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 57e280d938..dfce62ec2c 100644 --- a/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Common.Helpers; diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs index f2e98b131a..c3b8e79ee2 100644 --- a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class Vector4UtilsTests { - private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + private readonly ApproximateFloatComparer approximateFloatComparer = new ApproximateFloatComparer(1e-6f); [Theory] [InlineData(0)] @@ -21,11 +21,15 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => { Vector4Utils.Premultiply(ref v); return v; }).ToArray(); + Vector4[] expected = source.Select(v => + { + Vector4Utilities.Premultiply(ref v); + return v; + }).ToArray(); - Vector4Utils.Premultiply(source); + Vector4Utilities.Premultiply(source); - Assert.Equal(expected, source, this.ApproximateFloatComparer); + Assert.Equal(expected, source, this.approximateFloatComparer); } [Theory] @@ -36,11 +40,15 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => { Vector4Utils.UnPremultiply(ref v); return v; }).ToArray(); + Vector4[] expected = source.Select(v => + { + Vector4Utilities.UnPremultiply(ref v); + return v; + }).ToArray(); - Vector4Utils.UnPremultiply(source); + Vector4Utilities.UnPremultiply(source); - Assert.Equal(expected, source, this.ApproximateFloatComparer); + Assert.Equal(expected, source, this.approximateFloatComparer); } } } diff --git a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs new file mode 100644 index 0000000000..8e73218647 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs @@ -0,0 +1,368 @@ +// Copyright (c) Six Labors. +// 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 BufferedReadStreamTests + { + private readonly Configuration configuration; + + public BufferedReadStreamTests() + { + this.configuration = Configuration.CreateDefaultInstance(); + } + + public static readonly TheoryData BufferSizes = + new TheoryData() + { + 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 = this.CreateTestStream(bufferSize * 3)) + { + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(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 = this.CreateTestStream(bufferSize * 3)) + { + byte[] expected = stream.ToArray(); + int offset = expected.Length / 2; + using (var reader = new BufferedReadStream(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 = this.CreateTestStream(bufferSize * 3)) + { + byte[] expected = stream.ToArray(); + int i; + using (var reader = new BufferedReadStream(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 = this.CreateTestStream(bufferSize * 3)) + { + var buffer = new byte[2]; + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(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 = this.CreateTestStream(bufferSize * 3)) + { + const int increment = 2; + var buffer = new byte[2]; + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(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 = this.CreateTestStream(bufferSize * 3)) + { + const int increment = 2; + Span buffer = new byte[2]; + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(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 = this.CreateTestStream(bufferSize * 4)) + { + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(this.configuration, stream)) + { + int skip = 1; + int plusOne = 1; + int skip2 = bufferSize; + + // 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()); + } + } + } + + [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 = this.CreateTestStream(Math.Max(1, bufferSize / 4))) + { + byte[] expected = stream.ToArray(); + int offset = expected.Length / 2; + using (var reader = new BufferedReadStream(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 = this.CreateTestStream(bufferSize * 3)) + { + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(this.configuration, stream)) + { + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], reader.ReadByte()); + } + } + } + } + + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamThrowsOnBadPosition(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize)) + { + using (var reader = new BufferedReadStream(this.configuration, stream)) + { + Assert.Throws(() => reader.Position = -stream.Length); + Assert.Throws(() => reader.Position = stream.Length + 1); + } + } + } + + [Fact] + public void BufferedStreamCanSetPositionToEnd() + { + var bufferSize = 8; + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 2)) + { + using (var reader = new BufferedReadStream(this.configuration, stream)) + { + reader.Position = reader.Length; + } + } + } + + private MemoryStream CreateTestStream(int length) + { + var buffer = new byte[length]; + var random = new Random(); + 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) + { + return base.Read(buffer, offset, 1); + } + } + } +} diff --git a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs b/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs deleted file mode 100644 index 4fac8d9546..0000000000 --- a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs +++ /dev/null @@ -1,177 +0,0 @@ -// 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/FixedCapacityPooledMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/FixedCapacityPooledMemoryStreamTests.cs new file mode 100644 index 0000000000..0581a6ee2c --- /dev/null +++ b/tests/ImageSharp.Tests/IO/FixedCapacityPooledMemoryStreamTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Linq; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Tests.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.IO +{ + public class FixedCapacityPooledMemoryStreamTests + { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + + [Theory] + [InlineData(1)] + [InlineData(512)] + public void RentsManagedBuffer(int length) + { + MemoryStream ms = this.memoryAllocator.AllocateFixedCapacityMemoryStream(length); + Assert.Equal(length, this.memoryAllocator.AllocationLog.Single().Length); + ms.Dispose(); + Assert.Equal(1, this.memoryAllocator.ReturnLog.Count); + } + + [Theory] + [InlineData(42)] + [InlineData(2999)] + public void UsesRentedBuffer(int length) + { + using MemoryStream ms = this.memoryAllocator.AllocateFixedCapacityMemoryStream(length); + ms.TryGetBuffer(out ArraySegment buffer); + byte[] array = buffer.Array; + Assert.Equal(array.GetHashCode(), this.memoryAllocator.AllocationLog.Single().HashCodeOfBuffer); + + ms.Write(new byte[] { 123 }); + Assert.Equal(123, array[0]); + } + } +} diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs deleted file mode 100644 index 07f1b5cd07..0000000000 --- a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 new file mode 100644 index 0000000000..27aaabee2d --- /dev/null +++ b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// 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/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 82864f1562..5efbe2cba1 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -7,6 +10,24 @@ namespace SixLabors.ImageSharp.Tests { public class ImageCloneTests { + [Fact] + public void CloneAs_WhenDisposed_Throws() + { + var image = new Image(5, 5); + image.Dispose(); + + Assert.Throws(() => image.CloneAs()); + } + + [Fact] + public void Clone_WhenDisposed_Throws() + { + var image = new Image(5, 5); + image.Dispose(); + + Assert.Throws(() => image.Clone()); + } + [Theory] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToBgra32(TestImageProvider provider) @@ -109,4 +130,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs new file mode 100644 index 0000000000..ecbc331b28 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -0,0 +1,328 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +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( + () => + { + 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("Parameter \"frame\" must be not null.", ex.Message); + } + + [Fact] + public void AddNewFrame_PixelBuffer_DataMustNotBeNull() + { + Rgba32[] data = null; + + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.AddFrame(data); + }); + + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); + } + + [Fact] + public void AddNewFrame_PixelBuffer_BufferIncorrectSize() + { + ArgumentOutOfRangeException ex = Assert.Throws( + () => + { + this.Collection.AddFrame(new Rgba32[0]); + }); + + 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( + () => + { + 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("Parameter \"frame\" must be not null.", ex.Message); + } + + [Fact] + public void Constructor_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + 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 : unmanaged, 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); + Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + + cloned.ComparePixelBufferTo(imgSpan); + } + } + } + + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void ExtractFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image img = provider.GetImage()) + { + Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + TPixel[] sourcePixelData = imgSpan.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(Color.HotPink); + + Assert.Equal(2, this.Image.Frames.Count); + this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink); + } + + [Fact] + public void AddFrameFromPixelData() + { + Assert.True(this.Image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imgSpan)); + var pixelData = imgSpan.ToArray(); + this.Image.Frames.AddFrame(pixelData); + Assert.Equal(2, this.Image.Frames.Count); + } + + [Fact] + public void AddFrame_clones_sourceFrame() + { + var otherFrame = new ImageFrame(Configuration.Default, 10, 10); + ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); + + Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); + addedFrame.ComparePixelBufferTo(otherFrameSpan); + Assert.NotEqual(otherFrame, addedFrame); + } + + [Fact] + public void InsertFrame_clones_sourceFrame() + { + var otherFrame = new ImageFrame(Configuration.Default, 10, 10); + ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); + + Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); + addedFrame.ComparePixelBufferTo(otherFrameSpan); + Assert.NotEqual(otherFrame, addedFrame); + } + + [Fact] + public void MoveFrame_LeavesFrameInCorrectLocation() + { + 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_FalseIfNonMember() + { + 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)); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs new file mode 100644 index 0000000000..92109ed479 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -0,0 +1,322 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +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 (var sourceImage = new Image( + this.Image.GetConfiguration(), + this.Image.Width, + this.Image.Height, + Color.Blue)) + { + this.Collection.AddFrame(sourceImage.Frames.RootFrame); + } + + Rgba32[] expectedAllBlue = + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); + + Assert.Equal(2, this.Collection.Count); + var actualFrame = (ImageFrame)this.Collection[1]; + + actualFrame.ComparePixelBufferTo(expectedAllBlue); + } + + [Fact] + public void InsertFrame_OfDifferentPixelType() + { + using (var sourceImage = new Image( + this.Image.GetConfiguration(), + this.Image.Width, + this.Image.Height, + Color.Blue)) + { + this.Collection.InsertFrame(0, sourceImage.Frames.RootFrame); + } + + Rgba32[] expectedAllBlue = + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); + + Assert.Equal(2, this.Collection.Count); + var 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("Parameter \"source\" must be not null.", 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("Parameter \"source\" must be not null.", 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); + + var expectedClone = (Image)cloned; + + Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + expectedClone.ComparePixelBufferTo(imgSpan); + } + } + } + + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void ExtractFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image img = provider.GetImage()) + { + Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + var sourcePixelData = imgSpan.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); + + var expectedClone = (Image)cloned; + expectedClone.ComparePixelBufferTo(sourcePixelData); + } + } + } + + [Fact] + public void CreateFrame_Default() + { + this.Image.Frames.CreateFrame(); + + Assert.Equal(2, this.Image.Frames.Count); + + var 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); + + var frame = (ImageFrame)this.Image.Frames[1]; + + frame.ComparePixelBufferTo(Color.HotPink); + } + + [Fact] + public void MoveFrame_LeavesFrameInCorrectLocation() + { + 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_FalseIfNonMember() + { + 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)); + } + + /// + /// Integration test for end-to end API validation. + /// + /// The pixel type of the image. + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void ConstructGif_FromDifferentPixelTypes(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (var dest = new Image(source.GetConfiguration(), 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, appendSourceFileOrDescription: false, extension: "gif"); + 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.DisposalMethod, bData.DisposalMethod); + Assert.Equal(aData.FrameDelay, bData.FrameDelay); + Assert.Equal(aData.ColorTableLength, bData.ColorTableLength); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs new file mode 100644 index 0000000000..06cd7defc3 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 System.Globalization.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 new file mode 100644 index 0000000000..d4aef75387 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageFrameTests + { + public class Indexer + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + private void LimitBufferCapacity(int bufferCapacityInBytes) + { + var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + Rgba32 val = frame[3, 4]; + Assert.Equal(default(Rgba32), val); + frame[3, 4] = Color.Red; + val = frame[3, 4]; + Assert.Equal(Color.Red.ToRgba32(), val); + } + + public static TheoryData OutOfRangeData = new TheoryData() + { + { 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 var image = new Image(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 var image = new Image(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 var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => frame[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs deleted file mode 100644 index 3923970578..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ /dev/null @@ -1,331 +0,0 @@ -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 deleted file mode 100644 index 041b6c8468..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -// 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 e1c4a419e1..4df8238011 100644 --- a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 3f4cb8afa2..f77fc7921e 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -1,27 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.IO; using SixLabors.ImageSharp.PixelFormats; -using Moq; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - /// - /// Tests the class. + /// Tests the class. /// public class ImageSaveTests : IDisposable { - private readonly Image Image; + private readonly Image image; private readonly Mock fileSystem; private readonly Mock encoder; private readonly Mock encoderNotInFormat; @@ -39,67 +35,66 @@ namespace SixLabors.ImageSharp.Tests this.encoderNotInFormat = new Mock(); this.fileSystem = new Mock(); - var config = new Configuration() + 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); + this.image = new Image(config, 1, 1); } [Fact] public void SavePath() { - Stream stream = new MemoryStream(); + var stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); - this.Image.Save("path.png"); + this.image.Save("path.png"); - this.encoder.Verify(x => x.Encode(this.Image, stream)); + this.encoder.Verify(x => x.Encode(this.image, stream)); } - [Fact] public void SavePathWithEncoder() { - Stream stream = new MemoryStream(); + var stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - this.Image.Save("path.jpg", this.encoderNotInFormat.Object); + 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); + 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() { - Stream stream = new MemoryStream(); - this.Image.Save(stream, this.localImageFormat.Object); + var 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() { - Stream stream = new MemoryStream(); + var 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(); + this.image.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs new file mode 100644 index 0000000000..317a5129c4 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs @@ -0,0 +1,130 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + public class Decode_Cancellation : ImageLoadTestBase + { + private bool isTestStreamSeekable; + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0); + private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0); + private readonly CancellationTokenSource cts = new CancellationTokenSource(); + + public Decode_Cancellation() + { + this.TopLevelConfiguration.StreamProcessingBufferSize = 128; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task LoadAsync_Specific_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Fact] + public async Task LoadAsync_Agnostic_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Fact] + public async Task LoadAsync_Specific_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task IdentifyAsync_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Fact] + public async Task IdentifyAsync_CustomConfiguration_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Fact] + public async Task IdentifyWithFormatAsync_CustomConfiguration_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Fact] + public async Task IdentifyWithFormatAsync_DefaultConfiguration_Stream() + { + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token)); + } + + private async Task DoCancel() + { + // wait until we reach the middle of the steam + await this.notifyWaitPositionReachedSemaphore.WaitAsync(); + + // set the cancellation + this.cts.Cancel(); + + // continue processing the stream + this.continueSemaphore.Release(); + } + + protected override Stream CreateStream() => this.TestFormat.CreateAsyncSamaphoreStream(this.notifyWaitPositionReachedSemaphore, this.continueSemaphore, this.isTestStreamSeekable); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index 9d709d488b..5f3b16806f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -1,14 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.IO; -using Moq; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public partial class ImageTests @@ -24,10 +24,6 @@ namespace SixLabors.ImageSharp.Tests private ReadOnlySpan ActualImageSpan => this.ActualImageBytes.AsSpan(); - private byte[] ByteArray => this.DataStream.ToArray(); - - private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); - private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; private static readonly IImageFormat ExpectedGlobalFormat = @@ -94,6 +90,30 @@ namespace SixLabors.ImageSharp.Tests IImageFormat type = Image.DetectFormat(new Configuration(), this.DataStream); Assert.Null(type); } + + [Fact] + public async Task FromStreamAsync_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + IImageFormat type = await Image.DetectFormatAsync(new AsyncStreamWrapper(stream, () => false)); + Assert.Equal(ExpectedGlobalFormat, type); + } + } + + [Fact] + public async Task FromStreamAsync_CustomConfiguration() + { + IImageFormat type = await Image.DetectFormatAsync(this.LocalConfiguration, new AsyncStreamWrapper(this.DataStream, () => false)); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() + { + IImageFormat type = await Image.DetectFormatAsync(new Configuration(), new AsyncStreamWrapper(this.DataStream, () => false)); + Assert.Null(type); + } } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs new file mode 100644 index 0000000000..72de3fcc44 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -0,0 +1,195 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +// 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 Size(108, 202); + + private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; + + private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; + + private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; + + private static readonly IImageFormat ExpectedGlobalFormat = + Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); + + [Fact] + public void FromBytes_GlobalConfiguration() + { + IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type); + + Assert.Equal(ExpectedImageSize, info.Size()); + Assert.Equal(ExpectedGlobalFormat, type); + } + + [Fact] + public void FromBytes_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.ByteArray, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void FromFileSystemPath_GlobalConfiguration() + { + IImageInfo info = Image.Identify(ActualImagePath, out IImageFormat type); + + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } + + [Fact] + public void FromFileSystemPath_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.MockFilePath, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void FromStream_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + IImageInfo info = Image.Identify(stream, out IImageFormat type); + + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } + } + + [Fact] + public void FromStream_GlobalConfiguration_NoFormat() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + IImageInfo info = Image.Identify(stream); + + Assert.NotNull(info); + } + } + + [Fact] + public void FromStream_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void FromStream_CustomConfiguration_NoFormat() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream); + + Assert.Equal(this.LocalImageInfo, info); + } + + [Fact] + public void WhenNoMatchingFormatFound_ReturnsNull() + { + IImageInfo info = Image.Identify(new Configuration(), this.DataStream, out IImageFormat type); + + Assert.Null(info); + Assert.Null(type); + } + + [Fact] + public async Task FromStreamAsync_GlobalConfiguration_NoFormat() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + var asyncStream = new AsyncStreamWrapper(stream, () => false); + IImageInfo info = await Image.IdentifyAsync(asyncStream); + + Assert.NotNull(info); + } + } + + [Fact] + public async Task FromStreamAsync_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + var asyncStream = new AsyncStreamWrapper(stream, () => false); + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); + + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); + } + } + + [Fact] + public async Task FromPathAsync_CustomConfiguration() + { + IImageInfo info = await Image.IdentifyAsync(this.LocalConfiguration, this.MockFilePath); + Assert.Equal(this.LocalImageInfo, info); + } + + [Fact] + public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() + { + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, this.MockFilePath); + Assert.NotNull(info.ImageInfo); + Assert.Equal(this.LocalImageFormat, info.Format); + } + + [Fact] + public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration() + { + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath); + + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); + } + + [Fact] + public async Task FromPathAsync_GlobalConfiguration() + { + IImageInfo info = await Image.IdentifyAsync(ActualImagePath); + + Assert.Equal(ExpectedImageSize, info.Size()); + } + + [Fact] + public async Task FromStreamAsync_CustomConfiguration() + { + var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, asyncStream); + + Assert.Equal(this.LocalImageInfo, info.ImageInfo); + Assert.Equal(this.LocalImageFormat, info.Format); + } + + [Fact] + public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() + { + var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(new Configuration(), asyncStream); + + Assert.Null(info.ImageInfo); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index aabc3f50e5..44d7daa740 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; - +using System.Threading; using Moq; using SixLabors.ImageSharp.Formats; @@ -16,7 +16,11 @@ namespace SixLabors.ImageSharp.Tests { public abstract class ImageLoadTestBase : IDisposable { - protected Image returnImage; + private Lazy dataStreamLazy; + + protected Image localStreamReturnImageRgba32; + + protected Image localStreamReturnImageAgnostic; protected Mock localDecoder; @@ -24,9 +28,11 @@ namespace SixLabors.ImageSharp.Tests protected Mock localImageFormatMock; + protected Mock localImageInfoMock; + protected readonly string MockFilePath = Guid.NewGuid().ToString(); - internal readonly Mock localFileSystemMock = new Mock(); + internal readonly Mock LocalFileSystemMock = new Mock(); protected readonly TestFileSystem topLevelFileSystem = new TestFileSystem(); @@ -42,18 +48,25 @@ namespace SixLabors.ImageSharp.Tests public byte[] Marker { get; } - public MemoryStream DataStream { get; } + public Stream DataStream => this.dataStreamLazy.Value; public byte[] DecodedData { get; private set; } + protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray(); + protected ImageLoadTestBase() { - this.returnImage = new Image(1, 1); + this.localStreamReturnImageRgba32 = new Image(1, 1); + this.localStreamReturnImageAgnostic = new Image(1, 1); + this.localImageInfoMock = new Mock(); this.localImageFormatMock = new Mock(); - this.localDecoder = new Mock(); - this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); + var detector = new Mock(); + detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); + detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); + + this.localDecoder = detector.As(); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => { @@ -63,30 +76,46 @@ namespace SixLabors.ImageSharp.Tests this.DecodedData = ms.ToArray(); } }) - .Returns(this.returnImage); + .Returns(this.localStreamReturnImageRgba32); + + 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.localStreamReturnImageAgnostic); + + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); - this.LocalConfiguration = new Configuration - { - }; + 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.dataStreamLazy = new Lazy(this.CreateStream); + Stream StreamFactory() => this.DataStream; + + this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(StreamFactory); + this.topLevelFileSystem.AddFile(this.MockFilePath, StreamFactory); + this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object; this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; } public void Dispose() { - // clean up the global object; - this.returnImage?.Dispose(); + // Clean up the global object; + this.localStreamReturnImageRgba32?.Dispose(); + this.localStreamReturnImageAgnostic?.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 7a5fa87290..a4044b906c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -16,18 +16,18 @@ namespace SixLabors.ImageSharp.Tests [InlineData(true)] public void FromPixels(bool useSpan) { - Rgba32[] data = { Rgba32.Black, Rgba32.White, Rgba32.White, Rgba32.Black, }; + Rgba32[] data = { Color.Black, Color.White, Color.White, Color.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(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Rgba32.White, img[1, 0]); - Assert.Equal(Rgba32.Black, img[1, 1]); + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } } @@ -48,13 +48,13 @@ namespace SixLabors.ImageSharp.Tests : Image.LoadPixelData(data, 2, 2)) { Assert.NotNull(img); - Assert.Equal(Rgba32.Black, img[0, 0]); - Assert.Equal(Rgba32.White, img[0, 1]); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Rgba32.White, img[1, 0]); - Assert.Equal(Rgba32.Black, img[1, 1]); + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)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 deleted file mode 100644 index 1a21d3d105..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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 new file mode 100644 index 0000000000..9d4ffdace7 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + public class Load_FileSystemPath_PassLocalConfiguration : ImageLoadTestBase + { + [Fact] + public void Configuration_Path_Specific() + { + var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); + + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void Configuration_Path_Agnostic() + { + var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.SampleAgnostic(), img); + + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void Configuration_Path_Decoder_Specific() + { + 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 Configuration_Path_Decoder_Agnostic() + { + 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 Configuration_Path_OutFormat_Specific() + { + var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, out IImageFormat format); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); + + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void Configuration_Path_OutFormat_Agnostic() + { + var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, out IImageFormat format); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); + + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void WhenFileNotFound_Throws() + { + Assert.Throws( + () => + { + Image.Load(this.TopLevelConfiguration, Guid.NewGuid().ToString()); + }); + } + + [Fact] + public void WhenPathIsNull_Throws() + { + Assert.Throws( + () => + { + Image.Load(this.TopLevelConfiguration, (string)null); + }); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs new file mode 100644 index 0000000000..a5034e43b0 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -0,0 +1,139 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +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 var img = Image.Load(this.Path); + VerifyDecodedImage(img); + } + + [Fact] + public void Path_Agnostic() + { + using var img = Image.Load(this.Path); + VerifyDecodedImage(img); + } + + [Fact] + public async Task Path_Agnostic_Async() + { + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); + } + + [Fact] + public async Task Path_Specific_Async() + { + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); + } + + [Fact] + public async Task Path_Agnostic_Configuration_Async() + { + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); + } + + [Fact] + public void Path_Decoder_Specific() + { + using var img = Image.Load(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); + } + + [Fact] + public void Path_Decoder_Agnostic() + { + using var img = Image.Load(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); + } + + [Fact] + public async Task Path_Decoder_Agnostic_Async() + { + using var img = await Image.LoadAsync(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); + } + + [Fact] + public async Task Path_Decoder_Specific_Async() + { + using var img = await Image.LoadAsync(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); + } + + [Fact] + public void Path_OutFormat_Specific() + { + using var img = Image.Load(this.Path, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); + } + + [Fact] + public void Path_OutFormat_Agnostic() + { + using var img = Image.Load(this.Path, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); + } + + [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() + { + return Assert.ThrowsAsync( + () => Image.LoadAsync(Guid.NewGuid().ToString())); + } + + [Fact] + public Task Async_WhenPathIsNull_Throws() + { + return 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 deleted file mode 100644 index eed1a28252..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes.cs +++ /dev/null @@ -1,120 +0,0 @@ -// 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 new file mode 100644 index 0000000000..320f3696d6 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + public class Load_FromBytes_PassLocalConfiguration : ImageLoadTestBase + { + private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Configuration_Bytes_Specific(bool useSpan) + { + var 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.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Configuration_Bytes_Agnostic(bool useSpan) + { + var img = useSpan + ? Image.Load(this.TopLevelConfiguration, this.ByteSpan) + : Image.Load(this.TopLevelConfiguration, this.ByteArray); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.SampleAgnostic(), img); + + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Configuration_Bytes_Decoder_Specific(bool useSpan) + { + var localFormat = new TestFormat(); + + var img = useSpan ? + Image.Load(this.TopLevelConfiguration, this.ByteSpan, localFormat.Decoder) : + Image.Load(this.TopLevelConfiguration, this.ByteArray, localFormat.Decoder); + + Assert.NotNull(img); + localFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Configuration_Bytes_Decoder_Agnostic(bool useSpan) + { + var localFormat = new TestFormat(); + + var img = useSpan ? + Image.Load(this.TopLevelConfiguration, this.ByteSpan, localFormat.Decoder) : + Image.Load(this.TopLevelConfiguration, this.ByteArray, localFormat.Decoder); + + Assert.NotNull(img); + localFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Configuration_Bytes_OutFormat_Specific(bool useSpan) + { + IImageFormat format; + var img = useSpan ? + Image.Load(this.TopLevelConfiguration, this.ByteSpan, out format) : + Image.Load(this.TopLevelConfiguration, this.ByteArray, out format); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); + + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Configuration_Bytes_OutFormat_Agnostic(bool useSpan) + { + IImageFormat format; + var img = useSpan ? + Image.Load(this.TopLevelConfiguration, this.ByteSpan, out format) : + Image.Load(this.TopLevelConfiguration, this.ByteArray, out format); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); + + 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 new file mode 100644 index 0000000000..0f46bfa5b8 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +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 Span(ByteArray); + + private static void VerifyDecodedImage(Image img) + { + Assert.Equal(new Size(127, 64), img.Size()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Bytes_Specific(bool useSpan) + { + using (var img = useSpan ? Image.Load(ByteSpan) : Image.Load(ByteArray)) + { + VerifyDecodedImage(img); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Bytes_Agnostic(bool useSpan) + { + using (var img = useSpan ? Image.Load(ByteSpan) : Image.Load(ByteArray)) + { + VerifyDecodedImage(img); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Bytes_Decoder_Specific(bool useSpan) + { + using (var img = useSpan ? Image.Load(ByteSpan, new BmpDecoder()) : Image.Load(ByteArray, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Bytes_Decoder_Agnostic(bool useSpan) + { + using (var img = useSpan ? Image.Load(ByteSpan, new BmpDecoder()) : Image.Load(ByteArray, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Bytes_OutFormat_Specific(bool useSpan) + { + IImageFormat format; + using (var img = useSpan ? Image.Load(ByteSpan, out format) : Image.Load(ByteArray, out format)) + { + VerifyDecodedImage(img); + Assert.IsType(format); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Bytes_OutFormat_Agnostic(bool useSpan) + { + IImageFormat format; + using (var img = useSpan ? Image.Load(ByteSpan, out format) : Image.Load(ByteArray, out format)) + { + VerifyDecodedImage(img); + Assert.IsType(format); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream.cs deleted file mode 100644 index 6b6acb1b80..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream.cs +++ /dev/null @@ -1,102 +0,0 @@ -// 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 new file mode 100644 index 0000000000..c7737ef8b4 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + public class Load_FromStream_PassLocalConfiguration : ImageLoadTestBase + { + [Fact] + public void Configuration_Stream_Specific() + { + var img = Image.Load(this.TopLevelConfiguration, this.DataStream); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); + + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void Configuration_Stream_Agnostic() + { + var img = Image.Load(this.TopLevelConfiguration, this.DataStream); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.SampleAgnostic(), img); + + this.TestFormat.VerifyAgnosticDecodeCall(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.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void Configuration_Stream_Decoder_Specific() + { + var 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)); + } + + [Fact] + public void Configuration_Stream_Decoder_Agnostic() + { + var 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)); + } + + [Fact] + public void Configuration_Stream_OutFormat_Specific() + { + var img = Image.Load(this.TopLevelConfiguration, this.DataStream, out IImageFormat format); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); + + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void Configuration_Stream_OutFormat_Agnostic() + { + var img = Image.Load(this.TopLevelConfiguration, this.DataStream, out IImageFormat format); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); + + 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 new file mode 100644 index 0000000000..d462abf7b8 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + public class Load_FromStream_Throws : IDisposable + { + private static readonly byte[] Data = new byte[] { 0x01 }; + + private MemoryStream Stream { get; } = new MemoryStream(Data); + + [Fact] + public void Image_Load_Throws_UnknownImageFormatException() + { + Assert.Throws(() => + { + using (Image.Load(Configuration.Default, this.Stream, out IImageFormat format)) + { + } + }); + } + + [Fact] + public void Image_Load_T_Throws_UnknownImageFormatException() + { + Assert.Throws(() => + { + using (Image.Load(Configuration.Default, this.Stream, out IImageFormat format)) + { + } + }); + } + + 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 new file mode 100644 index 0000000000..1e7c7a5f57 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs @@ -0,0 +1,164 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +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 (var img = Image.Load(this.Stream)) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public void Stream_Agnostic() + { + using (var img = Image.Load(this.Stream)) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public void Stream_OutFormat_Specific() + { + using (var img = Image.Load(this.Stream, out IImageFormat format)) + { + VerifyDecodedImage(img); + Assert.IsType(format); + } + } + + [Fact] + public void Stream_Decoder_Specific() + { + using (var img = Image.Load(this.Stream, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public void Stream_Decoder_Agnostic() + { + using (var img = Image.Load(this.Stream, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public void Stream_OutFormat_Agnostic() + { + using (var img = Image.Load(this.Stream, out IImageFormat format)) + { + VerifyDecodedImage(img); + Assert.IsType(format); + } + } + + [Fact] + public async Task Async_Stream_OutFormat_Agnostic() + { + this.AllowSynchronousIO = false; + var formattedImage = await Image.LoadWithFormatAsync(this.Stream); + using (formattedImage.Image) + { + VerifyDecodedImage(formattedImage.Image); + Assert.IsType(formattedImage.Format); + } + } + + [Fact] + public async Task Async_Stream_Specific() + { + this.AllowSynchronousIO = false; + using (var img = await Image.LoadAsync(this.Stream)) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public async Task Async_Stream_Agnostic() + { + this.AllowSynchronousIO = false; + using (var img = await Image.LoadAsync(this.Stream)) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public async Task Async_Stream_OutFormat_Specific() + { + this.AllowSynchronousIO = false; + var formattedImage = await Image.LoadWithFormatAsync(this.Stream); + using (formattedImage.Image) + { + VerifyDecodedImage(formattedImage.Image); + Assert.IsType(formattedImage.Format); + } + } + + [Fact] + public async Task Async_Stream_Decoder_Specific() + { + this.AllowSynchronousIO = false; + using (var img = await Image.LoadAsync(this.Stream, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public async Task Async_Stream_Decoder_Agnostic() + { + this.AllowSynchronousIO = false; + using (var img = await Image.LoadAsync(this.Stream, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + + 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 45399919a0..ee46807e55 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs @@ -1,13 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; +using System.IO; + +using Moq; + using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { using SixLabors.ImageSharp.Formats; @@ -17,17 +20,17 @@ namespace SixLabors.ImageSharp.Tests public class Save { [Fact] - public void DetecedEncoding() + public void DetectedEncoding() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = System.IO.Path.Combine(dir, "DetecedEncoding.png"); + string file = System.IO.Path.Combine(dir, "DetectedEncoding.png"); using (var image = new Image(10, 10)) { image.Save(file); } - using (var img = Image.Load(file, out IImageFormat mime)) + using (Image.Load(file, out IImageFormat mime)) { Assert.Equal("image/png", mime.DefaultMimeType); } @@ -39,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string file = System.IO.Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); - NotSupportedException ex = Assert.Throws( + Assert.Throws( () => { using (var image = new Image(10, 10)) @@ -60,11 +63,23 @@ namespace SixLabors.ImageSharp.Tests image.Save(file, new PngEncoder()); } - using (var img = Image.Load(file, out var mime)) + using (Image.Load(file, out var mime)) { Assert.Equal("image/png", mime.DefaultMimeType); } } + + [Fact] + public void ThrowsWhenDisposed() + { + var image = new Image(5, 5); + image.Dispose(); + IImageEncoder encoder = Mock.Of(); + using (var stream = new MemoryStream()) + { + Assert.Throws(() => image.Save(stream, encoder)); + } + } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs new file mode 100644 index 0000000000..40c3b65b53 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using Moq; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests +{ + using System.Threading.Tasks; + using SixLabors.ImageSharp.Advanced; + using SixLabors.ImageSharp.Formats; + using SixLabors.ImageSharp.Tests.TestUtilities; + + 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 (var image = new Image(10, 10)) + { + await image.SaveAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task WhenExtensionIsUnknown_Throws() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = System.IO.Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); + + await Assert.ThrowsAsync( + async () => + { + using (var image = new Image(10, 10)) + { + await image.SaveAsync(file); + } + }); + } + + [Fact] + public async Task SetEncoding() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = System.IO.Path.Combine(dir, "SetEncoding.dat"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsync(file, new PngEncoder()); + } + + using (Image.Load(file, out var mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task ThrowsWhenDisposed() + { + var image = new Image(5, 5); + image.Dispose(); + IImageEncoder encoder = Mock.Of(); + using (var stream = new MemoryStream()) + { + await Assert.ThrowsAsync(async () => await image.SaveAsync(stream, encoder)); + } + } + + [Theory] + [InlineData("test.png")] + [InlineData("test.tga")] + [InlineData("test.bmp")] + [InlineData("test.jpg")] + [InlineData("test.gif")] + public async Task SaveAsync_NeverCallsSyncMethods(string filename) + { + using (var image = new Image(5, 5)) + { + IImageEncoder encoder = image.DetectEncoder(filename); + using (var stream = new MemoryStream()) + { + var asyncStream = new AsyncStreamWrapper(stream, () => false); + await image.SaveAsync(asyncStream, encoder); + } + } + } + + [Fact] + public async Task SaveAsync_WithNonSeekableStream_IsCancellable() + { + using var image = new Image(4000, 4000); + var encoder = new PngEncoder() { CompressionLevel = PngCompressionLevel.BestCompression }; + using var stream = new MemoryStream(); + var asyncStream = new AsyncStreamWrapper(stream, () => false); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromTicks(1)); + + await Assert.ThrowsAnyAsync(() => + image.SaveAsync(asyncStream, encoder, cts.Token)); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 16d999ad25..2b30d9459f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,10 +9,9 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Shapes; -using SixLabors.ImageSharp.Processing; using Xunit; // ReSharper disable InconsistentNaming @@ -26,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests /// A exposing the locked pixel memory of a instance. /// TODO: This should be an example in https://github.com/SixLabors/Samples /// - class BitmapMemoryManager : MemoryManager + public class BitmapMemoryManager : MemoryManager { private readonly Bitmap bitmap; @@ -42,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests } this.bitmap = bitmap; - var rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height); + var rectangle = new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height); this.bmpData = bitmap.LockBits(rectangle, ImageLockMode.ReadWrite, bitmap.PixelFormat); this.length = bitmap.Width * bitmap.Height; } @@ -60,13 +59,13 @@ namespace SixLabors.ImageSharp.Tests { this.bitmap.UnlockBits(this.bmpData); } - + this.IsDisposed = true; } public override unsafe Span GetSpan() { - void* ptr = (void*) this.bmpData.Scan0; + void* ptr = (void*)this.bmpData.Scan0; return new Span(ptr, this.length); } @@ -85,18 +84,19 @@ namespace SixLabors.ImageSharp.Tests public void WrapMemory_CreatedImageIsCorrect() { Configuration cfg = Configuration.Default.Clone(); - var metaData = new ImageMetaData(); + var metaData = new ImageMetadata(); var array = new Rgba32[25]; var memory = new Memory(array); using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) { - ref Rgba32 pixel0 = ref image.GetPixelSpan()[0]; + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + ref Rgba32 pixel0 = ref imageSpan[0]; Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); Assert.Equal(cfg, image.GetConfiguration()); - Assert.Equal(metaData, image.MetaData); + Assert.Equal(metaData, image.Metadata); } } @@ -113,13 +113,18 @@ namespace SixLabors.ImageSharp.Tests using (var memoryManager = new BitmapMemoryManager(bmp)) { Memory memory = memoryManager.Memory; - Bgra32 bg = NamedColors.Red; - Bgra32 fg = NamedColors.Green; + Bgra32 bg = Color.Red; + Bgra32 fg = Color.Green; 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.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory()); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + imageSpan.Fill(bg); + for (var i = 10; i < 20; i++) + { + image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); + } } Assert.False(memoryManager.IsDisposed); @@ -144,13 +149,18 @@ namespace SixLabors.ImageSharp.Tests using (var bmp = new Bitmap(51, 23)) { var memoryManager = new BitmapMemoryManager(bmp); - Bgra32 bg = NamedColors.Red; - Bgra32 fg = NamedColors.Green; + Bgra32 bg = Color.Red; + Bgra32 fg = Color.Green; 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.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory()); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + imageSpan.Fill(bg); + for (var i = 10; i < 20; i++) + { + image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); + } } Assert.True(memoryManager.IsDisposed); @@ -164,7 +174,7 @@ namespace SixLabors.ImageSharp.Tests } private static bool ShouldSkipBitmapTest => - !TestEnvironment.Is64BitProcess || TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1"; + !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 c5c7d19e1e..b7c6b3835a 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,14 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { /// @@ -25,7 +27,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.Equal(11*23, image.GetPixelSpan().Length); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.Equal(11 * 23, imageSpan.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(Configuration.Default, image.GetConfiguration()); @@ -41,7 +44,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.Equal(11 * 23, image.GetPixelSpan().Length); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.Equal(11 * 23, imageSpan.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(configuration, image.GetConfiguration()); @@ -52,13 +56,14 @@ namespace SixLabors.ImageSharp.Tests public void Configuration_Width_Height_BackgroundColor() { Configuration configuration = Configuration.Default.Clone(); - Rgba32 color = Rgba32.Aquamarine; + Rgba32 color = Color.Aquamarine; 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); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.Equal(11 * 23, imageSpan.Length); image.ComparePixelBufferTo(color); Assert.Equal(configuration, image.GetConfiguration()); @@ -72,18 +77,97 @@ namespace SixLabors.ImageSharp.Tests byte dirtyValue = 123; configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); - var metadata = new ImageMetaData(); + var metadata = new ImageMetadata(); - using (Image image = Image.CreateUninitialized(configuration, 21, 22, metadata)) + using (var image = Image.CreateUninitialized(configuration, 21, 22, metadata)) { Assert.Equal(21, image.Width); Assert.Equal(22, image.Height); Assert.Same(configuration, image.GetConfiguration()); - Assert.Same(metadata, image.MetaData); + 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) + { + var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + Rgba32 val = image[3, 4]; + Assert.Equal(default(Rgba32), val); + image[3, 4] = Color.Red; + val = image[3, 4]; + Assert.Equal(Color.Red.ToRgba32(), val); + } + + public static TheoryData OutOfRangeData = new TheoryData() + { + { 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 var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => _ = image[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 var image = new Image(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) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + } } } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs new file mode 100644 index 0000000000..afa217bbc9 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; + +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); + } + + [Theory] + [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] + public void GetSingleSpan(TestImageProvider provider) + { + provider.LimitAllocatorBufferCapacity().InPixels(10); + using Image image = provider.GetImage(); + Assert.False(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.False(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imageFrameSpan)); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs index cb09fa010c..1a480702f2 100644 --- a/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs +++ b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs index 10a531eafe..1ae217f0fe 100644 --- a/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs +++ b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs index 91f6804c0f..dd222f2c39 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/tests/ImageSharp.Tests/ImageInfoTests.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.MetaData; -using SixLabors.Primitives; +using SixLabors.ImageSharp.Metadata; using Xunit; @@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests var size = new Size(Width, Height); var rectangle = new Rectangle(0, 0, Width, Height); var pixelType = new PixelTypeInfo(8); - var meta = new ImageMetaData(); + var meta = new ImageMetadata(); var info = new ImageInfo(pixelType, Width, Height, meta); @@ -28,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(Height, info.Height); Assert.Equal(size, info.Size()); Assert.Equal(rectangle, info.Bounds()); - Assert.Equal(meta, info.MetaData); + Assert.Equal(meta, info.Metadata); } } } diff --git a/tests/ImageSharp.Tests/ImageOperationTests.cs b/tests/ImageSharp.Tests/ImageOperationTests.cs deleted file mode 100644 index 869882f672..0000000000 --- a/tests/ImageSharp.Tests/ImageOperationTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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 e20f1514cc..0761b0978d 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -1,48 +1,30 @@ - + + + - net462;net472;netcoreapp2.1 + netcoreapp3.1;netcoreapp2.1;net472 True - latest - full - portable True - SixLabors.ImageSharp.Tests SixLabors.ImageSharp.Tests AnyCPU;x64;x86 + SixLabors.ImageSharp.Tests + + true - - false - - - - false - - - - false - - - - - - - - - - - - - - - + - - - + + + - + + + + + + PreserveNewest @@ -54,4 +36,6 @@ PreserveNewest + + diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject b/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject deleted file mode 100644 index f015b4b86e..0000000000 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject +++ /dev/null @@ -1,9 +0,0 @@ - - - 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 deleted file mode 100644 index b0374ce1fa..0000000000 --- a/tests/ImageSharp.Tests/Issues/Issue412.cs +++ /dev/null @@ -1,56 +0,0 @@ -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 927f0a5edc..9b9dbc98e5 100644 --- a/tests/ImageSharp.Tests/Issues/Issue594.cs +++ b/tests/ImageSharp.Tests/Issues/Issue594.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,8 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Issues 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(0x0U, new NormalizedByte4(Vector4.Zero).PackedValue); + Assert.Equal(0x7F7F7F7FU, new NormalizedByte4(Vector4.One).PackedValue); Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); // Test ToVector4 @@ -46,48 +49,48 @@ namespace SixLabors.ImageSharp.Tests.Issues 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); + Assert.Equal(958796544U, 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); + // 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).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).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).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).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)); + // 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)); + // 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 @@ -96,8 +99,8 @@ namespace SixLabors.ImageSharp.Tests.Issues 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(0x0UL, new NormalizedShort4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new NormalizedShort4(Vector4.One).PackedValue); Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); // Test ToVector4 @@ -117,7 +120,7 @@ namespace SixLabors.ImageSharp.Tests.Issues // Test FromScaledVector4. var pixel = default(NormalizedShort4); pixel.FromScaledVector4(scaled); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, pixel.PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); // Test Ordering float x = 0.1f; @@ -125,43 +128,43 @@ namespace SixLabors.ImageSharp.Tests.Issues 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); + Assert.Equal(4150390751449251866UL, 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); + // var rgb = default(Rgb24); + // var rgba = default(Rgba32); + // var bgr = default(Bgr24); + // var bgra = default(Bgra32); + // var argb = default(Argb32); - //new NormalizedShort4(x, y, z, w).ToRgb24(ref rgb); - //Assert.Equal(rgb, new Rgb24(141, 90, 192)); + // new NormalizedShort4(x, y, z, w).ToRgb24(ref rgb); + // Assert.Equal(rgb, new Rgb24(141, 90, 192)); - //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).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).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).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)); + // 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)); + // 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.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)); + // 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 @@ -170,8 +173,8 @@ namespace SixLabors.ImageSharp.Tests.Issues 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(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. @@ -193,7 +196,7 @@ namespace SixLabors.ImageSharp.Tests.Issues // Test FromScaledVector4. var pixel = default(Short4); pixel.FromScaledVector4(scaled); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, pixel.PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); // Test clamping. Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 1234567.0f).ToVector4()); @@ -210,43 +213,43 @@ namespace SixLabors.ImageSharp.Tests.Issues y = 12653; z = 29623; w = 193; - Assert.Equal((ulong)0x00c173b7316d2d1b, new Short4(x, y, z, w).PackedValue); + Assert.Equal(0x00c173b7316d2d1bUL, 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); + // 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).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).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).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).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)); + // 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)); + // 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.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)); + // 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. diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs new file mode 100644 index 0000000000..939e5898cd --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -0,0 +1,284 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class ArrayPoolMemoryAllocatorTests + { + private const int MaxPooledBufferSizeInBytes = 2048; + + private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; + + /// + /// Gets the SUT for in-process tests. + /// + private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture(); + + /// + /// Gets the SUT for tests executed by , + /// recreated in each external process. + /// + private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture(); + + public class BufferTests : BufferTestSuite + { + public BufferTests() + : base(new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes)) + { + } + } + + public class Constructor + { + [Fact] + public void WhenBothParametersPassedByUser() + { + var mgr = new ArrayPoolMemoryAllocator(1111, 666); + Assert.Equal(1111, mgr.MaxPoolSizeInBytes); + Assert.Equal(666, mgr.PoolSelectorThresholdInBytes); + } + + [Fact] + public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated() + { + var mgr = new ArrayPoolMemoryAllocator(5000); + Assert.Equal(5000, mgr.MaxPoolSizeInBytes); + Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes); + } + + [Fact] + public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() + { + Assert.ThrowsAny(() => new ArrayPoolMemoryAllocator(100, 200)); + } + } + + [Theory] + [InlineData(32)] + [InlineData(512)] + [InlineData(MaxPooledBufferSizeInBytes - 1)] + public void SmallBuffersArePooled_OfByte(int size) + { + Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); + } + + [Theory] + [InlineData(128 * 1024 * 1024)] + [InlineData(MaxPooledBufferSizeInBytes + 1)] + public void LargeBuffersAreNotPooled_OfByte(int size) + { + static void RunTest(string sizeStr) + { + int size = int.Parse(sizeStr); + StaticFixture.CheckIsRentingPooledBuffer(size); + } + + RemoteExecutor.Invoke(RunTest, size.ToString()).Dispose(); + } + + [Fact] + public unsafe void SmallBuffersArePooled_OfBigValueType() + { + int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1; + + Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(count)); + } + + [Fact] + public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() + { + int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1; + + Assert.False(this.LocalFixture.CheckIsRentingPooledBuffer(count)); + } + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options) + { + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + using (IMemoryOwner firstAlloc = memoryAllocator.Allocate(42)) + { + BufferExtensions.GetSpan(firstAlloc).Fill(666); + } + + using (IMemoryOwner secondAlloc = memoryAllocator.Allocate(42, options)) + { + int expected = options == AllocationOptions.Clean ? 0 : 666; + Assert.Equal(expected, BufferExtensions.GetSpan(secondAlloc)[0]); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) + { + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + IMemoryOwner buffer = memoryAllocator.Allocate(32); + ref int ptrToPrev0 = ref MemoryMarshal.GetReference(BufferExtensions.GetSpan(buffer)); + + if (!keepBufferAlive) + { + buffer.Dispose(); + } + + memoryAllocator.ReleaseRetainedResources(); + + buffer = memoryAllocator.Allocate(32); + + Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref BufferExtensions.GetReference(buffer))); + } + + [Fact] + public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() + { + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + IMemoryOwner buffer = memoryAllocator.Allocate(32); + memoryAllocator.ReleaseRetainedResources(); + buffer.Dispose(); + } + + [Fact] + public void AllocationOverLargeArrayThreshold_UsesDifferentPool() + { + static void RunTest() + { + const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); + + IMemoryOwner small = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold - 1); + ref int ptr2Small = ref BufferExtensions.GetReference(small); + small.Dispose(); + + IMemoryOwner large = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold + 1); + + Assert.False(Unsafe.AreSame(ref ptr2Small, ref BufferExtensions.GetReference(large))); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Fact] + public void CreateWithAggressivePooling() + { + static void RunTest() + { + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling(); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(4096 * 4096)); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Fact] + public void CreateDefault() + { + static void RunTest() + { + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); + + Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Fact] + public void CreateWithModeratePooling() + { + static void RunTest() + { + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); + Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(1024 * 16)); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Theory] + [InlineData(-1)] + [InlineData(-111)] + public void Allocate_Negative_Throws_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => + this.LocalFixture.MemoryAllocator.Allocate(length)); + Assert.Equal("length", ex.ParamName); + } + + [Fact] + public void AllocateZero() + { + using IMemoryOwner buffer = this.LocalFixture.MemoryAllocator.Allocate(0); + Assert.Equal(0, buffer.Memory.Length); + } + + [Theory] + [InlineData(101)] + [InlineData((int.MaxValue / SizeOfLargeStruct) - 1)] + [InlineData(int.MaxValue / SizeOfLargeStruct)] + [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] + [InlineData((int.MaxValue / SizeOfLargeStruct) + 137)] + public void Allocate_OverCapacity_Throws_InvalidMemoryOperationException(int length) + { + this.LocalFixture.MemoryAllocator.BufferCapacityInBytes = 100 * SizeOfLargeStruct; + Assert.Throws(() => + this.LocalFixture.MemoryAllocator.Allocate(length)); + } + + [Theory] + [InlineData(-1)] + public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => + this.LocalFixture.MemoryAllocator.AllocateManagedByteBuffer(length)); + Assert.Equal("length", ex.ParamName); + } + + private class MemoryAllocatorFixture + { + public ArrayPoolMemoryAllocator MemoryAllocator { get; set; } = + new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); + + /// + /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location. + /// + public bool CheckIsRentingPooledBuffer(int length) + where T : struct + { + IMemoryOwner buffer = this.MemoryAllocator.Allocate(length); + ref T ptrToPrevPosition0 = ref BufferExtensions.GetReference(buffer); + buffer.Dispose(); + + buffer = this.MemoryAllocator.Allocate(length); + bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref BufferExtensions.GetReference(buffer)); + buffer.Dispose(); + + return sameBuffers; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct SmallStruct + { + private readonly uint dummy; + } + + private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5; + + [StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)] + private struct LargeStruct + { + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs new file mode 100644 index 0000000000..b6754f3922 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 new file mode 100644 index 0000000000..1124b64394 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs @@ -0,0 +1,319 @@ +// Copyright (c) Six Labors. +// 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 Xunit; + +// 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 LenthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_byte(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_float(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + 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(LenthValues))] + public void CanAllocateCleanBuffer_byte(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength, false); + this.TestCanAllocateCleanBuffer(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_double(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + private IMemoryOwner Allocate(int desiredLength, AllocationOptions options, bool managedByteBuffer) + where T : struct + { + if (managedByteBuffer) + { + if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, options) is IMemoryOwner buffer)) + { + throw new InvalidOperationException("typeof(T) != typeof(byte)"); + } + + return buffer; + } + + return this.MemoryAllocator.Allocate(desiredLength, options); + } + + private void TestCanAllocateCleanBuffer(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + ReadOnlySpan expected = new T[desiredLength]; + + for (int i = 0; i < 10; i++) + { + using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.Clean, testManagedByteBuffer)) + { + Assert.True(buffer.GetSpan().SequenceEqual(expected)); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, false); + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, true); + } + + private void TestSpanPropertyIsAlwaysTheSame(int desiredLength, bool testManagedByteBuffer = false) + where T : struct + { + using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + { + 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(LenthValues))] + public void WriteAndReadElements_float(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void WriteAndReadElements_byte(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), false); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); + } + + private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) + where T : struct + { + using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + { + 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(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength, false); + this.TestIndexOutOfRangeShouldThrow(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + private T TestIndexOutOfRangeShouldThrow(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + var dummy = default(T); + + using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + { + 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; + } + + [Theory] + [InlineData(1)] + [InlineData(7)] + [InlineData(1024)] + [InlineData(6666)] + public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength) + { + using (IManagedByteBuffer buffer = this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength)) + { + ref byte array0 = ref buffer.Array[0]; + ref byte span0 = ref buffer.GetReference(); + + Assert.True(Unsafe.AreSame(ref span0, ref array0)); + Assert.True(buffer.Array.Length >= buffer.GetSpan().Length); + } + } + + [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]); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs new file mode 100644 index 0000000000..8e7b305672 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class SimpleGcMemoryAllocatorTests + { + public class BufferTests : BufferTestSuite + { + public BufferTests() + : base(new SimpleGcMemoryAllocator()) + { + } + } + + protected SimpleGcMemoryAllocator MemoryAllocator { get; } = new SimpleGcMemoryAllocator(); + + [Theory] + [InlineData(-1)] + public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.Allocate(length)); + Assert.Equal("length", ex.ParamName); + } + + [Theory] + [InlineData(-1)] + public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.AllocateManagedByteBuffer(length)); + Assert.Equal("length", ex.ParamName); + } + + [StructLayout(LayoutKind.Explicit, Size = 512)] + private struct BigStruct + { + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 19ec725f27..549ecb7f4f 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -1,19 +1,18 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; +using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; -using SixLabors.Primitives; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Memory { public class Buffer2DTests @@ -21,37 +20,74 @@ namespace SixLabors.ImageSharp.Tests.Memory // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert { - public static void SpanPointsTo(Span span, IMemoryOwner buffer, int bufferOffset = 0) + 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 Unsafe.Add(ref buffer.GetReference(), bufferOffset); + ref T expected = ref buffer.Span[bufferOffset]; - Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); + True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); } } - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + + 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)) + { + 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(7, 42)] - [InlineData(1025, 17)] - public void Construct(int width, int height) + [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; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.Memory.Length); + Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); + Assert.Equal(0, buffer.GetSingleSpan().Length); } } + [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; + + Assert.Equal(expectedAlignment, memoryGroup.BufferLength); + } + [Fact] public void CreateClean() { using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) { - Span span = buffer.GetSpan(); + Span span = buffer.GetSingleSpan(); for (int j = 0; j < span.Length; j++) { Assert.Equal(0, span[j]); @@ -60,72 +96,209 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(7, 42, 0)] - [InlineData(7, 42, 10)] - [InlineData(17, 42, 41)] - public void GetRowSpanY(int width, int height, int y) + [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 GetRowSpanY(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)) { Span span = buffer.GetRowSpan(y); - // Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y); + + int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); + Assert.SpanPointsTo(span, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); } } + public static TheoryData GetRowSpanY_OutOfRange_Data = new TheoryData() + { + { Big, 10, 8, -1 }, + { Big, 10, 8, 8 }, + { 20, 10, 8, -1 }, + { 20, 10, 8, 10 }, + }; + [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) + [MemberData(nameof(GetRowSpanY_OutOfRange_Data))] + public void GetRowSpan_OutOfRange(int bufferCapacity, int width, int height, int y) { - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) - { - Span span = buffer.GetRowSpan(x, y); + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); - // Assert.Equal(width * y + x, span.Start); - Assert.Equal(width - x, span.Length); - Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y + x); - } + Exception ex = Assert.ThrowsAny(() => buffer.GetRowSpan(y)); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); } + public static TheoryData Indexer_OutOfRange_Data = new TheoryData() + { + { 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 }, + }; + [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) + [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); + + Exception ex = Assert.ThrowsAny(() => buffer[x, y]++); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); + } + + [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; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.MemorySource.GetSpan(); + int bufferIndex = (width * y) / buffer.FastMemoryGroup.BufferLength; + int subBufferStart = (width * y) - (bufferIndex * buffer.FastMemoryGroup.BufferLength); + + Span span = buffer.FastMemoryGroup[bufferIndex].Span.Slice(subBufferStart); ref TestStructs.Foo actual = ref buffer[x, y]; - ref TestStructs.Foo expected = ref span[y * width + x]; + ref TestStructs.Foo expected = ref span[x]; Assert.True(Unsafe.AreSame(ref expected, ref actual)); } } [Fact] - public void SwapOrCopyContent() + public void SwapOrCopyContent_WhenBothAllocated() { - using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5)) - using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7)) + using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) + using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) { - IMemoryOwner aa = a.MemorySource.MemoryOwner; - IMemoryOwner bb = b.MemorySource.MemoryOwner; + 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.MemorySource.MemoryOwner); - Assert.Equal(aa, b.MemorySource.MemoryOwner); + 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 var destData = MemoryGroup.Wrap(new int[100]); + using var dest = new Buffer2D(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.GetRowSpan(0)[0]; + int actual2 = dest.GetRowSpan(0)[0]; + int actual3 = dest.GetSafeRowMemory(0).Span[0]; + int actual4 = dest.GetFastRowMemory(0).Span[0]; + int actual5 = dest[0, 0]; + + Assert.Equal(1, actual1); + Assert.Equal(1, actual2); + Assert.Equal(1, actual3); + Assert.Equal(1, actual4); + Assert.Equal(1, actual5); + } + + [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) + { + var rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) + { + rnd.RandomFill(b.GetSingleSpan(), 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() + { + var rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) + { + rnd.RandomFill(b.GetSingleSpan(), 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); + } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index dc735e41b4..0dfc5f36b4 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using SixLabors.ImageSharp.Memory; -using SixLabors.Primitives; using Xunit; // ReSharper disable InconsistentNaming @@ -10,27 +9,27 @@ namespace SixLabors.ImageSharp.Tests.Memory { public class BufferAreaTests { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + [Fact] public void Construct() { - using (var buffer = Configuration.Default.MemoryAllocator.Allocate2D(10, 20)) - { - var rectangle = new Rectangle(3, 2, 5, 6); - var area = new BufferArea(buffer, rectangle); + using Buffer2D buffer = this.memoryAllocator.Allocate2D(10, 20); + var rectangle = new Rectangle(3, 2, 5, 6); + var area = new Buffer2DRegion(buffer, rectangle); - Assert.Equal(buffer, area.DestinationBuffer); - Assert.Equal(rectangle, area.Rectangle); - } + Assert.Equal(buffer, area.Buffer); + Assert.Equal(rectangle, area.Rectangle); } - private static Buffer2D CreateTestBuffer(int w, int h) + private Buffer2D CreateTestBuffer(int w, int h) { - var buffer = Configuration.Default.MemoryAllocator.Allocate2D(w, h); + Buffer2D buffer = this.memoryAllocator.Allocate2D(w, h); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { - buffer[x, y] = y * 100 + x; + buffer[x, y] = (y * 100) + x; } } @@ -38,111 +37,123 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(2, 3, 2, 2)] - [InlineData(5, 4, 3, 2)] - public void Indexer(int rx, int ry, int x, int y) + [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) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - Rectangle r = new Rectangle(rx, ry, 5, 6); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, 5, 6); - BufferArea area = buffer.GetArea(r); + Buffer2DRegion region = buffer.GetRegion(r); - int value = area[x, y]; - int expected = (ry + y) * 100 + rx + x; - Assert.Equal(expected, value); - } + int value = region[x, y]; + int expected = ((ry + y) * 100) + rx + x; + Assert.Equal(expected, value); } [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) + [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) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - Rectangle r = new Rectangle(rx, ry, w, h); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - BufferArea area = buffer.GetArea(r); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, w, h); - Span span = area.GetRowSpan(y); + Buffer2DRegion region = buffer.GetRegion(r); - Assert.Equal(w, span.Length); + Span span = region.GetRowSpan(y); - for (int i = 0; i < w; i++) - { - int expected = (ry + y) * 100 + rx + i; - int value = span[i]; + Assert.Equal(w, span.Length); - Assert.Equal(expected, value); - } + for (int i = 0; i < w; i++) + { + int expected = ((ry + y) * 100) + rx + i; + int value = span[i]; + + Assert.Equal(expected, value); } } [Fact] public void GetSubArea() { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + Buffer2DRegion area0 = buffer.GetRegion(6, 8, 10, 10); - BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); + Buffer2DRegion area1 = area0.GetSubRegion(4, 4, 5, 5); - var expectedRect = new Rectangle(10, 12, 5, 5); + var expectedRect = new Rectangle(10, 12, 5, 5); - Assert.Equal(buffer, area1.DestinationBuffer); - Assert.Equal(expectedRect, area1.Rectangle); + Assert.Equal(buffer, area1.Buffer); + Assert.Equal(expectedRect, area1.Rectangle); - int value00 = 12 * 100 + 10; - Assert.Equal(value00, area1[0, 0]); - } + int value00 = (12 * 100) + 10; + Assert.Equal(value00, area1[0, 0]); } - [Fact] - public void DangerousGetPinnableReference() + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void GetReferenceToOrigin(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - ref int r = ref area0.GetReferenceToOrigin(); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + Buffer2DRegion area0 = buffer.GetRegion(6, 8, 10, 10); - int expected = buffer[6, 8]; - Assert.Equal(expected, r); - } + ref int r = ref area0.GetReferenceToOrigin(); + + int expected = buffer[6, 8]; + Assert.Equal(expected, r); } - [Fact] - public void Clear_FullArea() + [Theory] + [InlineData(1000)] + [InlineData(70)] + public void Clear_FullArea(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(22, 13)) + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + + using Buffer2D buffer = this.CreateTestBuffer(22, 13); + var emptyRow = new int[22]; + buffer.GetRegion().Clear(); + + for (int y = 0; y < 13; y++) { - buffer.GetArea().Clear(); - Span fullSpan = buffer.GetSpan(); - Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length])); + Span row = buffer.GetRowSpan(y); + Assert.True(row.SequenceEqual(emptyRow)); } } - [Fact] - public void Clear_SubArea() + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void Clear_SubArea(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area = buffer.GetArea(5, 5, 10, 10); - area.Clear(); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - Assert.NotEqual(0, buffer[4, 4]); - Assert.NotEqual(0, buffer[15, 15]); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + Buffer2DRegion region = buffer.GetRegion(5, 5, 10, 10); + region.Clear(); - Assert.Equal(0, buffer[5, 5]); - Assert.Equal(0, buffer[14, 14]); + Assert.NotEqual(0, buffer[4, 4]); + Assert.NotEqual(0, buffer[15, 15]); - 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])); - } + 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.GetRowSpan(y).Slice(region.Rectangle.X, region.Width); + Assert.True(span.SequenceEqual(new int[region.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 new file mode 100644 index 0000000000..0eb219593d --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -0,0 +1,120 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 new file mode 100644 index 0000000000..6fecb19888 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public class MemoryGroupIndexTests + { + [Fact] + public void Equal() + { + var a = new MemoryGroupIndex(10, 1, 3); + var b = new MemoryGroupIndex(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() + { + var a = new MemoryGroupIndex(10, 3, 3); + var b = new MemoryGroupIndex(10, 5, 3); + + Assert.False(a == b); + Assert.True(a != b); + Assert.True(a < b); + Assert.False(a > b); + } + + [Fact] + public void SmallerElementIndex() + { + var a = new MemoryGroupIndex(10, 3, 3); + var b = new MemoryGroupIndex(10, 3, 9); + + Assert.False(a == b); + Assert.True(a != b); + Assert.True(a < b); + Assert.False(a > b); + } + + [Fact] + public void Increment() + { + var a = new MemoryGroupIndex(10, 3, 3); + a += 1; + Assert.Equal(new MemoryGroupIndex(10, 3, 4), a); + } + + [Fact] + public void Increment_OverflowBuffer() + { + var a = new MemoryGroupIndex(10, 5, 3); + var b = new MemoryGroupIndex(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 new file mode 100644 index 0000000000..e9094fcca4 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class Allocate : MemoryGroupTestsBase + { +#pragma warning disable SA1509 + public static TheoryData AllocateData = + new TheoryData() + { + { 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(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 BufferSizesAreCorrect( + T dummy, + int bufferCapacity, + int bufferAlignment, + long totalLength, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + + // Act: + using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); + + // Assert: + 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; + + HashSet bufferHashes; + + int expectedBlockCount = 5; + using (var 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))); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs new file mode 100644 index 0000000000..0dff35957a --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using Xunit; + +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); + var 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); + var 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) + { + var 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) + { + var 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.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs new file mode 100644 index 0000000000..61b9f7a895 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs @@ -0,0 +1,107 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class SwapOrCopyContent : MemoryGroupTestsBase + { + [Fact] + public void WhenBothAreMemoryOwners_ShouldSwap() + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; + using MemoryGroup a = this.MemoryAllocator.AllocateGroup(100, 50); + using MemoryGroup b = this.MemoryAllocator.AllocateGroup(120, 50); + + Memory a0 = a[0]; + Memory a1 = a[1]; + Memory b0 = b[0]; + Memory b1 = b[1]; + + bool swap = MemoryGroup.SwapOrCopyContent(a, b); + + Assert.True(swap); + Assert.Equal(b0, a[0]); + Assert.Equal(b1, a[1]); + Assert.Equal(a0, b[0]); + Assert.Equal(a1, b[1]); + Assert.NotEqual(a[0], b[0]); + } + + [Fact] + public void WhenBothAreMemoryOwners_ShouldReplaceViews() + { + using MemoryGroup a = this.MemoryAllocator.AllocateGroup(100, 100); + using MemoryGroup b = this.MemoryAllocator.AllocateGroup(120, 100); + + a[0].Span[42] = 1; + b[0].Span[33] = 2; + MemoryGroupView aView0 = a.View; + MemoryGroupView bView0 = b.View; + + MemoryGroup.SwapOrCopyContent(a, b); + Assert.False(aView0.IsValid); + Assert.False(bView0.IsValid); + Assert.ThrowsAny(() => _ = aView0[0].Span); + Assert.ThrowsAny(() => _ = bView0[0].Span); + + Assert.True(a.View.IsValid); + Assert.True(b.View.IsValid); + Assert.Equal(2, a.View[0].Span[33]); + Assert.Equal(1, b.View[0].Span[42]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + using var destOwner = new TestMemoryManager(data); + using var dest = MemoryGroup.Wrap(destOwner.Memory); + + using MemoryGroup source = this.MemoryAllocator.AllocateGroup(21, 30); + + source[0].Span[10] = color; + + // Act: + bool swap = MemoryGroup.SwapOrCopyContent(dest, source); + + // Assert: + Assert.False(swap); + Assert.Equal(color, dest[0].Span[10]); + Assert.NotEqual(source[0], dest[0]); + } + + [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); + + using var destOwner = new TestMemoryManager(data); + var dest = MemoryGroup.Wrap(destOwner.Memory); + + using MemoryGroup source = this.MemoryAllocator.AllocateGroup(22, 30); + + source[0].Span[10] = color; + + // Act: + Assert.ThrowsAny(() => MemoryGroup.SwapOrCopyContent(dest, source)); + + Assert.Equal(color, source[0].Span[10]); + Assert.NotEqual(color, dest[0].Span[10]); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs new file mode 100644 index 0000000000..771d64b38a --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using Xunit; + +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 new file mode 100644 index 0000000000..3ab5797ddb --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -0,0 +1,245 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests : MemoryGroupTestsBase + { + [Fact] + public void IsValid_TrueAfterCreation() + { + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); + + Assert.True(g.IsValid); + } + + [Fact] + public void IsValid_FalseAfterDisposal() + { + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); + + g.Dispose(); + Assert.False(g.IsValid); + } + +#pragma warning disable SA1509 + private static readonly TheoryData CopyAndTransformData = + new TheoryData() + { + { 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 var mgr0 = new TestMemoryManager(data0); + using var mgr1 = new TestMemoryManager(data1); + + using var 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)); + } + + public static TheoryData GetBoundedSlice_SuccessData = new TheoryData() + { + { 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.GetBoundedSlice(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 TheoryData() + { + { 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.GetBoundedSlice(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(); + + var 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; + } + } + + [StructLayout(LayoutKind.Sequential, Size = 5)] + private struct S5 + { + public override string ToString() => "S5"; + } + + [StructLayout(LayoutKind.Sequential, Size = 4)] + private struct S4 + { + public override string ToString() => "S4"; + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs new file mode 100644 index 0000000000..20cf66d42c --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public abstract class MemoryGroupTestsBase + { + internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + + /// + /// 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); + var 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 deleted file mode 100644 index 21217d73f2..0000000000 --- a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs +++ /dev/null @@ -1,162 +0,0 @@ -// 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 2c9417b117..62319156fc 100644 --- a/tests/ImageSharp.Tests/Memory/TestStructs.cs +++ b/tests/ImageSharp.Tests/Memory/TestStructs.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,8 +6,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Memory { - - public static class TestStructs { public struct Foo : IEquatable @@ -29,6 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { result[i] = new Foo(i + 1, i + 1); } + return result; } @@ -39,16 +38,15 @@ namespace SixLabors.ImageSharp.Tests.Memory public override int GetHashCode() { int hashCode = -1817952719; - hashCode = hashCode * -1521134295 + base.GetHashCode(); - hashCode = hashCode * -1521134295 + this.A.GetHashCode(); - hashCode = hashCode * -1521134295 + this.B.GetHashCode(); + 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})"; } - /// /// sizeof(AlignedFoo) == sizeof(long) /// @@ -80,17 +78,18 @@ namespace SixLabors.ImageSharp.Tests.Memory { result[i] = new AlignedFoo(i + 1, i + 1); } + 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(); + 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/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs deleted file mode 100644 index 8c49039603..0000000000 --- a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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 deleted file mode 100644 index b9619cb3f8..0000000000 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ /dev/null @@ -1,114 +0,0 @@ -// 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 deleted file mode 100644 index b5886522a8..0000000000 --- a/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -// 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 deleted file mode 100644 index c10ffb6c8e..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ /dev/null @@ -1,521 +0,0 @@ -// 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 deleted file mode 100644 index c9542a98a9..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 deleted file mode 100644 index 2b8d4d7160..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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 deleted file mode 100644 index 8bc192af5c..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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 deleted file mode 100644 index beca4db49f..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// 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 deleted file mode 100644 index 43a1ed7f47..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// 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 deleted file mode 100644 index 328cc3fa6a..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100644 index 5599e80d19..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -// 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 deleted file mode 100644 index 86f308ea19..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs +++ /dev/null @@ -1,128 +0,0 @@ -// 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 deleted file mode 100644 index f8924c43cc..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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 deleted file mode 100644 index aba587846a..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs +++ /dev/null @@ -1,447 +0,0 @@ -// 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 deleted file mode 100644 index a3e5a20f68..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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 deleted file mode 100644 index 6a47c988cd..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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 deleted file mode 100644 index 9286ac815e..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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 deleted file mode 100644 index 71dd18621c..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -// 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, 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 deleted file mode 100644 index 43165c617d..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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 deleted file mode 100644 index 1d482e2c1c..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -// 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 deleted file mode 100644 index 845a149b5d..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs +++ /dev/null @@ -1,121 +0,0 @@ -// 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 deleted file mode 100644 index f3ef5effbe..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs +++ /dev/null @@ -1,412 +0,0 @@ -// 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 deleted file mode 100644 index a3f796275a..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -// 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 deleted file mode 100644 index 17b5dacc4c..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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 deleted file mode 100644 index c91076afc9..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -// 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 deleted file mode 100644 index 99ae73f49a..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 deleted file mode 100644 index 46b8b31b40..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 new file mode 100644 index 0000000000..3f89049049 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// 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.GetGifMetadata(); + gifFrameMetadata.FrameDelay = frameDelay; + gifFrameMetadata.ColorTableLength = colorTableLength; + gifFrameMetadata.DisposalMethod = disposalMethod; + + var clone = new ImageFrameMetadata(metaData); + GifFrameMetadata cloneGifFrameMetadata = clone.GetGifMetadata(); + + 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.GetGifMetadata().Equals(clone.GetGifMetadata())); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs new file mode 100644 index 0000000000..a82ea70179 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Metadata +{ + /// + /// Tests the class. + /// + public class ImageMetadataTests + { + [Fact] + public void ConstructorImageMetadata() + { + var metaData = new ImageMetadata(); + + var exifProfile = new ExifProfile(); + + 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() + { + var metaData = new ImageMetadata + { + 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() + { + 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)); + + using (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/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs new file mode 100644 index 0000000000..466568bfec --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -0,0 +1,497 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifProfileTests + { + public enum TestImageWriteFormat + { + /// + /// Writes a jpg file. + /// + Jpeg, + + /// + /// Writes a png file. + /// + 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).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); + } + + [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); + + 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)] + 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); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void SetValue(TestImageWriteFormat imageFormat) + { + 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); + + var expectedLatitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; + image.Metadata.ExifProfile.SetValue(ExifTag.GPSLatitude, expectedLatitude); + + IExifValue latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); + Assert.Equal(expectedLatitude, latitude.Value); + + int profileCount = image.Metadata.ExifProfile.Values.Count; + image = WriteAndRead(image, imageFormat); + + Assert.NotNull(image.Metadata.ExifProfile); + + // 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 + Assert.Equal(profileCount - 3, 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); + Assert.Null(referenceBlackWhite); + + latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); + Assert.Equal(expectedLatitude, latitude.Value); + + 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, 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); + + Image thumbnail = profile.CreateThumbnail(); + Assert.NotNull(thumbnail); + Assert.Equal(256, thumbnail.Width); + Assert.Equal(170, thumbnail.Height); + } + + [Fact] + public void ReadWriteLargeProfileJpg() + { + ExifTag[] tags = new[] { ExifTag.Software, ExifTag.Copyright, ExifTag.Model, ExifTag.ImageDescription }; + foreach (ExifTag tag in tags) + { + // 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(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) + 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. + 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); + + byte[] bytes = profile.ToByteArray(); + Assert.Equal(525, bytes.Length); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) + { + // Arrange + var image = new Image(1, 1); + image.Metadata.ExifProfile = CreateExifProfile(); + + // Act + 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(); + 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) + { + IExifValue actualProfileValue = actualProfile.GetValueInternal(expectedProfileTag); + IExifValue expectedProfileValue = expectedProfile.GetValueInternal(expectedProfileTag); + Assert.Equal(expectedProfileValue.GetValue(), actualProfileValue.GetValue()); + } + } + + private static ExifProfile CreateExifProfile() + { + var profile = new ExifProfile(); + + foreach (KeyValuePair exifProfileValue in TestProfileValues) + { + profile.SetValueInternal(exifProfileValue.Key, exifProfileValue.Value); + } + + return profile; + } + + internal static ExifProfile GetExifProfile() + { + 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); + 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 (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 new file mode 100644 index 0000000000..401546e5c6 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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(Array.Empty()); + + 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..2b00cc5b48 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifTagDescriptionAttributeTests + { + [Fact] + public void TestExifTag() + { + var exifProfile = new ExifProfile(); + + 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 new file mode 100644 index 0000000000..5fe1b51baf --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifValueTests + { + private 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); + + return this.profile.GetValue(ExifTag.Software); + } + + [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 new file mode 100644 index 0000000000..898c693562 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -0,0 +1,555 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values +{ + public class ExifValuesTests + { + public static TheoryData ByteTags => new TheoryData + { + { ExifTag.FaxProfile }, + { ExifTag.ModeNumber }, + { ExifTag.GPSAltitudeRef } + }; + + public static TheoryData ByteArrayTags => new TheoryData + { + { ExifTag.ClipPath }, + { ExifTag.VersionYear }, + { ExifTag.XMP }, + { ExifTag.CFAPattern2 }, + { ExifTag.TIFFEPStandardID }, + { ExifTag.XPTitle }, + { ExifTag.XPComment }, + { ExifTag.XPAuthor }, + { ExifTag.XPKeywords }, + { ExifTag.XPSubject }, + { ExifTag.GPSVersionID }, + }; + + public static TheoryData DoubleArrayTags => new TheoryData + { + { ExifTag.PixelScale }, + { ExifTag.IntergraphMatrix }, + { ExifTag.ModelTiePoint }, + { ExifTag.ModelTransform } + }; + + public static TheoryData LongTags => new TheoryData + { + { 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 TheoryData + { + { ExifTag.FreeOffsets }, + { ExifTag.FreeByteCounts }, + { ExifTag.ColorResponseUnit }, + { ExifTag.TileOffsets }, + { ExifTag.SMinSampleValue }, + { ExifTag.SMaxSampleValue }, + { ExifTag.JPEGQTables }, + { ExifTag.JPEGDCTables }, + { ExifTag.JPEGACTables }, + { ExifTag.StripRowCounts }, + { ExifTag.IntergraphRegisters }, + { ExifTag.TimeZoneOffset } + }; + + public static TheoryData NumberTags => new TheoryData + { + { ExifTag.ImageWidth }, + { ExifTag.ImageLength }, + { ExifTag.TileWidth }, + { ExifTag.TileLength }, + { ExifTag.BadFaxLines }, + { ExifTag.ConsecutiveBadFaxLines }, + { ExifTag.PixelXDimension }, + { ExifTag.PixelYDimension } + }; + + public static TheoryData NumberArrayTags => new TheoryData + { + { ExifTag.StripOffsets }, + { ExifTag.TileByteCounts }, + { ExifTag.ImageLayer } + }; + + public static TheoryData RationalTags => new TheoryData + { + { 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 }, + }; + + public static TheoryData RationalArrayTags => new TheoryData + { + { 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 TheoryData + { + { ExifTag.OldSubfileType }, + { ExifTag.Compression }, + { ExifTag.PhotometricInterpretation }, + { ExifTag.Thresholding }, + { ExifTag.CellWidth }, + { ExifTag.CellLength }, + { ExifTag.FillOrder }, + { ExifTag.Orientation }, + { ExifTag.SamplesPerPixel }, + { ExifTag.PlanarConfiguration }, + { 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 TheoryData + { + { ExifTag.BitsPerSample }, + { ExifTag.MinSampleValue }, + { ExifTag.MaxSampleValue }, + { ExifTag.GrayResponseCurve }, + { ExifTag.ColorMap }, + { ExifTag.ExtraSamples }, + { ExifTag.PageNumber }, + { ExifTag.TransferFunction }, + { ExifTag.Predictor }, + { 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 TheoryData + { + { ExifTag.ShutterSpeedValue }, + { ExifTag.BrightnessValue }, + { ExifTag.ExposureBiasValue }, + { ExifTag.AmbientTemperature }, + { ExifTag.WaterDepth }, + { ExifTag.CameraElevationAngle } + }; + + public static TheoryData SignedRationalArrayTags => new TheoryData + { + { ExifTag.Decode } + }; + + public static TheoryData StringTags => new TheoryData + { + { 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 TheoryData + { + { ExifTag.FileSource }, + { ExifTag.SceneType } + }; + + public static TheoryData UndefinedArrayTags => new TheoryData + { + { ExifTag.JPEGTables }, + { ExifTag.OECF }, + { ExifTag.ExifVersion }, + { ExifTag.ComponentsConfiguration }, + { ExifTag.MakerNote }, + { ExifTag.UserComment }, + { ExifTag.FlashpixVersion }, + { ExifTag.SpatialFrequencyResponse }, + { ExifTag.SpatialFrequencyResponse2 }, + { ExifTag.Noise }, + { ExifTag.CFAPattern }, + { ExifTag.DeviceSettingDescription }, + { ExifTag.ImageSourceData }, + { ExifTag.GPSProcessingMethod }, + { ExifTag.GPSAreaInformation } + }; + + [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)); + + var typed = (ExifByte)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(ByteArrayTags))] + public void ExifByteArrayTests(ExifTag tag) + { + byte[] expected = new[] { byte.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifByteArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(DoubleArrayTags))] + public void ExifDoubleArrayTests(ExifTag tag) + { + double[] expected = new[] { double.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var 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)); + + var typed = (ExifLong)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(LongArrayTags))] + public void ExifLongArrayTests(ExifTag tag) + { + uint[] expected = new[] { uint.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifLongArray)value; + Assert.Equal(expected, typed.Value); + } + + [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)); + + var typed = (ExifNumber)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(NumberArrayTags))] + public void ExifNumberArrayTests(ExifTag tag) + { + Number[] expected = new[] { new Number(uint.MaxValue) }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifNumberArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(RationalTags))] + public void ExifRationalTests(ExifTag tag) + { + var expected = new Rational(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)); + + var typed = (ExifRational)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(RationalArrayTags))] + public void ExifRationalArrayTests(ExifTag tag) + { + Rational[] expected = new[] { new Rational(21, 42) }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var 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)); + + var typed = (ExifShort)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(ShortArrayTags))] + public void ExifShortArrayTests(ExifTag tag) + { + ushort[] expected = new[] { ushort.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifShortArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(SignedRationalTags))] + public void ExifSignedRationalTests(ExifTag tag) + { + var expected = new SignedRational(21, 42); + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifSignedRational)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(SignedRationalArrayTags))] + public void ExifSignedRationalArrayTests(ExifTag tag) + { + SignedRational[] expected = new[] { new SignedRational(21, 42) }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifSignedRationalArray)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)); + + var 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)); + + var typed = (ExifByte)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(UndefinedArrayTags))] + public void ExifUndefinedArrayTests(ExifTag tag) + { + byte[] expected = new[] { byte.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifByteArray)value; + 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 new file mode 100644 index 0000000000..5451cbf37e --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataReaderLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs new file mode 100644 index 0000000000..aa24c26739 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataReaderMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs new file mode 100644 index 0000000000..fe31d74ac9 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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/IccDataReaderMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs new file mode 100644 index 0000000000..3fbef46de9 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// 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(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) + { + IccDataReader reader = this.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 = this.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 = this.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 = this.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/IccDataReaderNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs new file mode 100644 index 0000000000..cf4cf80d1b --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataReaderPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs new file mode 100644 index 0000000000..2b2b564a7c --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// 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 = this.CreateReader(textBytes); + + string output = reader.ReadAsciiString(length); + + Assert.Equal(expected, output); + } + + [Fact] + public void ReadAsciiStringWithNegativeLengthThrowsArgumentException() + { + IccDataReader reader = this.CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadAsciiString(-1)); + } + + [Fact] + public void ReadUnicodeStringWithNegativeLengthThrowsArgumentException() + { + IccDataReader reader = this.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 = this.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 = this.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 = this.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 = this.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/IccDataReaderTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs new file mode 100644 index 0000000000..ea77004ed0 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs @@ -0,0 +1,447 @@ +// Copyright (c) Six Labors. +// 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..7c5070af1c --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// 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/IccDataWriterCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs new file mode 100644 index 0000000000..593eed97ce --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataWriterLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs new file mode 100644 index 0000000000..e48d89ddbb --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataWriterLutTests1.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs new file mode 100644 index 0000000000..711e3426d2 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.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) + { + IccDataWriter writer = this.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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataWriterLutTests2.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs new file mode 100644 index 0000000000..ecfbad3951 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.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) + { + IccDataWriter writer = this.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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataWriterMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs new file mode 100644 index 0000000000..4346265c76 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + using SixLabors.ImageSharp; + + 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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataWriterMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs new file mode 100644 index 0000000000..bf8b7d0699 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// 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(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) + { + IccDataWriter writer = this.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) + { + IccDataWriter writer = this.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) + { + IccDataWriter writer = this.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) + { + IccDataWriter writer = this.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/IccDataWriterNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs new file mode 100644 index 0000000000..a918adc3f8 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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/IccDataWriterPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs new file mode 100644 index 0000000000..1dc37a1953 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.CreateWriter(); + + writer.WriteAsciiString(data, length, ensureNullTerminator); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Fact] + public void WriteAsciiStringWithNullWritesEmpty() + { + IccDataWriter writer = this.CreateWriter(); + + int count = writer.WriteAsciiString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(Array.Empty(), output); + } + + [Fact] + public void WriteAsciiStringWithNegativeLengthThrowsArgumentException() + { + IccDataWriter writer = this.CreateWriter(); + + Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); + } + + [Fact] + public void WriteUnicodeStringWithNullWritesEmpty() + { + IccDataWriter writer = this.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 = this.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 = this.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 = this.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 = this.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/IccDataWriterTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs new file mode 100644 index 0000000000..6325f26ce0 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs @@ -0,0 +1,412 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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..9fa3e644cd --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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..a40082f78d --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// 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; + var 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) + { + 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..e9d960ebbc --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// 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 = this.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 = this.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..0d4495912c --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// 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 = this.CreateWriter(); + + var 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 = this.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..b06a529641 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// 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/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs new file mode 100644 index 0000000000..9e763536bc --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -0,0 +1,369 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC +{ + public class IptcProfileTests + { + private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; + + public static IEnumerable AllIptcTags() + { + foreach (object tag in Enum.GetValues(typeof(IptcTag))) + { + yield return new object[] { tag }; + } + } + + [Theory] + [MemberData(nameof(AllIptcTags))] + public void IptcProfile_SetValue_WithStrictEnabled_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var value = new string('s', tag.MaxLength() + 1); + var 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 + var profile = new IptcProfile(); + var value = new string('s', tag.MaxLength() + 1); + var 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 + var profile = new IptcProfile(); + var datetime = new DateTimeOffset(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 + var profile = new IptcProfile(); + var dateTimeUtc = new DateTime(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_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(JpegDecoder)) + { + Assert.NotNull(image.Metadata.IptcProfile); + var iptcValues = image.Metadata.IptcProfile.Values.ToList(); + 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); + Assert.Null(image.Metadata.IptcProfile); + } + + [Fact] + public void IptcProfile_ToAndFromByteArray_Works() + { + // arrange + var profile = new IptcProfile(); + var expectedCaptionWriter = "unittest"; + var expectedCaption = "test"; + profile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); + profile.SetValue(IptcTag.Caption, expectedCaption); + + // act + profile.UpdateData(); + byte[] profileBytes = profile.Data; + var profileFromBytes = new IptcProfile(profileBytes); + + // assert + var iptcValues = profileFromBytes.Values.ToList(); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); + ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); + } + + [Fact] + public void IptcProfile_CloneIsDeep() + { + // arrange + var profile = new IptcProfile(); + var captionWriter = "unittest"; + var 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()); + var 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 + var iptcValue = new IptcValue(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 + var image = new Image(1, 1); + image.Metadata.IptcProfile = new IptcProfile(); + var expectedCaptionWriter = "unittest"; + var expectedCaption = "test"; + image.Metadata.IptcProfile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); + image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption); + + // act + Image reloadedImage = WriteAndReadJpeg(image); + + // assert + IptcProfile actual = reloadedImage.Metadata.IptcProfile; + Assert.NotNull(actual); + var 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 + var profile = new IptcProfile(); + var expectedValue1 = "test"; + var expectedValue2 = "another one"; + profile.SetValue(tag, expectedValue1, false); + + // act + profile.SetValue(tag, expectedValue2, false); + + // assert + var 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 + var profile = new IptcProfile(); + var expectedValue = "another one"; + profile.SetValue(tag, "test", false); + + // act + profile.SetValue(tag, expectedValue, false); + + // assert + var values = profile.Values.ToList(); + Assert.Equal(1, values.Count); + ContainsIptcValue(values, tag, expectedValue); + } + + [Fact] + public void IptcProfile_RemoveByTag_RemovesAllEntrys() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.Byline, "test"); + profile.SetValue(IptcTag.Byline, "test2"); + + // act + var 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 + var profile = new IptcProfile(); + profile.SetValue(IptcTag.Byline, "test"); + profile.SetValue(IptcTag.Byline, "test2"); + + // act + var 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 + var profile = new IptcProfile(); + 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 (var memStream = new MemoryStream()) + { + image.SaveAsJpeg(memStream); + 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 caddd49216..46efe6527d 100644 --- a/tests/ImageSharp.Tests/Numerics/RationalTests.cs +++ b/tests/ImageSharp.Tests/Numerics/RationalTests.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - using Xunit; namespace SixLabors.ImageSharp.Tests @@ -91,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void ToStringRepresention() + public void ToStringRepresentation() { var rational = new Rational(0, 0); Assert.Equal("[ Indeterminate ]", rational.ToString()); diff --git a/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs b/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs index 77920ba2fe..ed9cf7e308 100644 --- a/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs +++ b/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - using Xunit; namespace SixLabors.ImageSharp.Tests @@ -98,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void ToStringRepresention() + public void ToStringRepresentation() { var rational = new SignedRational(0, 0); Assert.Equal("[ Indeterminate ]", rational.ToString()); @@ -119,4 +117,4 @@ namespace SixLabors.ImageSharp.Tests 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 new file mode 100644 index 0000000000..784f9821fa --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.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() + { + var left = new A8(16); + var right = new A8(32); + + Assert.True(left == new A8(16)); + Assert.True(left != right); + Assert.Equal(left, (object)new A8(16)); + } + + [Fact] + public void A8_FromScaledVector4() + { + // Arrange + A8 alpha = default; + int expected = 128; + Vector4 scaled = new A8(.5F).ToScaledVector4(); + + // Act + alpha.FromScaledVector4(scaled); + byte actual = alpha.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void A8_ToScaledVector4() + { + // Arrange + var alpha = new A8(.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 A8_ToVector4() + { + // Arrange + var alpha = new A8(.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 A8_ToRgba32() + { + var input = new A8(128); + var expected = new Rgba32(0, 0, 0, 128); + + Rgba32 actual = default; + input.ToRgba32(ref actual); + Assert.Equal(expected, actual); + } + + [Fact] + public void A8_FromBgra5551() + { + // arrange + var alpha = default(A8); + 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/Alpha8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs deleted file mode 100644 index 8f68c9d03f..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs +++ /dev/null @@ -1,98 +0,0 @@ -// 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); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs index b9f7414900..c8cf0cf111 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs @@ -1,6 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -9,11 +10,72 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { public class Argb32Tests { + /// + /// 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(0x0U, new Argb32(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Argb32(Vector4.One).PackedValue); } @@ -60,6 +122,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs index 2295fbe56f..110bdc2f0c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,8 +9,26 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { public class Bgr24Tests { + [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 } }; + new TheoryData { { 1, 2, 3 }, { 4, 5, 6 }, { 0, 255, 42 } }; [Theory] [MemberData(nameof(ColorData))] @@ -59,7 +77,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.False(a.Equals((object)b)); } - [Fact] public void FromRgba32() { @@ -95,5 +112,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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 967e358e1e..8c53e117fd 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,12 +9,43 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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); @@ -63,6 +94,154 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromGrey16() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + [Fact] public void Bgr565_Clamping() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index a5c53ed8b0..195f92b0e2 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,8 +9,32 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { public class Bgra32Tests { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + 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.NotEqual(color1, color2); + } + public static readonly TheoryData ColorData = - new TheoryData() + new TheoryData { { 1, 2, 3, 4 }, { 4, 5, 6, 7 }, { 0, 255, 42, 0 }, { 1, 2, 3, 255 } }; @@ -65,7 +89,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.False(x.Equals((object)y)); } - [Fact] public void FromRgba32() { @@ -102,5 +125,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(Vec(1, 2, 3, 4), rgb.ToVector4()); } + + [Fact] + public void Bgra32_FromBgra5551() + { + // arrange + var bgra = default(Bgra32); + uint expected = uint.MaxValue; + + // act + bgra.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // 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 8b56ec19fe..776f5cdc6c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,6 +9,36 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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() { @@ -48,6 +78,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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() { @@ -64,6 +108,136 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromGrey8() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromL8(new L8(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() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs index 76edee8a73..cdc03b292d 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,6 +9,36 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { public class Bgra5551Tests { + /// + /// 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() { @@ -18,6 +48,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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); @@ -36,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var bgra = new Bgra5551(Vector4.One); - // act + // act Vector4 actual = bgra.ToScaledVector4(); // assert @@ -46,6 +83,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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() { @@ -53,7 +104,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new Bgra5551(Vector4.One).ToScaledVector4(); int expected = 0xFFFF; var pixel = default(Bgra5551); - pixel.FromScaledVector4(scaled); // act pixel.FromScaledVector4(scaled); @@ -63,6 +113,156 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromGrey8() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromL8(new L8(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() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs index 8391ef25ae..bb7e82e50e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,12 +9,42 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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(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); } @@ -45,6 +75,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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() { @@ -61,6 +105,132 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromGrey16() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromL16(new L16(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() { diff --git a/tests/ImageSharp.Tests/PixelFormats/ColorBuilderTests.cs b/tests/ImageSharp.Tests/PixelFormats/ColorBuilderTests.cs deleted file mode 100644 index e56cac2794..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/ColorBuilderTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 deleted file mode 100644 index 302e56ec71..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 deleted file mode 100644 index cb19c031d0..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs +++ /dev/null @@ -1,130 +0,0 @@ -// 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 - { - [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); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs deleted file mode 100644 index 1e17985e60..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Diagnostics; -using System.Numerics; - -using SixLabors.ImageSharp.Common.Helpers; -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 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); - } - - 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 85a3b8b320..e59cb33444 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var halfSingle = new HalfSingle(-1F); - // act + // act Vector4 actual = halfSingle.ToScaledVector4(); // assert @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void HalfSingle_FromScaledVector4() { - // arrange + // arrange Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); int expected = 48128; var halfSingle = default(HalfSingle); diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index ccdd23e8fb..b59e598b44 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -71,5 +71,22 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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 c61dd97d2a..27726e9a33 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var halfVector4 = new HalfVector4(-Vector4.One); - // act + // act Vector4 actual = halfVector4.ToScaledVector4(); // assert @@ -58,12 +58,26 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); ulong expected = 13547034390470638592uL; - // act + // act halfVector4.FromScaledVector4(scaled); ulong actual = halfVector4.PackedValue; // assert Assert.Equal(expected, actual); } + + [Fact] + public void HalfVector4_FromBgra5551() + { + // arrange + var halfVector4 = default(HalfVector4); + Vector4 expected = Vector4.One; + + // act + halfVector4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, halfVector4.ToScaledVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs new file mode 100644 index 0000000000..4204fc2f70 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class L16Tests + { + [Fact] + public void AreEqual() + { + var color1 = new L16(3000); + var color2 = new L16(3000); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new L16(12345); + var color2 = new L16(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 + L16 gray = default; + const ushort expected = 32767; + Vector4 scaled = new L16(expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void L16_ToScaledVector4(ushort input) + { + // Arrange + var gray = new L16(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 L16_FromVector4() + { + // Arrange + L16 gray = default; + const ushort expected = 32767; + var vector = new L16(expected).ToVector4(); + + // Act + gray.FromVector4(vector); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void L16_ToVector4(ushort input) + { + // Arrange + var gray = new L16(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 L16_FromRgba32() + { + // Arrange + L16 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 L16_ToRgba32(ushort input) + { + // Arrange + ushort expected = ImageMaths.DownScaleFrom16BitTo8Bit(input); + var gray = new L16(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 L16_FromBgra5551() + { + // arrange + var gray = default(L16); + 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/L8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs new file mode 100644 index 0000000000..09d67ab9ab --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs @@ -0,0 +1,281 @@ +// Copyright (c) Six Labors. +// 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 L8Tests + { + 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 L8_PackedValue_EqualsInput(byte input) + => Assert.Equal(input, new L8(input).PackedValue); + + [Fact] + public void AreEqual() + { + var color1 = new L8(100); + var color2 = new L8(100); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new L8(100); + var color2 = new L8(200); + + Assert.NotEqual(color1, color2); + } + + [Fact] + public void L8_FromScaledVector4() + { + // Arrange + L8 gray = default; + const byte expected = 128; + Vector4 scaled = new L8(expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + byte actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_ToScaledVector4(byte input) + { + // Arrange + var gray = new L8(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 L8_FromVector4(byte luminance) + { + // Arrange + L8 gray = default; + var vector = new L8(luminance).ToVector4(); + + // Act + gray.FromVector4(vector); + byte actual = gray.PackedValue; + + // Assert + Assert.Equal(luminance, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_ToVector4(byte input) + { + // Arrange + var gray = new L8(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 L8_FromRgba32(byte rgb) + { + // Arrange + L8 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 L8_ToRgba32(byte luminance) + { + // Arrange + var gray = new L8(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 L8_FromBgra5551() + { + // arrange + var grey = default(L8); + 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 = L8Tests.LuminanceData; + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_FromRgba32_IsInverseOf_ToRgba32(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + L8 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Rgba32_ToL8_IsInverseOf_L8_ToRgba32(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + L8 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var l8Vector = original.ToVector4(); + var rgbaVector = original.ToVector4(); + + Assert.Equal(l8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var rgbaVector = original.ToVector4(); + + L8 mirror = default; + mirror.FromVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + 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) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 rgbaVector = original.ToScaledVector4(); + + L8 mirror = default; + mirror.FromScaledVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs new file mode 100644 index 0000000000..f36d9765c9 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs @@ -0,0 +1,285 @@ +// Copyright (c) Six Labors. +// 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 La16Tests + { + 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, 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() + { + var color1 = new La16(100, 50); + var color2 = new La16(100, 50); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new La16(100, 50); + var color2 = new La16(200, 50); + + Assert.NotEqual(color1, color2); + } + + [Fact] + public void La16_FromScaledVector4() + { + // Arrange + La16 gray = default; + const ushort expected = 32896; + Vector4 scaled = new La16(128, 128).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_ToScaledVector4(byte input) + { + // Arrange + var gray = new La16(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 + La16 gray = default; + var vector = new La16(luminance, luminance).ToVector4(); + + // Act + gray.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 + var gray = new La16(input, 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(scaledInput, actual.W); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_FromRgba32(byte rgb) + { + // Arrange + La16 gray = default; + byte expected = ImageMaths.Get8BitBT709Luminance(rgb, rgb, rgb); + + // Act + gray.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 + var gray = new La16(luminance, 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(luminance, actual.A); + } + + [Fact] + public void La16_FromBgra5551() + { + // arrange + var grey = default(La16); + byte expected = byte.MaxValue; + + // act + grey.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) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + La16 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Rgba32_ToLa16_IsInverseOf_La16_ToRgba32(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + La16 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var la16Vector = original.ToVector4(); + var rgbaVector = original.ToVector4(); + + Assert.Equal(la16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var rgbaVector = original.ToVector4(); + + La16 mirror = default; + mirror.FromVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 la16Vector = original.ToScaledVector4(); + Vector4 rgbaVector = original.ToScaledVector4(); + + Assert.Equal(la16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 rgbaVector = original.ToScaledVector4(); + + La16 mirror = default; + mirror.FromScaledVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs new file mode 100644 index 0000000000..d3fdbd085d --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class La32Tests + { + [Fact] + public void AreEqual() + { + var color1 = new La32(3000, 100); + var color2 = new La32(3000, 100); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new La32(12345, 100); + var color2 = new La32(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 + La32 gray = default; + const ushort expected = 32767; + Vector4 scaled = new La32(expected, expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.L; + ushort actualA = gray.A; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(expected, actualA); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void La32_ToScaledVector4(ushort input) + { + // Arrange + var gray = new La32(input, 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(vectorInput, actual.W); + } + + [Fact] + public void La32_FromVector4() + { + // Arrange + La32 gray = default; + const ushort expected = 32767; + var vector = new La32(expected, expected).ToVector4(); + + // Act + gray.FromVector4(vector); + ushort actual = gray.L; + ushort actualA = gray.A; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(expected, actualA); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void La32_ToVector4(ushort input) + { + // Arrange + var gray = new La32(input, 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(vectorInput, actual.W); + } + + [Fact] + public void La32_FromRgba32() + { + // Arrange + La32 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.L; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(ushort.MaxValue, gray.A); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(8100)] + public void La32_ToRgba32(ushort input) + { + // Arrange + ushort expected = ImageMaths.DownScaleFrom16BitTo8Bit(input); + var gray = new La32(input, ushort.MaxValue); + + // 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 La32_FromBgra5551() + { + // arrange + var gray = default(La32); + ushort expected = ushort.MaxValue; + + // act + gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, gray.L); + Assert.Equal(expected, gray.A); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs index 506ebe0fe0..9278a8a48a 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -66,5 +66,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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 19a49e5d8a..9b754ecc2f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,13 +9,43 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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(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); } @@ -53,12 +83,152 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); uint expected = 0x81818181; - // act + // 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.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromGrey16() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromL16(new L16(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 216ed4ad75..0b346b256f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -14,9 +14,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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(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); } @@ -34,8 +35,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [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()); + 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] @@ -69,5 +70,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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 d06d46d06f..8166421a19 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,13 +9,43 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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(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); } @@ -54,12 +84,152 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); ulong expected = 0x7FFF7FFF7FFF7FFF; - // act + // 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.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromGrey16() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromL16(new L16(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 new file mode 100644 index 0000000000..548e2f4d89 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors. +// 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 +{ + 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 : unmanaged, IPixel + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); + Assert.IsType(type, blender); + } + + public static TheoryData ColorBlendingExpectedResults = new TheoryData + { + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Normal, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, + { Color.MistyRose, Color.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 + { + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Color.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/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs index 9c3ea90d53..931af15680 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -1,56 +1,60 @@ -// 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 +// Copyright (c) Six Labors. +// 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.CreateRgba32Image()) + using (Image dest = provider.GetImage()) + { + var options = new GraphicsOptions + { + Antialias = false, + 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); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs index 9a196d9d5b..27674bc500 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -10,9 +10,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { public class PorterDuffFunctionsTests { - 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) }, + 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] @@ -23,15 +24,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders Assert.Equal(expected, actual); } - 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) - }, + 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] @@ -42,15 +39,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - 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) - }, + 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] @@ -61,34 +54,26 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - 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) - }, + public static TheoryData SubtractFunctionData = 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) + [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); } - 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) - }, + 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] @@ -99,15 +84,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - 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) - }, + 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] @@ -118,15 +99,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - 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) - }, + 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] @@ -137,15 +114,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - 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) - }, + 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] @@ -156,15 +129,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - 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) - }, + 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] diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs new file mode 100644 index 0000000000..a09974e8df --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs @@ -0,0 +1,386 @@ +// Copyright (c) Six Labors. +// 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 +{ + 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 : 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 + { + var dest = new Span(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 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 : 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 + { + var dest = new Span(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 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 : 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 + { + var dest = new Span(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 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(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 + { + var dest = new Span(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 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 : 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 + { + var dest = new Span(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 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 : 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 + { + var dest = new Span(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 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 : 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 + { + var dest = new Span(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 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 : 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 + { + var dest = new Span(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 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 : 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 + { + var dest = new Span(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 deleted file mode 100644 index 7de1cbb190..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs +++ /dev/null @@ -1,369 +0,0 @@ -// 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/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs new file mode 100644 index 0000000000..6fda9dbbad --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -0,0 +1,109 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public abstract partial class PixelConverterTests + { + public static class ReferenceImplementations + { + 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; + } + + 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; + } + + // L8 and L16 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(L16)) + { + ref L16 l16Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationPixels)); + for (int i = 0; i < count; i++) + { + ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref l16Ref, i); + dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); + } + + return; + } + + if (typeof(TDestinationPixel) == typeof(L8)) + { + ref L8 l8Ref = ref MemoryMarshal.GetReference( + MemoryMarshal.Cast(destinationPixels)); + for (int i = 0; i < count; i++) + { + ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref l8Ref, i); + dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); + } + + 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.FromScaledVector4(sp.ToScaledVector4()); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs index c539e9dcf0..3de6804dcf 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs @@ -1,11 +1,14 @@ -using SixLabors.ImageSharp.PixelFormats; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; using Xunit; namespace SixLabors.ImageSharp.Tests.PixelFormats { - public abstract class PixelConverterTests + public abstract partial class PixelConverterTests { public static readonly TheoryData RgbaData = new TheoryData @@ -122,39 +125,5 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expectedPacked, actualPacked); } } - - - private static class ReferenceImplementations - { - 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/PixelConversionModifiersExtensionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs new file mode 100644 index 0000000000..aa39588f94 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public class PixelConversionModifiersExtensionsTests + { + [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, 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); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs index c881ae96ba..1d4d583411 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +12,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { public class Argb32OperationsTests : PixelOperationsTests { - public Argb32OperationsTests(ITestOutputHelper output) : base(output) { @@ -22,4 +21,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations 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 index 323d3914cf..712b1495ba 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,6 +15,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public Bgr24OperationsTests(ITestOutputHelper output) : base(output) { + this.HasAlpha = false; } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra32OperationsTests.cs index 1c966951fc..7f248b682d 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra32OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra32OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs new file mode 100644 index 0000000000..9a0e51563a --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Bgra5551OperationsTests : PixelOperationsTests + { + public Bgra5551OperationsTests(ITestOutputHelper output) + : base(output) + { + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs deleted file mode 100644 index c3de335470..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs +++ /dev/null @@ -1,114 +0,0 @@ -// 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 deleted file mode 100644 index acd6ef23ac..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs +++ /dev/null @@ -1,114 +0,0 @@ -// 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.L16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs new file mode 100644 index 0000000000..6acd439f21 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// 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 L16OperationsTests : PixelOperationsTests + { + public L16OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L8OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L8OperationsTests.cs new file mode 100644 index 0000000000..a16f8c66d2 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L8OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// 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 L8OperationsTests : PixelOperationsTests + { + public L8OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs new file mode 100644 index 0000000000..07ec79777c --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// 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 La16OperationsTests : PixelOperationsTests + { + public La16OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs new file mode 100644 index 0000000000..bd8bb40da2 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// 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 La32OperationsTests : PixelOperationsTests + { + public La32OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} 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..07bf838eee --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// 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); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb48OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb48OperationsTests.cs index 0a28db6b0a..7ab6777664 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb48OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb48OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs index 1ecbaf3615..e4f1fa4624 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Buffers; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba64OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba64OperationsTests.cs index 6787602bb2..a0ef2f765f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba64OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba64OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.RgbaVectorOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.RgbaVectorOperationsTests.cs index f9cc042a77..c552fb3592 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.RgbaVectorOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.RgbaVectorOperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index d9845e4741..3c4b9dc795 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -1,15 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; +using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - +using SixLabors.ImageSharp.ColorSpaces.Companding; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -17,14 +18,16 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { public partial class PixelOperationsTests { +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter [Theory] [WithBlankImages(1, 1, PixelTypes.All)] public void GetGlobalInstance(TestImageProvider _) - where T : struct, IPixel => Assert.NotNull(PixelOperations.Instance); + where T : unmanaged, IPixel => Assert.NotNull(PixelOperations.Instance); } +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter public abstract class PixelOperationsTests : MeasureFixture - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public const string SkipProfilingBenchmarks = #if true @@ -33,6 +36,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations null; #endif + protected bool HasAlpha { get; set; } = true; + protected PixelOperationsTests(ITestOutputHelper output) : base(output) { @@ -70,25 +75,33 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations internal static PixelOperations Operations => PixelOperations.Instance; - internal static TPixel[] CreateExpectedPixelData(Vector4[] source) + internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) { var expected = new TPixel[source.Length]; for (int i = 0; i < expected.Length; i++) { - expected[i].FromVector4(source[i]); + Vector4 v = source[i]; + vectorModifier?.Invoke(ref v); + + expected[i].FromVector4(v); } + return expected; } - internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source) + internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) { var expected = new TPixel[source.Length]; for (int i = 0; i < expected.Length; i++) { - expected[i].FromScaledVector4(source[i]); + Vector4 v = source[i]; + vectorModifier?.Invoke(ref v); + + expected[i].FromScaledVector4(v); } + return expected; } @@ -102,8 +115,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromVector4(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan())); } [Theory] @@ -116,8 +128,137 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromScaledVector4(this.Configuration, s, d.GetSpan()) - ); + (s, d) => + { + Span destPixels = d.GetSpan(); + Operations.FromVector4Destructive(this.Configuration, (Span)s, destPixels, PixelConversionModifiers.Scale); + }); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromCompandedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + } + + void ExpectedAction(ref Vector4 v) + { + SRgbCompanding.Compress(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) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromPremultipliedVector4(int count) + { + void SourceAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utilities.Premultiply(ref v); + } + } + + void ExpectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utilities.UnPremultiply(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) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromPremultipliedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utilities.Premultiply(ref v); + } + } + + void ExpectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utilities.UnPremultiply(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) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromCompandedPremultipliedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + + if (this.HasAlpha) + { + Vector4Utilities.Premultiply(ref v); + } + } + + void ExpectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utilities.UnPremultiply(ref v); + } + + SRgbCompanding.Compress(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) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } [Theory] @@ -130,8 +271,32 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan())); + } + + public static readonly TheoryData Generic_To_Data = new TheoryData + { + 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 dummy) + where TDestPixel : unmanaged, IPixel + { + const int Count = 2134; + TPixel[] source = CreatePixelTestData(Count); + var expected = new TDestPixel[Count]; + + PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); + + TestOperation(source, expected, (s, d) => Operations.To(this.Configuration, (ReadOnlySpan)s, d.GetSpan())); } [Theory] @@ -144,8 +309,117 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToScaledVector4(this.Configuration, s, d.GetSpan()) - ); + (s, d) => + { + Span destVectors = d.GetSpan(); + Operations.ToVector4(this.Configuration, (ReadOnlySpan)s, destVectors, PixelConversionModifiers.Scale); + }); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToCompandedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) + { + SRgbCompanding.Compress(ref v); + } + + void ExpectedAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + } + + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => 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) + { + Vector4Utilities.UnPremultiply(ref v); + } + + void ExpectedAction(ref Vector4 v) + { + Vector4Utilities.Premultiply(ref v); + } + + TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToPremultipliedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) + { + Vector4Utilities.UnPremultiply(ref v); + } + + void ExpectedAction(ref Vector4 v) + { + Vector4Utilities.Premultiply(ref v); + } + + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => 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) + { + Vector4Utilities.UnPremultiply(ref v); + SRgbCompanding.Compress(ref v); + } + + void ExpectedAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + Vector4Utilities.Premultiply(ref v); + } + + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } [Theory] @@ -165,8 +439,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -191,8 +464,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -212,8 +484,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -236,8 +507,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -257,8 +527,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -282,8 +551,226 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromBgra5551Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + + Bgra5551 bgra = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromBgra5551(bgra); + } + + TestOperation( + source, + expected, + (s, d) => 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]; + Bgra5551 bgra = default; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + bgra.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref bgra); + expected[offset] = bytes[0]; + expected[offset + 1] = bytes[1]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromL8(int count) + { + byte[] sourceBytes = CreateByteTestData(count); + L8[] source = sourceBytes.Select(b => new L8(b)).ToArray(); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromL8(source[i]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromL8(this.Configuration, s, d.GetSpan())); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToL8(int count) + { + TPixel[] source = CreatePixelTestData(count); + var expected = new L8[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromScaledVector4(source[i].ToScaledVector4()); + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToL8(this.Configuration, s, d.GetSpan())); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromL16(int count) + { + L16[] source = CreateVector4TestData(count).Select(v => + { + L16 g = default; + g.FromVector4(v); + return g; + }).ToArray(); + + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromL16(source[i]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromL16(this.Configuration, s, d.GetSpan())); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToL16(int count) + { + TPixel[] source = CreatePixelTestData(count); + var expected = new L16[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromScaledVector4(source[i].ToScaledVector4()); + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToL16(this.Configuration, s, d.GetSpan())); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromLa16Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + + La16 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromLa16(la); + } + + TestOperation( + source, + expected, + (s, d) => 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]; + La16 la = default; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + la.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref la); + expected[offset] = bytes[0]; + expected[offset + 1] = bytes[1]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToLa16Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromLa32Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + + La32 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromLa32(la); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromLa32Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToLa32Bytes(int count) + { + int size = Unsafe.SizeOf(); + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * size]; + La32 la = default; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + la.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]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToLa32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -303,8 +790,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -327,8 +813,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -348,8 +833,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -373,8 +857,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -394,8 +877,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -422,8 +904,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -443,8 +924,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -473,29 +953,40 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); } - internal static Vector4[] CreateExpectedVector4Data(TPixel[] source) + public delegate void RefAction(ref T1 arg1); + + internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction vectorModifier = null) { var expected = new Vector4[source.Length]; for (int i = 0; i < expected.Length; i++) { - expected[i] = source[i].ToVector4(); + var v = source[i].ToVector4(); + + vectorModifier?.Invoke(ref v); + + expected[i] = v; } + return expected; } - internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source) + internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source, RefAction vectorModifier = null) { var expected = new Vector4[source.Length]; for (int i = 0; i < expected.Length; i++) { - expected[i] = source[i].ToScaledVector4(); + Vector4 v = source[i].ToScaledVector4(); + + vectorModifier?.Invoke(ref v); + + expected[i] = v; } + return expected; } @@ -513,19 +1004,23 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations } } - internal static Vector4[] CreateVector4TestData(int length) + internal static Vector4[] CreateVector4TestData(int length, RefAction vectorModifier = null) { var result = new Vector4[length]; var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { - result[i] = GetVector(rnd); + Vector4 v = GetVector(rnd); + vectorModifier?.Invoke(ref v); + + result[i] = v; } + return result; } - internal static TPixel[] CreatePixelTestData(int length) + internal static TPixel[] CreatePixelTestData(int length, RefAction vectorModifier = null) { var result = new TPixel[length]; @@ -534,13 +1029,16 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { Vector4 v = GetVector(rnd); + + vectorModifier?.Invoke(ref v); + result[i].FromVector4(v); } return result; } - internal static TPixel[] CreateScaledPixelTestData(int length) + internal static TPixel[] CreateScaledPixelTestData(int length, RefAction vectorModifier = null) { var result = new TPixel[length]; @@ -549,6 +1047,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { Vector4 v = GetVector(rnd); + + vectorModifier?.Invoke(ref v); + result[i].FromScaledVector4(v); } @@ -564,17 +1065,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { result[i] = (byte)rnd.Next(255); } + return result; } internal static Vector4 GetVector(Random rnd) { - return new Vector4( - (float)rnd.NextDouble(), - (float)rnd.NextDouble(), - (float)rnd.NextDouble(), - (float)rnd.NextDouble() - ); + return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); } [StructLayout(LayoutKind.Sequential)] @@ -598,7 +1095,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations where TDest : struct { public TSource[] SourceBuffer { get; } + public IMemoryOwner ActualDestBuffer { get; } + public TDest[] ExpectedDestBuffer { get; } public TestBuffers(TSource[] source, TDest[] expectedDest) @@ -624,6 +1123,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { // ReSharper disable PossibleNullReferenceException Assert.Equal(expected[i], actual[i], comparer); + // ReSharper restore PossibleNullReferenceException } } @@ -639,4 +1139,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs deleted file mode 100644 index 3c562057a8..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs +++ /dev/null @@ -1,104 +0,0 @@ -// 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 46e5fbc3cd..e3e65798a3 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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); + Assert.Equal(6554U, new Rg32(0.1f, -0.3f).PackedValue); // Test the limits. - Assert.Equal((uint)0x0, new Rg32(Vector2.Zero).PackedValue); + Assert.Equal(0x0U, new Rg32(Vector2.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rg32(Vector2.One).PackedValue); } @@ -62,6 +62,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs index 92e8d302d4..926f820c73 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public class Rgb24Tests { public static readonly TheoryData ColorData = - new TheoryData() + new TheoryData { { 1, 2, 3 }, { 4, 5, 6 }, @@ -103,11 +103,31 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void ToRgba32() { + // arrange var rgb = new Rgb24(1, 2, 3); Rgba32 rgba = default; + var expected = new Rgba32(1, 2, 3, 255); + + // act rgb.ToRgba32(ref rgba); - Assert.Equal(new Rgba32(1, 2, 3, 255), rgba); + // assert + Assert.Equal(expected, rgba); + } + + [Fact] + public void Rgb24_FromBgra5551() + { + // arrange + var rgb = new Rgb24(255, 255, 255); + + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // 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 d30e498609..835779e684 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var short3 = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); var expected = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); - // act + // act Vector4 scaled = short3.ToScaledVector4(); pixel.FromScaledVector4(scaled); @@ -58,5 +58,21 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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 a897dd4cdb..ba222c2305 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,6 +9,36 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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() { @@ -16,12 +46,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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(0x7B7DB6DBU, 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); + Assert.Equal(536871014U, 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(0x0U, new Rgba1010102(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rgba1010102(Vector4.One).PackedValue); } @@ -64,6 +94,136 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromGrey16() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromL16(new L16(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() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index ad1d137406..0eb95d4cbe 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -1,11 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.PixelFormats { @@ -22,10 +21,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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"); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000F"); + var color5 = Rgba32.ParseHex("#000000"); + var color6 = Rgba32.ParseHex("#000000FF"); Assert.Equal(color1, color2); Assert.Equal(color1, color3); @@ -42,9 +41,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { 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"); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000000"); + var color5 = Rgba32.ParseHex("#FF000000"); Assert.NotEqual(color1, color2); Assert.NotEqual(color1, color3); @@ -90,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void FromAndToHex() { // 8 digit hex matches css4 spec. RRGGBBAA - var color = Rgba32.FromHex("#AABBCCDD"); // 170, 187, 204, 221 + var color = Rgba32.ParseHex("#AABBCCDD"); // 170, 187, 204, 221 Assert.Equal(170, color.R); Assert.Equal(187, color.G); Assert.Equal(204, color.B); @@ -108,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats } /// - /// Tests that the individual byte elements are layed out in RGBA order. + /// Tests that the individual byte elements are laid out in RGBA order. /// [Fact] public unsafe void ByteLayout() @@ -127,8 +126,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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(0x0U, new Rgba32(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rgba32(Vector4.One).PackedValue); } @@ -205,7 +205,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromRgba32(expected); actual.FromRgba32(rgba); @@ -221,7 +221,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Bgra32); var expected = new Bgra32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromBgra32(expected); actual.FromRgba32(rgba); @@ -237,7 +237,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Argb32); var expected = new Argb32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromArgb32(expected); actual.FromRgba32(rgba); @@ -276,5 +276,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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 3e5d7a56ed..8d29c8e2d4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -12,15 +12,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void Rgba64_PackedValues() { - Assert.Equal((ulong)0x73334CCC2666147B, new Rgba64(5243, 9830, 19660, 29491).PackedValue); + Assert.Equal(0x73334CCC2666147BUL, 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); + 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); @@ -33,37 +31,48 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(Vector4.One, new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); } - [Fact] - public void Rgba64_ToScaledVector4() + [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 - var short2 = new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); + var short2 = new Rgba64(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(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); + Assert.Equal(rr, actual.X); + Assert.Equal(gg, actual.Y); + Assert.Equal(bb, actual.Z); + Assert.Equal(aa, actual.W); } - [Fact] - public void Rgba64_FromScaledVector4() + [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 - var pixel = default(Rgba64); - var short4 = new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); - const ulong expected = 0xFFFFFFFFFFFFFFFF; + var source = new Rgba64(r, g, b, a); + + // act + Vector4 scaled = source.ToScaledVector4(); - // act - Vector4 scaled = short4.ToScaledVector4(); - pixel.FromScaledVector4(scaled); - ulong actual = pixel.PackedValue; + Rgba64 actual = default; + actual.FromScaledVector4(scaled); // assert - Assert.Equal(expected, actual); + Assert.Equal(source, actual); } [Fact] @@ -91,5 +100,188 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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); + } + + [Fact] + public void Equality_WhenTrue() + { + var c1 = new Rgba64(100, 2000, 3000, 40000); + var c2 = new Rgba64(100, 2000, 3000, 40000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + var c1 = new Rgba64(100, 2000, 3000, 40000); + var c2 = new Rgba64(101, 2000, 3000, 40000); + var c3 = new Rgba64(100, 2000, 3000, 40001); + + Assert.False(c1.Equals(c2)); + Assert.False(c2.Equals(c3)); + Assert.False(c3.Equals(c1)); + } + + [Fact] + public void Rgba64_FromRgba32() + { + var source = new Rgba32(20, 38, 76, 115); + var expected = new Rgba64(5140, 9766, 19532, 29555); + + Rgba64 actual = default; + actual.FromRgba32(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Rgba32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Rgba32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Bgra32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Bgra32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Argb32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Argb32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Rgb24() + { + var expected = new Rgba64(5140, 9766, 19532, ushort.MaxValue); + var source = new Rgb24(20, 38, 76); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Bgr24() + { + var expected = new Rgba64(5140, 9766, 19532, ushort.MaxValue); + var source = new Bgr24(20, 38, 76); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Vector4() + { + var source = new Vector4(0f, 0.2f, 0.5f, 1f); + Rgba64 expected = default; + expected.FromScaledVector4(source); + + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ToRgba32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Rgba32(20, 38, 76, 115); + + // act + var actual = source.ToRgba32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToBgra32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Bgra32(20, 38, 76, 115); + + // act + var actual = source.ToBgra32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToArgb32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Argb32(20, 38, 76, 115); + + // act + var actual = source.ToArgb32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToRgb24_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Rgb24(20, 38, 76); + + // act + var actual = source.ToRgb24(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToBgr24_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Bgr24(20, 38, 76); + + // act + var actual = source.ToBgr24(); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs index e880e38517..de6cc09de7 100644 --- a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats } /// - /// Tests that the individual float elements are layed out in RGBA order. + /// Tests that the individual float elements are laid out in RGBA order. /// [Fact] public void FloatLayout() @@ -146,5 +146,47 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, rgba.ToScaledVector4()); + } + + [Fact] + public void RgbaVector_FromGrey8() + { + // arrange + var rgba = default(RgbaVector); + Vector4 expected = Vector4.One; + + // act + rgba.FromL8(new L8(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 c9a3b33c9a..b65299cccd 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -13,11 +13,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void Short2_PackedValues() { // Test ordering - Assert.Equal((uint)0x361d2db1, new Short2(0x2db1, 0x361d).PackedValue); + Assert.Equal(0x361d2db1U, 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(0x0U, new Short2(Vector2.Zero).PackedValue); + Assert.Equal(0x7FFF7FFFU, new Short2(Vector2.One * 0x7FFF).PackedValue); Assert.Equal(0x80008000, new Short2(Vector2.One * -0x8000).PackedValue); } @@ -34,9 +35,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [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()); + 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] @@ -70,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var short2 = new Short2(Vector2.One * 0x7FFF); const ulong expected = 0x7FFF7FFF; - // act + // act Vector4 scaled = short2.ToScaledVector4(); pixel.FromScaledVector4(scaled); uint actual = pixel.PackedValue; @@ -102,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(20, 38, 0, 255); - // act + // act short2.FromRgba32(expected); short2.ToRgba32(ref actual); @@ -141,5 +142,22 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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 247342a053..8da95e0f50 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats 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(0x00c173b7316d2d1bUL, 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(0x0UL, new Short4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new Short4(Vector4.One * 0x7FFF).PackedValue); Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); } @@ -61,7 +61,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // act var pixel = default(Short4); pixel.FromScaledVector4(scaled); - ulong actual = pixel.PackedValue; // assert Assert.Equal((ulong)expected, pixel.PackedValue); @@ -106,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(20, 38, 0, 255); - // act + // act short4.FromRgba32(expected); short4.ToRgba32(ref actual); @@ -122,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Bgra32); var expected = new Bgra32(20, 38, 0, 255); - // act + // act short4.FromBgra32(expected); Rgba32 temp = default; short4.ToRgba32(ref temp); @@ -140,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Argb32); var expected = new Argb32(20, 38, 0, 255); - // act + // act short4.FromArgb32(expected); Rgba32 temp = default; short4.ToRgba32(ref temp); @@ -181,5 +180,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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 bd8c647421..b8c7786211 100644 --- a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Colors [Fact] public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() { - var color = Rgba32.FromHex("183060C0"); + var color = Rgba32.ParseHex("183060C0"); var colorVector = RgbaVector.FromHex("183060C0"); Assert.Equal(color.R, (byte)(colorVector.R * 255)); diff --git a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs index 2fbe260ecd..c95d70532f 100644 --- a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs @@ -1,6 +1,8 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Globalization; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; using Xunit; @@ -8,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives { public class ColorMatrixTests { - private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + private readonly ApproximateFloatComparer approximateFloatComparer = new ApproximateFloatComparer(1e-6f); [Fact] public void ColorMatrixIdentityIsCorrect() @@ -16,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives ColorMatrix val = default; val.M11 = val.M22 = val.M33 = val.M44 = 1F; - Assert.Equal(val, ColorMatrix.Identity, this.ApproximateFloatComparer); + Assert.Equal(val, ColorMatrix.Identity, this.approximateFloatComparer); } [Fact] @@ -48,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives ColorMatrix value1 = this.CreateAllTwos(); ColorMatrix value2 = this.CreateAllThrees(); - ColorMatrix m; + var m = default(ColorMatrix); // First row m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); @@ -80,14 +82,14 @@ namespace SixLabors.ImageSharp.Tests.Primitives 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); + Assert.Equal(m, value1 * value2, this.approximateFloatComparer); } [Fact] public void ColorMatrixMultiplyScalar() { ColorMatrix m = this.CreateAllTwos(); - Assert.Equal(this.CreateAllFours(), m * 2, this.ApproximateFloatComparer); + Assert.Equal(this.CreateAllFours(), m * 2, this.approximateFloatComparer); } [Fact] @@ -149,12 +151,14 @@ namespace SixLabors.ImageSharp.Tests.Primitives 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)); + 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 Assert.Equal(expected, m.ToString()); } diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs index 0af8ae45f9..2b37b24b7b 100644 --- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs @@ -1,9 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Primitives @@ -19,37 +17,25 @@ namespace SixLabors.ImageSharp.Tests.Primitives [Fact] public void DenseMatrixThrowsOnNullInitializer() { - Assert.Throws(() => - { - var dense = new DenseMatrix(null); - }); + Assert.Throws(() => new DenseMatrix(null)); } [Fact] public void DenseMatrixThrowsOnEmptyZeroWidth() { - Assert.Throws(() => - { - var dense = new DenseMatrix(0, 10); - }); + Assert.Throws(() => new DenseMatrix(0, 10)); } [Fact] public void DenseMatrixThrowsOnEmptyZeroHeight() { - Assert.Throws(() => - { - var dense = new DenseMatrix(10, 0); - }); + Assert.Throws(() => new DenseMatrix(10, 0)); } [Fact] public void DenseMatrixThrowsOnEmptyInitializer() { - Assert.Throws(() => - { - var dense = new DenseMatrix(new float[0, 0]); - }); + Assert.Throws(() => new DenseMatrix(new float[0, 0])); } [Fact] @@ -130,5 +116,25 @@ namespace SixLabors.ImageSharp.Tests.Primitives Assert.Equal(2, transposed[1, 0]); Assert.Equal(3, transposed[2, 0]); } + + [Fact] + public void DenseMatrixEquality() + { + var dense = new DenseMatrix(3, 1); + var dense2 = new DenseMatrix(3, 1); + var dense3 = new DenseMatrix(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()); + } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Primitives/PointFTests.cs b/tests/ImageSharp.Tests/Primitives/PointFTests.cs new file mode 100644 index 0000000000..c09cfe50c1 --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/PointFTests.cs @@ -0,0 +1,213 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Numerics; +using System.Runtime.CompilerServices; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class PointFTests + { + private static readonly ApproximateFloatComparer ApproximateFloatComparer = + new ApproximateFloatComparer(1e-6f); + + [Fact] + public void CanReinterpretCastFromVector2() + { + var vector = new Vector2(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) + { + var p1 = new PointF(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) + { + var p = new PointF(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) + { + var p = new PointF(x, y); + var s = new Size(x1, y1); + + var addExpected = new PointF(x + x1, y + y1); + var subExpected = new PointF(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) + { + var p = new PointF(x, y); + var s = new SizeF(y, x); + + var addExpected = new PointF(x + y, y + x); + var subExpected = new PointF(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() + { + var p = new PointF(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty); + + var pout = PointF.Transform(p, matrix); + + Assert.Equal(-2.82842732F, pout.X, ApproximateFloatComparer); + Assert.Equal(21.2132034F, pout.Y, ApproximateFloatComparer); + } + + [Fact] + public void SkewTest() + { + var p = new PointF(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, PointF.Empty); + + var 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) + { + var pLeft = new PointF(x, y); + var pRight = new PointF(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() + { + var point = new PointF(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() + { + var point = new PointF(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() + { + var p = new PointF(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 PointF(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 new file mode 100644 index 0000000000..ffa025f563 --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/PointTests.cs @@ -0,0 +1,258 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Numerics; +using Xunit; + +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) + { + var p1 = new Point(x, y); + var p2 = new Point(new Size(x, y)); + + Assert.Equal(p1, p2); + } + + [Theory] + [InlineData(int.MaxValue)] + [InlineData(int.MinValue)] + [InlineData(0)] + public void SingleIntConstructorTest(int x) + { + var p1 = new Point(x); + var p2 = new Point(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) + { + var p = new Point(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) + { + var 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 Point(x, y); + var s = new Size(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) + { + var pf = new PointF(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) + { + var p1 = new Point(x, y); + var p2 = new Point(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() + { + var p = new Point(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); + + var pout = Point.Transform(p, matrix); + + Assert.Equal(new Point(-3, 21), pout); + } + + [Fact] + public void SkewTest() + { + var p = new Point(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty); + + var 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) + { + var p1 = new Point(x, y); + var p2 = new Point((x / 2) - 1, (y / 2) - 1); + var p3 = new Point(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() + { + var point = new Point(0, 0); + Assert.False(point.Equals(null)); + Assert.False(point.Equals(0)); + Assert.False(point.Equals(new PointF(0, 0))); + } + + [Fact] + public void GetHashCodeTest() + { + var point = new Point(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) + { + var rect = new Rectangle(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() + { + var p = new Point(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 Point(x, y); + + (int deconstructedX, int deconstructedY) = p; + + Assert.Equal(x, deconstructedX); + Assert.Equal(y, deconstructedY); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs new file mode 100644 index 0000000000..66791fd3c5 --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs @@ -0,0 +1,285 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using Xunit; + +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) + { + var rect1 = new RectangleF(x, y, width, height); + var p = new PointF(x, y); + var s = new SizeF(width, height); + var rect2 = new RectangleF(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) + { + var expected = new RectangleF(left, top, right - left, bottom - top); + var 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) + { + var rect = new RectangleF(x, y, width, height); + var p = new PointF(x, y); + var s = new SizeF(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) + { + var point = new PointF(x, y); + var rect = new RectangleF(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) + { + var size = new SizeF(x, y); + var rect = new RectangleF(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) + { + var rect1 = new RectangleF(x, y, width, height); + var rect2 = new RectangleF(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() + { + var rectangle = new RectangleF(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() + { + var rect1 = new RectangleF(10, 10, 10, 10); + var rect2 = new RectangleF(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) + { + var rect = new RectangleF(x, y, width, height); + float x1 = (x + width) / 2; + float y1 = (y + height) / 2; + var p = new PointF(x1, y1); + var r = new RectangleF(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) + { + var rect = new RectangleF(x, y, width, height); + var inflatedRect = new RectangleF(x - width, y - height, width + (2 * width), height + (2 * height)); + + rect.Inflate(width, height); + Assert.Equal(inflatedRect, rect); + + var s = new SizeF(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) + { + var rect1 = new RectangleF(x, y, width, height); + var rect2 = new RectangleF(y, x, width, height); + var expectedRect = RectangleF.Intersect(rect1, rect2); + rect1.Intersect(rect2); + Assert.Equal(expectedRect, rect1); + Assert.False(rect1.IntersectsWith(expectedRect)); + } + + [Fact] + public void IntersectIntersectingRectsTest() + { + var rect1 = new RectangleF(0, 0, 5, 5); + var rect2 = new RectangleF(1, 1, 3, 3); + var expected = new RectangleF(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) + { + var a = new RectangleF(x, y, width, height); + var b = new RectangleF(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); + + var expectedRectangle = new RectangleF(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) + { + var r1 = new RectangleF(x, y, width, height); + var expectedRect = new RectangleF(x + width, y + height, width, height); + var p = new PointF(width, height); + + r1.Offset(p); + Assert.Equal(expectedRect, r1); + + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Fact] + public void ToStringTest() + { + var r = new RectangleF(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 RectangleF(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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs new file mode 100644 index 0000000000..94c45ed364 --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs @@ -0,0 +1,336 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using Xunit; + +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) + { + var rect1 = new Rectangle(x, y, width, height); + var rect2 = new Rectangle(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) + { + var rect1 = new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); + var 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) + { + var rect = new Rectangle(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); + + var p = new Point(width, height); + var s = new Size(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) + { + var point = new Point(x, y); + var rect = new Rectangle(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) + { + var size = new Size(x, y); + var rect = new Rectangle(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) + { + var rect1 = new Rectangle(x, y, width, height); + var rect2 = new Rectangle(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() + { + var rectangle = new Rectangle(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() + { + var rect1 = new Rectangle(10, 10, 10, 10); + var rect2 = new Rectangle(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) + { + var rect = new RectangleF(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) + { + var rect = new Rectangle(unchecked((2 * x) - width), unchecked((2 * y) - height), width, height); + var p = new Point(x, y); + var r = new Rectangle(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 Rectangle(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); + + var s = new Size(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) + { + var rect = new Rectangle(x, y, width, height); + var expectedRect = Rectangle.Intersect(rect, rect); + rect.Intersect(rect); + Assert.Equal(expectedRect, rect); + Assert.False(rect.IntersectsWith(expectedRect)); + } + + [Fact] + public void IntersectIntersectingRectsTest() + { + var rect1 = new Rectangle(0, 0, 5, 5); + var rect2 = new Rectangle(1, 1, 3, 3); + var expected = new Rectangle(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) + { + var a = new Rectangle(x, y, width, height); + var b = new Rectangle(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); + + var expectedRectangle = new Rectangle(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) + { + var r1 = new Rectangle(x, y, width, height); + var expectedRect = new Rectangle(x + width, y + height, width, height); + var p = new Point(width, height); + + r1.Offset(p); + Assert.Equal(expectedRect, r1); + + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Fact] + public void ToStringTest() + { + var r = new Rectangle(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) + { + var r = new Rectangle(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 new file mode 100644 index 0000000000..1db4d3863b --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/SizeFTests.cs @@ -0,0 +1,249 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using Xunit; + +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) + { + var s1 = new SizeF(width, height); + var p1 = new PointF(width, height); + var s2 = new SizeF(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) + { + var s1 = new SizeF(width, height); + var s2 = new SizeF(height, width); + var addExpected = new SizeF(width + height, width + height); + var subExpected = new SizeF(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) + { + var sLeft = new SizeF(width, height); + var sRight = new SizeF(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() + { + var size = new SizeF(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() + { + var size = new SizeF(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) + { + var s1 = new SizeF(width, height); + var p1 = (PointF)s1; + var s2 = new Size(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() + { + var sz = new SizeF(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 SizeF(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 SizeF(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 SizeF(dimension, dimension); + SizeF expected = new SizeF(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 SizeF(width, height); + SizeF expected = new SizeF(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 SizeF(width, height); + + (float deconstructedWidth, float deconstructedHeight) = s; + + Assert.Equal(width, deconstructedWidth); + Assert.Equal(height, deconstructedHeight); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Primitives/SizeTests.cs b/tests/ImageSharp.Tests/Primitives/SizeTests.cs new file mode 100644 index 0000000000..e011e417bd --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/SizeTests.cs @@ -0,0 +1,379 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using Xunit; + +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) + { + var s1 = new Size(width, height); + var s2 = new Size(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) + { + var p = new Size(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) + { + var 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) + { + var sz1 = new Size(width, height); + var sz2 = new Size(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) + { + var szF = new SizeF(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) + { + var p1 = new Size(width, height); + var p2 = new Size(unchecked(width - 1), unchecked(height - 1)); + var p3 = new Size(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() + { + var size = new Size(0, 0); + Assert.False(size.Equals(null)); + Assert.False(size.Equals(0)); + Assert.False(size.Equals(new SizeF(0, 0))); + } + + [Fact] + public void GetHashCodeTest() + { + var size = new Size(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() + { + var sz = new Size(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 Size(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 Size(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 Size(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 Size(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 Size(100, 100); + Assert.Throws(() => size / 0); + + SizeF expectedSizeF = new SizeF(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 Size(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 Size(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 SizeF(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 SizeF(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 Size(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 new file mode 100644 index 0000000000..ae9befba0d --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.ComponentModel.DataAnnotations; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing +{ + 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 { 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.GetConfiguration(), 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); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs new file mode 100644 index 0000000000..8efac7593d --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs @@ -0,0 +1,127 @@ +// Copyright (c) Six Labors. +// 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.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class AdaptiveThresholdTests : BaseImageOperationsExtensionTest + { + [Fact] + public void AdaptiveThreshold_UsesDefaults_Works() + { + // arrange + var 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 + var 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 + var 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 + var 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)] + 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 deleted file mode 100644 index 5f6e825f63..0000000000 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs +++ /dev/null @@ -1,107 +0,0 @@ -// 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 569c4ba217..5bdfda02eb 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Binarization; @@ -15,40 +14,40 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void BinaryThreshold_CorrectProcessor() { this.operations.BinaryThreshold(.23f); - BinaryThresholdProcessor p = this.Verify>(); + BinaryThresholdProcessor p = this.Verify(); Assert.Equal(.23f, p.Threshold); - Assert.Equal(NamedColors.White, p.UpperColor); - Assert.Equal(NamedColors.Black, p.LowerColor); + 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); + BinaryThresholdProcessor p = this.Verify(this.rect); Assert.Equal(.93f, p.Threshold); - Assert.Equal(NamedColors.White, p.UpperColor); - Assert.Equal(NamedColors.Black, p.LowerColor); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); } [Fact] public void BinaryThreshold_CorrectProcessorWithUpperLower() { - this.operations.BinaryThreshold(.23f, NamedColors.HotPink, NamedColors.Yellow); - BinaryThresholdProcessor p = this.Verify>(); + this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow); + BinaryThresholdProcessor p = this.Verify(); Assert.Equal(.23f, p.Threshold); - Assert.Equal(NamedColors.HotPink, p.UpperColor); - Assert.Equal(NamedColors.Yellow, p.LowerColor); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.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); + this.operations.BinaryThreshold(.93f, Color.HotPink, Color.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); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.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 c98f910464..0bbb962fc9 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Dithering; using Xunit; @@ -10,6 +9,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization { public class OrderedDitherFactoryTests { +#pragma warning disable SA1025 // Code should not contain multiple whitespace in a row + private static readonly DenseMatrix Expected2x2Matrix = new DenseMatrix( new uint[2, 2] { @@ -47,6 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization { 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() @@ -100,4 +102,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization } } } -} \ 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 e425b63151..eb176f5f03 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void BoxBlur_BoxBlurProcessorDefaultsSet() { this.operations.BoxBlur(); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(7, processor.Radius); } @@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void BoxBlur_amount_BoxBlurProcessorDefaultsSet() { this.operations.BoxBlur(34); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(34, processor.Radius); } @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void BoxBlur_amount_rect_BoxBlurProcessorDefaultsSet() { this.operations.BoxBlur(5, this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); Assert.Equal(5, processor.Radius); } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index 60fa19b490..3ffb8f4e33 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -1,75 +1,201 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; -using SixLabors.ImageSharp.PixelFormats; +using System.Diagnostics.CodeAnalysis; 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 { + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")] public class DetectEdgesTest : BaseImageOperationsExtensionTest { - [Fact] - public void DetectEdges_SobelProcessorDefaultsSet() + public void DetectEdges_EdgeDetector2DProcessorDefaultsSet() { this.operations.DetectEdges(); + EdgeDetector2DProcessor processor = this.Verify(); - // TODO: Enable once we have updated the images - // SobelProcessor processor = this.Verify>(); - // Assert.True(processor.Grayscale); + Assert.True(processor.Grayscale); + Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); } [Fact] - public void DetectEdges_Rect_SobelProcessorDefaultsSet() + 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 = + new TheoryData + { + { 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(); + + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) + { + this.operations.DetectEdges(kernel, this.rect); + 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(kernel, grayscale, this.rect); + EdgeDetector2DProcessor processor = this.Verify(this.rect); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + public static TheoryData EdgeDetectorKernelData = + new TheoryData + { + { KnownEdgeDetectorKernels.Laplacian3x3, true }, + { KnownEdgeDetectorKernels.Laplacian3x3, false }, + { KnownEdgeDetectorKernels.Laplacian5x5, true }, + { KnownEdgeDetectorKernels.Laplacian5x5, false }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, true }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, false }, + }; + + [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(kernel, this.rect); + 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(kernel, grayscale, this.rect); + EdgeDetectorProcessor processor = this.Verify(this.rect); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + public static TheoryData EdgeDetectorCompassKernelData = + new TheoryData + { + { KnownEdgeDetectorKernels.Kirsch, true }, + { KnownEdgeDetectorKernels.Kirsch, false }, + { KnownEdgeDetectorKernels.Robinson, true }, + { KnownEdgeDetectorKernels.Robinson, false }, + }; + + [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(kernel, this.rect); + EdgeDetectorCompassProcessor processor = this.Verify(this.rect); - // TODO: Enable once we have updated the images - // SobelProcessor processor = this.Verify>(this.rect); - // Assert.True(processor.Grayscale); + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } - 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(EdgeDetectionTheoryData))] - public void DetectEdges_filter_SobelProcessorDefaultsSet(TestType type, EdgeDetectionOperators filter) - where TProcessor : IEdgeDetectorProcessor + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) { - this.operations.DetectEdges(filter); + this.operations.DetectEdges(kernel, grayscale); + EdgeDetectorCompassProcessor processor = this.Verify(); - // TODO: Enable once we have updated the images - // var processor = this.Verify(); - // Assert.True(processor.Grayscale); + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } [Theory] - [MemberData(nameof(EdgeDetectionTheoryData))] - public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet(TestType type, EdgeDetectionOperators filter) - where TProcessor : IEdgeDetectorProcessor + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) { - bool grey = (int)filter % 2 == 0; - this.operations.DetectEdges(filter, grey); + this.operations.DetectEdges(kernel, grayscale, this.rect); + EdgeDetectorCompassProcessor processor = this.Verify(this.rect); - // TODO: Enable once we have updated the images - // var processor = this.Verify() - // Assert.Equal(grey, processor.Grayscale); + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } } -} \ 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 c87a834eb6..26454fcb6b 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void GaussianBlur_GaussianBlurProcessorDefaultsSet() { this.operations.GaussianBlur(); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(3f, processor.Sigma); } @@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet() { this.operations.GaussianBlur(0.2f); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(.2f, processor.Sigma); } @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void GaussianBlur_amount_rect_GaussianBlurProcessorDefaultsSet() { this.operations.GaussianBlur(0.6f, this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); Assert.Equal(.6f, processor.Sigma); } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs index 675498745e..d264e82e1d 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet() { this.operations.GaussianSharpen(); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(3f, processor.Sigma); } @@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet() { this.operations.GaussianSharpen(0.2f); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(.2f, processor.Sigma); } @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet() { this.operations.GaussianSharpen(0.6f, this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); Assert.Equal(.6f, processor.Sigma); } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs new file mode 100644 index 0000000000..b50e5d7cc9 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using Xunit; + +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 8b3524fe66..73f6a3f473 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Convolution; using Xunit; @@ -23,11 +22,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution 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 } + { -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] @@ -65,4 +64,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/DelegateTest.cs b/tests/ImageSharp.Tests/Processing/DelegateTest.cs deleted file mode 100644 index 73d3c80230..0000000000 --- a/tests/ImageSharp.Tests/Processing/DelegateTest.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 f393d5923d..9f0a80453a 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -1,106 +1,174 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using System; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering; - using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Binarization { public class DitherTest : BaseImageOperationsExtensionTest { - private readonly IOrderedDither orderedDither; - private readonly IErrorDiffuser errorDiffuser; - private readonly Rgba32[] TestPalette = + private class Assert : Xunit.Assert { - Rgba32.Red, - Rgba32.Green, - Rgba32.Blue + public static void Equal(ReadOnlySpan a, ReadOnlySpan b) + { + 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 = KnownDitherers.BayerDither4x4; - this.errorDiffuser = KnownDiffusers.FloydSteinberg; + this.orderedDither = KnownDitherings.Bayer4x4; + this.errorDiffuser = KnownDitherings.FloydSteinberg; } [Fact] public void Dither_CorrectProcessor() { this.operations.Dither(this.orderedDither); - OrderedDitherPaletteProcessor p = this.Verify>(); + PaletteDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(NamedColors.WebSafePalette, p.Palette); + Assert.Equal(Color.WebSafePalette, p.Palette); } [Fact] public void Dither_rect_CorrectProcessor() { this.operations.Dither(this.orderedDither, this.rect); - OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(NamedColors.WebSafePalette, p.Palette); + Assert.Equal(Color.WebSafePalette, p.Palette); } + [Fact] public void Dither_index_CorrectProcessor() { - this.operations.Dither(this.orderedDither, this.TestPalette); - OrderedDitherPaletteProcessor p = this.Verify>(); + this.operations.Dither(this.orderedDither, this.testPalette); + PaletteDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(this.TestPalette, p.Palette); + Assert.Equal(this.testPalette, p.Palette); } [Fact] public void Dither_index_rect_CorrectProcessor() { - this.operations.Dither(this.orderedDither, this.TestPalette, this.rect); - OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + 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); + Assert.Equal(this.testPalette, p.Palette); } - [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); + 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.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); + 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.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); + this.operations.Dither(this.errorDiffuser, this.testPalette); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.errorDiffuser, p.Dither); + Assert.Equal(this.testPalette, p.Palette); } [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); + 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); + } + + [Fact] + public void ErrorDitherEquality() + { + IDither dither = KnownDitherings.FloydSteinberg; + ErrorDither dither2 = ErrorDither.FloydSteinberg; + ErrorDither dither3 = ErrorDither.FloydSteinberg; + + 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()); + + 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()); + + 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; + + 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()); + + 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()); + + 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()); } } -} \ 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 7775de2d24..5bc6256d9d 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Effects @@ -14,41 +13,41 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects [Fact] public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet() { - this.operations.BackgroundColor(Rgba32.BlanchedAlmond); - var processor = this.Verify>(); + this.operations.BackgroundColor(Color.BlanchedAlmond); + BackgroundColorProcessor processor = this.Verify(); - Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions); - Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + Assert.Equal(this.options, processor.GraphicsOptions); + Assert.Equal(Color.BlanchedAlmond, processor.Color); } [Fact] public void BackgroundColor_amount_rect_BackgroundColorProcessorDefaultsSet() { - this.operations.BackgroundColor(Rgba32.BlanchedAlmond, this.rect); - var processor = this.Verify>(this.rect); + this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect); + BackgroundColorProcessor processor = this.Verify(this.rect); - Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions); - Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + Assert.Equal(this.options, processor.GraphicsOptions); + Assert.Equal(Color.BlanchedAlmond, processor.Color); } [Fact] public void BackgroundColor_amount_options_BackgroundColorProcessorDefaultsSet() { - this.operations.BackgroundColor(this.options, Rgba32.BlanchedAlmond); - var processor = this.Verify>(); + this.operations.BackgroundColor(this.options, Color.BlanchedAlmond); + BackgroundColorProcessor processor = this.Verify(); Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + Assert.Equal(Color.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); + this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect); + BackgroundColorProcessor processor = this.Verify(this.rect); Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + Assert.Equal(Color.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 9cd24fc6d1..2fd7ac7efc 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Effects; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void OilPaint_OilPaintingProcessorDefaultsSet() { this.operations.OilPaint(); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(10, processor.Levels); Assert.Equal(15, processor.BrushSize); @@ -24,29 +23,30 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void OilPaint_rect_OilPaintingProcessorDefaultsSet() { this.operations.OilPaint(this.rect); - var processor = this.Verify>(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() + public void OilPaint_Levels_Brush_OilPaintingProcessorDefaultsSet() { this.operations.OilPaint(34, 65); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(34, processor.Levels); Assert.Equal(65, processor.BrushSize); } [Fact] - public void OilPaint_Levels_Brsuh_rect_OilPaintingProcessorDefaultsSet() + public void OilPaint_Levels_Brush_rect_OilPaintingProcessorDefaultsSet() { this.operations.OilPaint(54, 43, this.rect); - var processor = this.Verify>(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 a93eaf0bc6..33061e1e48 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Effects; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Pixelate_PixelateProcessorDefaultsSet() { this.operations.Pixelate(); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(4, processor.Size); } @@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Pixelate_Size_PixelateProcessorDefaultsSet() { this.operations.Pixelate(12); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(12, processor.Size); } @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Pixelate_Size_rect_PixelateProcessorDefaultsSet() { this.operations.Pixelate(23, this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); Assert.Equal(23, processor.Size); } diff --git a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs new file mode 100644 index 0000000000..03fa6dfc2e --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +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 = new List(); + + 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 + { + var op = new FakeImageOperations(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; } = new List(); + + 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 d651f2f04e..f87ace1897 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; @@ -14,14 +13,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void BlackWhite_CorrectProcessor() { this.operations.BlackWhite(); - BlackWhiteProcessor p = this.Verify>(); + this.Verify(); } [Fact] public void BlackWhite_rect_CorrectProcessor() { this.operations.BlackWhite(this.rect); - BlackWhiteProcessor p = this.Verify>(this.rect); + 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 e210450a8c..75a9072c59 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Brightness_amount_BrightnessProcessorDefaultsSet() { this.operations.Brightness(1.5F); - BrightnessProcessor processor = this.Verify>(); + BrightnessProcessor processor = this.Verify(); Assert.Equal(1.5F, processor.Amount); } @@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Brightness_amount_rect_BrightnessProcessorDefaultsSet() { this.operations.Brightness(1.5F, this.rect); - BrightnessProcessor processor = this.Verify>(this.rect); + BrightnessProcessor processor = this.Verify(this.rect); Assert.Equal(1.5F, processor.Amount); } diff --git a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs index aeafe5fe1d..e65b67815a 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs @@ -1,12 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Filters; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; @@ -15,32 +14,34 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters { public class ColorBlindnessTest : BaseImageOperationsExtensionTest { - 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 } + 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_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) - where T : IImageProcessor + where T : IImageProcessor { this.operations.ColorBlindness(colorBlindness); - T p = this.Verify(); + this.Verify(); } + [Theory] [MemberData(nameof(TheoryData))] public void ColorBlindness_rect_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) - where T : IImageProcessor + where T : IImageProcessor { this.operations.ColorBlindness(colorBlindness, this.rect); - T p = this.Verify(this.rect); + 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 21a552e6af..e181999fa3 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Effects @@ -16,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Contrast_amount_ContrastProcessorDefaultsSet() { this.operations.Contrast(1.5F); - ContrastProcessor processor = this.Verify>(); + ContrastProcessor processor = this.Verify(); Assert.Equal(1.5F, processor.Amount); } @@ -25,9 +23,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Contrast_amount_rect_ContrastProcessorDefaultsSet() { this.operations.Contrast(1.5F, this.rect); - ContrastProcessor processor = this.Verify>(this.rect); + ContrastProcessor 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/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs index 414a0d74e4..15945e4680 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs @@ -1,9 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters @@ -17,14 +14,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Filter_CorrectProcessor() { this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F)); - FilterProcessor p = this.Verify>(); + this.Verify(); } [Fact] public void Filter_rect_CorrectProcessor() { this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F), this.rect); - FilterProcessor p = this.Verify>(this.rect); + 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 d63d978207..36c2ff7699 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs @@ -1,12 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Filters; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; @@ -15,23 +14,24 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters { public class GrayscaleTest : BaseImageOperationsExtensionTest { - public static IEnumerable ModeTheoryData = new[] { - new object[]{ new TestType>(), GrayscaleMode.Bt709 } + public static IEnumerable ModeTheoryData = new[] + { + new object[] { new TestType(), GrayscaleMode.Bt709 } }; [Theory] [MemberData(nameof(ModeTheoryData))] public void Grayscale_mode_CorrectProcessor(TestType testType, GrayscaleMode mode) - where T : IImageProcessor + where T : IImageProcessor { this.operations.Grayscale(mode); - var p = this.Verify(); + this.Verify(); } [Theory] [MemberData(nameof(ModeTheoryData))] public void Grayscale_mode_rect_CorrectProcessor(TestType testType, GrayscaleMode mode) - where T : IImageProcessor + where T : IImageProcessor { this.operations.Grayscale(mode, this.rect); this.Verify(this.rect); @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Grayscale_rect_CorrectProcessor() { this.operations.Grayscale(this.rect); - this.Verify>(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 f56578dd68..9d85af5896 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; @@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Hue_amount_HueProcessorDefaultsSet() { this.operations.Hue(34f); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(34f, processor.Degrees); } @@ -24,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Hue_amount_rect_HueProcessorDefaultsSet() { this.operations.Hue(5f, this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); Assert.Equal(5f, processor.Degrees); } diff --git a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs index c93afc9427..e773a177fe 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; @@ -14,14 +13,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Invert_InvertProcessorDefaultsSet() { this.operations.Invert(); - var processor = this.Verify>(); + this.Verify(); } [Fact] public void Pixelate_rect_PixelateProcessorDefaultsSet() { this.operations.Invert(this.rect); - var processor = this.Verify>(this.rect); + 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 a982521404..798c0e0550 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; @@ -14,14 +13,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Kodachrome_amount_KodachromeProcessorDefaultsSet() { this.operations.Kodachrome(); - var processor = this.Verify>(); + this.Verify(); } [Fact] public void Kodachrome_amount_rect_KodachromeProcessorDefaultsSet() { this.operations.Kodachrome(this.rect); - var processor = this.Verify>(this.rect); + 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 new file mode 100644 index 0000000000..cbf44e4c69 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Effects +{ + 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 c104f8c252..e7d289ea5d 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs @@ -1,10 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.IO; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; +using SixLabors.ImageSharp.Tests.Processing; + using Xunit; namespace SixLabors.ImageSharp.Tests @@ -18,14 +16,16 @@ namespace SixLabors.ImageSharp.Tests public void Lomograph_amount_LomographProcessorDefaultsSet() { this.operations.Lomograph(); - var processor = this.Verify>(); + var processor = this.Verify(); + Assert.Equal(processor.GraphicsOptions, this.options); } [Fact] public void Lomograph_amount_rect_LomographProcessorDefaultsSet() { this.operations.Lomograph(this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); + Assert.Equal(processor.GraphicsOptions, this.options); } } -} \ 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 adbb8cf295..8e8b4636ce 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Alpha_amount_AlphaProcessorDefaultsSet() { this.operations.Opacity(0.2f); - OpacityProcessor processor = this.Verify>(); + OpacityProcessor processor = this.Verify(); Assert.Equal(.2f, processor.Amount); } @@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Alpha_amount_rect_AlphaProcessorDefaultsSet() { this.operations.Opacity(0.6f, this.rect); - OpacityProcessor processor = this.Verify>(this.rect); + OpacityProcessor processor = this.Verify(this.rect); Assert.Equal(.6f, processor.Amount); } diff --git a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs index f28827b716..2cfd8519d8 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; @@ -15,14 +14,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Polaroid_amount_PolaroidProcessorDefaultsSet() { this.operations.Polaroid(); - var processor = this.Verify>(); + var processor = this.Verify(); + Assert.Equal(processor.GraphicsOptions, this.options); } [Fact] public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet() { this.operations.Polaroid(this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); + Assert.Equal(processor.GraphicsOptions, this.options); } } -} \ 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 4b8e80881c..b61a12102e 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Saturation_amount_SaturationProcessorDefaultsSet() { this.operations.Saturate(34); - SaturateProcessor processor = this.Verify>(); + SaturateProcessor processor = this.Verify(); Assert.Equal(34, processor.Amount); } @@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Saturation_amount_rect_SaturationProcessorDefaultsSet() { this.operations.Saturate(5, this.rect); - SaturateProcessor processor = this.Verify>(this.rect); + SaturateProcessor processor = this.Verify(this.rect); Assert.Equal(5, processor.Amount); } diff --git a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs index 9351c8443f..c7f85b732d 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; @@ -14,14 +13,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Sepia_amount_SepiaProcessorDefaultsSet() { this.operations.Sepia(); - var processor = this.Verify>(); + this.Verify(); } [Fact] public void Sepia_amount_rect_SepiaProcessorDefaultsSet() { this.operations.Sepia(this.rect); - var processor = this.Verify>(this.rect); + 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 new file mode 100644 index 0000000000..cd0a65ad51 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs @@ -0,0 +1,178 @@ +// Copyright (c) Six Labors. +// 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.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(); + + var processorMock = new Mock(); + 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() + { + var operations = new FakeImageOperationsProvider.FakeImageOperations(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 + { + var img = new Image(1, 1); + img.Dispose(); + img.EnsureNotDisposed(); + } + catch (ObjectDisposedException ex) + { + return ex.Message; + } + + return "?"; + } + + private static void CheckThrowsCorrectObjectDisposedException(Action action) + { + var 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 new file mode 100644 index 0000000000..c206938a24 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs @@ -0,0 +1,199 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using Moq; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing +{ + /// + /// Contains test cases for default implementation. + /// + public class ImageProcessingContextTests + { + 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 Rectangle(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 TheoryData() + { + { 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); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index 1595ed32cc..1c1da6f191 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -1,21 +1,26 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 public class HistogramEqualizationTests { + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); + [Theory] [InlineData(256)] [InlineData(65536)] public void HistogramEqualizationTest(int luminanceLevels) { // Arrange - byte[] pixels = new byte[] + var pixels = new byte[] { 52, 55, 61, 59, 70, 61, 76, 61, 62, 59, 55, 104, 94, 85, 59, 71, @@ -27,18 +32,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization 70, 87, 69, 68, 65, 73, 78, 90 }; - var image = new Image(8, 8); - for (int y = 0; y < 8; y++) + 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 = new byte[] - { + var 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, @@ -47,22 +53,94 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization 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(luminanceLevels)); + // 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.LowContrast, PixelTypes.Rgba32)] + public void Adaptive_SlidingWindow_15Tiles_WithClipping(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new HistogramEqualizationOptions + { + 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()) + { + var options = new HistogramEqualizationOptions + { + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, + LuminanceLevels = 256, + ClipHistogram = true, + NumberOfTiles = 10 + }; + 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. + [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()) + { + var options = new HistogramEqualizationOptions() + { + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, + LuminanceLevels = 256, + ClipHistogram = true, + ClipLimit = 5, + NumberOfTiles = 10 + }; + image.Mutate(x => x.HistogramEqualization(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } } } diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs index 899082e361..8bc0a2c97f 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -1,13 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Overlays @@ -18,21 +15,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Glow_GlowProcessorWithDefaultValues() { this.operations.Glow(); - var p = this.Verify>(); + GlowProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.GlowColor); + 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() { - this.operations.Glow(Rgba32.Aquamarine); - var p = this.Verify>(); + this.operations.Glow(Color.Aquamarine); + GlowProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Aquamarine, p.GlowColor); + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.Aquamarine, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } @@ -40,10 +37,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Glow_Radux_GlowProcessorWithDefaultValues() { this.operations.Glow(3.5f); - var p = this.Verify>(); + GlowProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.GlowColor); + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); } @@ -52,11 +49,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays { var rect = new Rectangle(12, 123, 43, 65); this.operations.Glow(rect); - var p = this.Verify>(rect); + GlowProcessor p = this.Verify(rect); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.GlowColor); + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.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 f47bffe26f..32e8ba3846 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs @@ -1,11 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Overlays @@ -16,10 +14,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Vignette_VignetteProcessorWithDefaultValues() { this.operations.Vignette(); - var p = this.Verify>(); + VignetteProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.VignetteColor); + 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); } @@ -27,11 +25,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays [Fact] public void Vignette_Color_VignetteProcessorWithDefaultValues() { - this.operations.Vignette(Rgba32.Aquamarine); - var p = this.Verify>(); + this.operations.Vignette(Color.Aquamarine); + VignetteProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Aquamarine, p.VignetteColor); + 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); } @@ -40,10 +38,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Vignette_Radux_VignetteProcessorWithDefaultValues() { this.operations.Vignette(3.5f, 12123f); - var p = this.Verify>(); + VignetteProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.VignetteColor); + 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); } @@ -53,12 +51,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays { var rect = new Rectangle(12, 123, 43, 65); this.operations.Vignette(rect); - var p = this.Verify>(rect); + VignetteProcessor p = this.Verify(rect); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.VignetteColor); + 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); } } -} \ 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 44fdfc7039..24e52d5d0e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -1,55 +1,55 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - public class BinaryDitherTests : FileTestBase + public class BinaryDitherTests { public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - public static readonly TheoryData OrderedDitherers = new TheoryData + public static readonly TheoryData OrderedDitherers = new TheoryData { - { "Bayer8x8", KnownDitherers.BayerDither8x8 }, - { "Bayer4x4", KnownDitherers.BayerDither4x4 }, - { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, - { "Bayer2x2", KnownDitherers.BayerDither2x2 } + { "Bayer8x8", KnownDitherings.Bayer8x8 }, + { "Bayer4x4", KnownDitherings.Bayer4x4 }, + { "Ordered3x3", KnownDitherings.Ordered3x3 }, + { "Bayer2x2", KnownDitherings.Bayer2x2 } }; - public static readonly TheoryData ErrorDiffusers = new TheoryData + 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 }, + { "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 IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; - private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + private static IDither DefaultErrorDiffuser => KnownDitherings.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 + [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()) { @@ -59,22 +59,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } [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 + [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()) { - image.Mutate(x => x.BinaryDiffuse(diffuser, .5F)); + image.Mutate(x => x.BinaryDither(diffuser)); image.DebugSave(provider, name); } } [Theory] - [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + [WithFile(TestImages.Png.Bike, TestPixelTypes)] public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -84,21 +84,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } [Theory] - [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + [WithFile(TestImages.Png.Bike, TestPixelTypes)] public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f)); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser)); image.DebugSave(provider); } } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image source = provider.GetImage()) using (Image image = source.Clone()) @@ -113,20 +113,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { 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.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds)); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser, bounds)); image.DebugSave(provider); 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 988c9125ba..c5b7808cc2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs @@ -1,29 +1,34 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { using SixLabors.ImageSharp.Processing; - public class BinaryThresholdTest : FileTestBase + public class BinaryThresholdTest { public static readonly TheoryData BinaryThresholdValues - = new TheoryData + = new TheoryData { .25F, .75F }; + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, TestImages.Png.Bike + }; + + public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; + [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(BinaryThresholdValues), DefaultPixelType)] + [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] public void ImageShouldApplyBinaryThresholdFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -33,21 +38,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(BinaryThresholdValues), DefaultPixelType)] + [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] public void ImageShouldApplyBinaryThresholdInBox(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image source = provider.GetImage()) using (var image = source.Clone()) { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); image.Mutate(x => x.BinaryThreshold(value, bounds)); - image.DebugSave(provider, value); + 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 new file mode 100644 index 0000000000..7e0676aab7 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// 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.Convolution +{ + [GroupOutput("Convolution")] + public abstract class Basic1ParameterConvolutionTests + { + 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 : 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 + { + 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); + + protected abstract void Apply(IImageProcessingContext ctx, int value, Rectangle bounds); + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs new file mode 100644 index 0000000000..490a6ea493 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -0,0 +1,203 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +{ + 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 ]]"; + + [Fact] + public void VerifyComplexComponents() + { + // Get the saved components + var components = new List(); + 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 (var image = new Image(1, 1)) + { + Configuration configuration = image.GetConfiguration(); + var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma); + using (var 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 TheoryData + { + 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 + { + static void RunTest(string providerDump, string infoDump) + { + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); + BokehBlurInfo value = BasicSerializer.Deserialize(infoDump); + + provider.RunValidatingProcessorTest( + x => x.BokehBlur(value.Radius, value.Components, value.Gamma), + testOutputDetails: value.ToString(), + appendPixelTypeToFileName: false); + } + + RemoteExecutor + .Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value)) + .Dispose(); + } + + [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 + { + static void RunTest(string providerDump) + { + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); + provider.RunValidatingProcessorTest( + x => x.BokehBlur(8, 2, 3), + appendSourceFileOrDescription: false); + } + + RemoteExecutor + .Invoke(RunTest, BasicSerializer.Serialize(provider)) + .Dispose(); + } + + [Theory] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] + public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string infoDump) + { + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); + BokehBlurInfo value = BasicSerializer.Deserialize(infoDump); + + provider.RunValidatingProcessorTest( + x => + { + Size size = x.GetCurrentSize(); + var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); + x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); + }, + testOutputDetails: value.ToString(), + appendPixelTypeToFileName: false); + } + + RemoteExecutor + .Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value)) + .Dispose(); + } + + [Theory] + [WithTestPatternImages(100, 300, PixelTypes.Bgr24)] + public void WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest(41, c => c.BokehBlur()); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index 0c40debad1..529a4b49c3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs @@ -1,51 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { - public class BoxBlurTest : FileTestBase + [GroupOutput("Convolution")] + public class BoxBlurTest : Basic1ParameterConvolutionTests { - public static readonly TheoryData BoxBlurValues - = new TheoryData - { - 3, - 5 - }; - - [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(BoxBlurValues), DefaultPixelType)] - public void ImageShouldApplyBoxBlurFilter(TestImageProvider provider, int value) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BoxBlur(value)); - image.DebugSave(provider, value); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(BoxBlurValues), DefaultPixelType)] - public void ImageShouldApplyBoxBlurFilterInBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel - { - using (Image source = provider.GetImage()) - using (var image = source.Clone()) - { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - - image.Mutate(x => x.BoxBlur(value, bounds)); - image.DebugSave(provider, value); + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.BoxBlur(value); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, 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/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index de72f6d09e..e468778de4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -1,39 +1,57 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics.CodeAnalysis; 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 +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - public class DetectEdgesTest : FileTestBase + [GroupOutput("Convolution")] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")] + public class DetectEdgesTest { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); + 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; - public static readonly string[] CommonTestImages = { TestImages.Png.Bike }; + public static readonly TheoryData DetectEdgesFilters + = new TheoryData + { + { KnownEdgeDetectorKernels.Laplacian3x3, nameof(KnownEdgeDetectorKernels.Laplacian3x3) }, + { KnownEdgeDetectorKernels.Laplacian5x5, nameof(KnownEdgeDetectorKernels.Laplacian5x5) }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, nameof(KnownEdgeDetectorKernels.LaplacianOfGaussian) }, + }; + + public static readonly TheoryData DetectEdges2DFilters + = 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 DetectEdgesFilters = new TheoryData + public static readonly TheoryData DetectEdgesCompassFilters + = new TheoryData { - EdgeDetectionOperators.Kayyali, - EdgeDetectionOperators.Kirsch, - EdgeDetectionOperators.Laplacian3x3, - EdgeDetectionOperators.Laplacian5x5, - EdgeDetectionOperators.LaplacianOfGaussian, - EdgeDetectionOperators.Prewitt, - EdgeDetectionOperators.RobertsCross, - EdgeDetectionOperators.Robinson, - EdgeDetectionOperators.Scharr, - EdgeDetectionOperators.Sobel + { KnownEdgeDetectorKernels.Kirsch, nameof(KnownEdgeDetectorKernels.Kirsch) }, + { KnownEdgeDetectorKernels.Robinson, nameof(KnownEdgeDetectorKernels.Robinson) }, }; [Theory] - [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTestOnWrappedMemoryImage( ctx => @@ -42,40 +60,91 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); ctx.DetectEdges(bounds); }, + comparer: OpaqueComparer, useReferenceOutputFrom: nameof(this.DetectEdges_InBox)); } [Theory] - [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, DefaultPixelType)] - [WithFileCollection(nameof(CommonTestImages), nameof(DetectEdgesFilters), DefaultPixelType)] - public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, EdgeDetectionOperators detector) - where TPixel : struct, IPixel + [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, detector.ToString()); - image.CompareToReferenceOutput(ValidatorComparer, provider, detector.ToString()); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); } } [Theory] - [WithFileCollection(nameof(CommonTestImages), CommonNonDefaultPixelTypes)] + [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()) + { + image.Mutate(x => x.DetectEdges(detector)); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); + } + } + + [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()) + { + image.Mutate(x => x.DetectEdges(detector)); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); + } + } + + [Theory] + [WithFileCollection(nameof(TestImages), CommonNonDefaultPixelTypes)] public void DetectEdges_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + 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(ValidatorComparer, provider); + image.CompareToReferenceOutput(comparer, provider); } } [Theory] - [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] + [WithFile(Tests.TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void DetectEdges_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -85,9 +154,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } [Theory] - [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] public void DetectEdges_InBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -95,8 +164,50 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution image.Mutate(x => x.DetectEdges(bounds)); image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); + image.CompareToReferenceOutput(OpaqueComparer, 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 6bd3b34bb1..31b3d20db0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -1,46 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { - public class GaussianBlurTest : FileTestBase + [GroupOutput("Convolution")] + public class GaussianBlurTest : Basic1ParameterConvolutionTests { - public static readonly TheoryData GaussianBlurValues = new TheoryData { 3, 5 }; - - [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(GaussianBlurValues), DefaultPixelType)] - public void ImageShouldApplyGaussianBlurFilter(TestImageProvider provider, int value) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.GaussianBlur(value)); - image.DebugSave(provider, value); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(GaussianBlurValues), DefaultPixelType)] - public void ImageShouldApplyGaussianBlurFilterInBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel - { - 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.GaussianBlur(value, bounds)); - image.DebugSave(provider, value); + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianBlur(value); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } - } + 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 8eb1f85eb2..7d3e918038 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs @@ -1,50 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { - public class GaussianSharpenTest : FileTestBase + [GroupOutput("Convolution")] + public class GaussianSharpenTest : Basic1ParameterConvolutionTests { - public static readonly TheoryData GaussianSharpenValues - = new TheoryData - { - 3, - 5 - }; + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianSharpen(value); - [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(GaussianSharpenValues), DefaultPixelType)] - public void ImageShouldApplyGaussianSharpenFilter(TestImageProvider provider, int value) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.GaussianSharpen(value)); - image.DebugSave(provider, value); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(GaussianSharpenValues), DefaultPixelType)] - public void ImageShouldApplyGaussianSharpenFilterInBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel - { - using (Image source = provider.GetImage()) - using (var image = source.Clone()) - { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - - image.Mutate(x => x.GaussianSharpen(value, bounds)); - image.DebugSave(provider, value); - - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } - } + 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/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 9774cb50cf..adc3c381a0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -1,130 +1,177 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - public class DitherTests : FileTestBase + public class DitherTests { - public static readonly string[] CommonTestImages = + 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 { - TestImages.Png.CalliphoraPartial, TestImages.Png.Bike + { 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 TheoryData - { - { "Bayer8x8", KnownDitherers.BayerDither8x8 }, - { "Bayer4x4", KnownDitherers.BayerDither4x4 }, - { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, - { "Bayer2x2", KnownDitherers.BayerDither2x2 } - }; + public static readonly TheoryData OrderedDitherers + = new TheoryData + { + { KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) }, + { KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) }, + { KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) }, + { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } + }; - 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 readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; - private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + 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 && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion); [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), DefaultPixelType)] - [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, DefaultPixelType)] - public void DitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IOrderedDither ditherer) - where TPixel : struct, IPixel + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDiffusionFilterInBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + if (SkipAllDitherTests) { - image.Mutate(x => x.Dither(ditherer)); - image.DebugSave(provider, name); + return; } + + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Dither(DefaultErrorDiffuser, rect), + comparer: ValidatorComparer); } [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 + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDitherFilterInBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + if (SkipAllDitherTests) { - image.Mutate(x => x.Diffuse(diffuser, .5F)); - image.DebugSave(provider, name); + return; } + + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Dither(DefaultDitherer, rect), + comparer: ValidatorComparer); } [Theory] [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] - public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + if (SkipAllDitherTests) { - image.Mutate(x => x.Dither(DefaultDitherer)); - image.DebugSave(provider); + return; } + + // Increased tolerance because of compatibility issues on .NET 4.6.2: + var comparer = ImageComparer.TolerantPercentage(1f); + provider.RunValidatingProcessorTest(x => x.Dither(DefaultErrorDiffuser), comparer: comparer); } [Theory] - [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] - public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] + public void DiffusionFilter_WorksWithAllErrorDiffusers( + TestImageProvider provider, + IDither diffuser, + string name) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + if (SkipAllDitherTests) { - image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, 0.5f)); - image.DebugSave(provider); + return; } + + provider.RunValidatingProcessorTest( + x => x.Dither(diffuser), + testOutputDetails: name, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] - public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] + public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) + if (SkipAllDitherTests) { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - - image.Mutate(x => x.Dither(DefaultDitherer, bounds)); - image.DebugSave(provider); - - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + return; } + + provider.RunValidatingProcessorTest( + x => x.Dither(DefaultDitherer), + comparer: ValidatorComparer); } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] - public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] + public void DitherFilter_WorksWithAllDitherers( + TestImageProvider provider, + IDither ditherer, + string name) + where TPixel : unmanaged, IPixel { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) + if (SkipAllDitherTests) { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + return; + } - image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, .5F, bounds)); - image.DebugSave(provider); + provider.RunValidatingProcessorTest( + x => x.Dither(ditherer), + testOutputDetails: name, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); + } - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + [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) + { + return; } + + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.Dither(dither), + name, + ImageComparer.TolerantPercentage(0.001f)); } } -} \ 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 792c7b0802..b29e452216 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs @@ -1,44 +1,37 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Effects { - public class BackgroundColorTest : FileTestBase + [GroupOutput("Effects")] + public class BackgroundColorTest { + public static readonly string[] InputImages = + { + TestImages.Png.Splash, + TestImages.Png.Ducky + }; + [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyBackgroundColorFilter(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BackgroundColor(NamedColors.HotPink)); - image.DebugSave(provider); - } + provider.RunValidatingProcessorTest(x => x.BackgroundColor(Color.HotPink)); } [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyBackgroundColorFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image source = provider.GetImage()) - using (var image = source.Clone()) - { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - - image.Mutate(x => x.BackgroundColor(NamedColors.HotPink, bounds)); - image.DebugSave(provider); - - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.BackgroundColor(Color.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 d4429aaf3d..0d68a860d3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -1,49 +1,53 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Effects { - public class OilPaintTest : FileTestBase + [GroupOutput("Effects")] + public class OilPaintTest { public static readonly TheoryData OilPaintValues = new TheoryData { - { 15, 10 }, { 6, 5 } + { 15, 10 }, + { 6, 5 } }; + public static readonly string[] InputImages = + { + TestImages.Png.CalliphoraPartial, + TestImages.Bmp.Car + }; + [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(OilPaintValues), DefaultPixelType)] - public void ApplyOilPaintFilter(TestImageProvider provider, int levels, int brushSize) - where TPixel : struct, IPixel + [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider, int levels, int brushSize) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.OilPaint(levels, brushSize)); - image.DebugSave(provider, string.Join("-", levels, brushSize)); - } + provider.RunValidatingProcessorTest( + x => + { + x.OilPaint(levels, brushSize); + return $"{levels}-{brushSize}"; + }, + ImageComparer.TolerantPercentage(0.01F), + appendPixelTypeToFileName: false); } [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(OilPaintValues), DefaultPixelType)] - public void ApplyOilPaintFilterInBox(TestImageProvider provider, int levels, int brushSize) - where TPixel : struct, IPixel + [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 { - 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.OilPaint(levels, brushSize, bounds)); - image.DebugSave(provider, string.Join("-", levels, brushSize)); - - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.OilPaint(levels, brushSize, rect), + $"{levels}-{brushSize}", + ImageComparer.TolerantPercentage(0.01F)); } } -} \ 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 new file mode 100644 index 0000000000..919cb31379 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +{ + [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; + var gray = new Vector4(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; + var gray = new Vector4(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 cb9a0ba0cf..2173cbef82 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs @@ -1,85 +1,33 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Effects { - public class PixelateTest : FileTestBase + [GroupOutput("Effects")] + public class PixelateTest { - public static readonly TheoryData PixelateValues - = new TheoryData - { - 4 , - 8 - }; + public static readonly TheoryData PixelateValues = new TheoryData { 4, 8 }; [Theory] - [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] - public void ImageShouldApplyPixelateFilter(TestImageProvider provider, int value) - where TPixel : struct, IPixel + [WithFile(TestImages.Png.Ducky, nameof(PixelateValues), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider, int value) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Pixelate(value)); - image.DebugSave(provider, value); - - // Test the neigbouring pixels - for (int y = 0; y < image.Height; y += value) - { - for (int x = 0; x < image.Width; x += value) - { - TPixel source = image[x, y]; - for (int pixY = y; pixY < y + value && pixY < image.Height; pixY++) - { - for (int pixX = x; pixX < x + value && pixX < image.Width; pixX++) - { - Assert.Equal(source, image[pixX, pixY]); - } - } - } - } - } + provider.RunValidatingProcessorTest(x => x.Pixelate(value), value, appendPixelTypeToFileName: false); } [Theory] [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] - public void ImageShouldApplyPixelateFilterInBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel + [WithFile(TestImages.Png.CalliphoraPartial, nameof(PixelateValues), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int value) + where TPixel : unmanaged, IPixel { - using (Image source = provider.GetImage()) - using (var image = source.Clone()) - { - var bounds = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - - image.Mutate(x => x.Pixelate(value, bounds)); - image.DebugSave(provider, value); - - for (int y = 0; y < image.Height; y++) - { - for (int x = 0; x < image.Width; x++) - { - int tx = x; - int ty = y; - TPixel sourceColor = source[tx, ty]; - if (bounds.Contains(tx, ty)) - { - int sourceX = tx - ((tx - bounds.Left) % value) + (value / 2); - int sourceY = ty - ((ty - bounds.Top) % value) + (value / 2); - - sourceColor = image[sourceX, sourceY]; - } - Assert.Equal(sourceColor, image[tx, ty]); - } - } - - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } + 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 64aeae0534..fdcc3c6f79 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyBlackWhiteFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.BlackWhite(), comparer: ImageComparer.TolerantPercentage(0.002f)); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs index 54a8dd4b7d..d7e5b13cce 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects = new TheoryData { .5F, - 1.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); + where TPixel : unmanaged, 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 8ac56655ea..a007f71940 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -31,6 +31,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [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); + where TPixel : unmanaged, 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 e5e4fa4a90..25fe9c84c2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects = new TheoryData { .5F, - 1.5F + 1.5F }; [Theory] [WithTestPatternImages(nameof(ContrastValues), 48, 48, PixelTypes.Rgba32)] public void ApplyContrastFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, 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 68daa80eac..535179cb16 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,7 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Primitives; + using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; [GroupOutput("Filters")] @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void ApplyFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ColorMatrix m = CreateCombinedTestFilterMatrix(); @@ -30,13 +30,23 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ColorMatrix m = CreateCombinedTestFilterMatrix(); provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer); } + [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); @@ -44,6 +54,5 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters 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 f08ec147ec..279b699ee3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs @@ -1,9 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using Xunit; @@ -24,12 +22,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters /// /// 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 : struct, IPixel + where TPixel : unmanaged, 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 4ce700bad0..3538f0dba9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters = new TheoryData { 180, - -180 + -180 }; [Theory] [WithTestPatternImages(nameof(HueValues), 48, 48, PixelTypes.Rgba32)] public void ApplyHueFilter(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, 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 1b4c70646a..a2e0b0b4b2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyInvertFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Invert()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs index b7b635c2d2..f21d458365 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyKodachromeFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Kodachrome()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs new file mode 100644 index 0000000000..c924ddc4f2 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +{ + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + [GroupOutput("Filters")] + public class LightnessTest + { + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); + + public static readonly TheoryData LightnessValues + = new TheoryData + { + .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 013ec38740..a7ef2f8625 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyLomographFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Lomograph()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs index 35e405f4c9..64025a6fba 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,16 +15,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects public static readonly TheoryData AlphaValues = new TheoryData { - 20/100F, - 80/100F + 20 / 100F, + 80 / 100F }; [Theory] [WithTestPatternImages(nameof(AlphaValues), 48, 48, PixelTypes.Rgba32)] public void ApplyAlphaFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, 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 3b39542a55..8be43efa92 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyPolaroidFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Polaroid()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs index 31fab8b65d..91c6e4af82 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters = new TheoryData { .5F, - 1.5F, + 1.5F, }; [Theory] [WithTestPatternImages(nameof(SaturationValues), 48, 48, PixelTypes.Rgba32)] public void ApplySaturationFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, 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 b7d381f5f2..af2c2136a9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplySepiaFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Sepia()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs index 479ee346a5..f0d6b784b1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs @@ -1,68 +1,18 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Overlays { - public class GlowTest : FileTestBase + [GroupOutput("Overlays")] + public class GlowTest : OverlayTestBase { - [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyGlowFilter(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Glow()); - image.DebugSave(provider); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyGlowFilterColor(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Glow(NamedColors.Orange)); - image.DebugSave(provider); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyGlowFilterRadius(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Glow(image.Width / 4F)); - image.DebugSave(provider); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyGlowFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image source = provider.GetImage()) - using (var image = source.Clone()) - { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Glow(color); - image.Mutate(x => x.Glow(bounds)); - image.DebugSave(provider); + protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => + ctx.Glow(radiusX); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } - } + 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 new file mode 100644 index 0000000000..fa4d422b1d --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// 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.Overlays +{ + [GroupOutput("Overlays")] + public abstract class OverlayTestBase + { + 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 + { + provider.Utility.TestGroupName = this.GetType().Name; + Color color = TestUtils.GetColorByName(colorName); + + provider.RunValidatingProcessorTest(x => this.Apply(x, color), colorName, ValidatorComparer, appendPixelTypeToFileName: false); + } + + [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); + } + + [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)); + } + + protected abstract void Apply(IImageProcessingContext ctx, Color color); + + protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY); + + protected abstract void Apply(IImageProcessingContext ctx, Rectangle rect); + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs index 3a378a0951..6eccde4bc5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs @@ -1,68 +1,18 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.Overlays { - public class VignetteTest : FileTestBase + [GroupOutput("Overlays")] + public class VignetteTest : OverlayTestBase { - [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyVignetteFilter(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Vignette()); - image.DebugSave(provider); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyVignetteFilterColor(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Vignette(NamedColors.Orange)); - image.DebugSave(provider); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyVignetteFilterRadius(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Vignette(image.Width / 4F, image.Height / 4F)); - image.DebugSave(provider); - } - } - - [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldApplyVignetteFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image source = provider.GetImage()) - using (var image = source.Clone()) - { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Vignette(color); - image.Mutate(x => x.Vignette(bounds)); - image.DebugSave(provider); + protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => + ctx.Vignette(radiusX, radiusY); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } - } + 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 b3900325db..2b4460429c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -13,46 +13,51 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [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); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new OctreeQuantizer(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() { var quantizer = new OctreeQuantizer(); - IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(false); - frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); - frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index a4e6edd53e..0df498cd1c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -11,69 +10,70 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { public class PaletteQuantizerTests { - private static readonly Rgba32[] Rgb = new Rgba32[] { Rgba32.Red, Rgba32.Green, Rgba32.Blue }; + private static readonly Color[] Palette = new Color[] { Color.Red, Color.Green, Color.Blue }; [Fact] public void PaletteQuantizerConstructor() { - var quantizer = new PaletteQuantizer(Rgb); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - quantizer = new PaletteQuantizer(Rgb, false); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Null(quantizer.Diffuser); + expected = new QuantizerOptions { Dither = null }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDiffusers.Atkinson, 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); + + 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); } [Fact] public void PaletteQuantizerCanCreateFrameQuantizer() { - var quantizer = new PaletteQuantizer(Rgb); - IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + var quantizer = new PaletteQuantizer(Palette); + IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, false); - frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); - frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); - } - - [Fact] - public void PaletteQuantizerThrowsOnInvalidGenericMethodCall() - { - var quantizer = new PaletteQuantizer(Rgb); - - Assert.Throws(() => ((IQuantizer)quantizer).CreateFrameQuantizer(Configuration.Default)); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } [Fact] public void KnownQuantizersWebSafeTests() { IQuantizer quantizer = KnownQuantizers.WebSafe; - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } [Fact] public void KnownQuantizersWernerTests() { IQuantizer quantizer = KnownQuantizers.Werner; - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs new file mode 100644 index 0000000000..a25eca5b02 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -0,0 +1,220 @@ +// Copyright (c) Six Labors. +// 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 SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization +{ + public class QuantizerTests + { + /// + /// Something is causing tests to fail on NETFX in CI. + /// Could be a JIT error as everything runs well and is identical to .NET Core output. + /// Not worth investigating for now. + /// + /// + private static readonly bool SkipAllQuantizerTests = TestEnvironment.IsFramework; + + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, + TestImages.Png.Bike + }; + + private static readonly QuantizerOptions NoDitherOptions = new QuantizerOptions { Dither = null }; + private static readonly QuantizerOptions DiffuserDitherOptions = new QuantizerOptions { Dither = KnownDitherings.FloydSteinberg }; + private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.Bayer8x8 }; + + private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = 0F + }; + + private static readonly QuantizerOptions Diffuser0_25_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .25F + }; + + private static readonly QuantizerOptions Diffuser0_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .5F + }; + + private static readonly QuantizerOptions Diffuser0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .75F + }; + + private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = 0F + }; + + private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .25F + }; + + private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .5F + }; + + private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .75F + }; + + public static readonly TheoryData Quantizers + = new TheoryData + { + // 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 TheoryData + { + 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), + }; + + 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 + { + if (SkipAllQuantizerTests) + { + return; + } + + 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), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] + public void ApplyQuantization(TestImageProvider provider, IQuantizer quantizer) + where TPixel : unmanaged, IPixel + { + if (SkipAllQuantizerTests) + { + return; + } + + 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 + { + if (SkipAllQuantizerTests) + { + return; + } + + 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); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index 625043c7f1..8881aa9ad5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -13,46 +13,51 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [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); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new WuQuantizer(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() { var quantizer = new WuQuantizer(); - IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new WuQuantizer(false); - frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new WuQuantizer(KnownDiffusers.Atkinson); - frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs new file mode 100644 index 0000000000..379f74d094 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -0,0 +1,265 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 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. + /// + /// 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 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 + /// + /// The pixel type of the image. + [Theory] + [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] + public void Transform_FromSourceRectangle1(TestImageProvider provider) + where TPixel : unmanaged, 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 : unmanaged, 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 : 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)); + } + + 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 : unmanaged, IPixel + { + Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span data)); + 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); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index 9b37fb266a..44f88c3a21 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -1,83 +1,93 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; +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; + using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public class AutoOrientTests : FileTestBase + [GroupOutput("Transforms")] + public class AutoOrientTests { - public static readonly string[] FlipFiles = { TestImages.Bmp.F }; - - public static readonly TheoryData OrientationValues - = new TheoryData - { - { RotateMode.None, FlipMode.None, 0 }, - { RotateMode.None, FlipMode.None, 1 }, - { RotateMode.None, FlipMode.Horizontal, 2 }, - { RotateMode.Rotate180, FlipMode.None, 3 }, - { RotateMode.Rotate180, FlipMode.Horizontal, 4 }, - { RotateMode.Rotate90, FlipMode.Horizontal, 5 }, - { RotateMode.Rotate270, FlipMode.None, 6 }, - { RotateMode.Rotate90, FlipMode.Vertical, 7 }, - { RotateMode.Rotate90, FlipMode.None, 8 }, - }; + public const string FlipTestFile = TestImages.Bmp.F; public static readonly TheoryData InvalidOrientationValues = new TheoryData { { 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) } + { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, + { ExifDataType.Long, BitConverter.GetBytes(4U) }, + { ExifDataType.SignedLong, BitConverter.GetBytes(5) } }; + public static readonly TheoryData ExifOrientationValues = new TheoryData + { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + }; + [Theory] - [WithFileCollection(nameof(FlipFiles), nameof(OrientationValues), DefaultPixelType)] - public void ImageShouldAutoRotate(TestImageProvider provider, RotateMode rotateType, FlipMode flipType, ushort orientation) - where TPixel : struct, IPixel + [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.RotateFlip(rotateType, flipType)); - image.DebugSave(provider, string.Join("_", rotateType, flipType, orientation, "1_before")); + image.Metadata.ExifProfile = new ExifProfile(); + image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation); image.Mutate(x => x.AutoOrient()); - image.DebugSave(provider, string.Join("_", rotateType, flipType, orientation, "2_after")); + image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); } } [Theory] - [WithFileCollection(nameof(FlipFiles), nameof(InvalidOrientationValues), DefaultPixelType)] - public void ImageShouldAutoRotateInvalidValues(TestImageProvider provider, ExifDataType dataType, byte[] orientation) - where TPixel : struct, IPixel + [WithFile(FlipTestFile, nameof(InvalidOrientationValues), PixelTypes.Rgba32)] + public void AutoOrient_WorksWithCorruptExifData(TestImageProvider provider, ExifDataType dataType, byte[] orientation) + where TPixel : unmanaged, IPixel { var profile = new ExifProfile(); 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; + // Change the number of components bytes[20] = 1; + var orientationCodeData = new byte[8]; + Array.Copy(orientation, orientationCodeData, orientation.Length); + + ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); + using (Image image = provider.GetImage()) + using (Image reference = image.Clone()) { - image.MetaData.ExifProfile = new ExifProfile(bytes); + 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 c01c3b1bd3..78c35fa9b3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,20 +6,19 @@ 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.Processing.Processors.Transforms { [GroupOutput("Transforms")] - public class CropTest : FileTestBase + public class CropTest { [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 + where TPixel : unmanaged, IPixel { var rect = new Rectangle(x, y, w, h); FormattableString info = $"X{x}Y{y}.W{w}H{h}"; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs index 7285270217..16668fb207 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,25 +7,43 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public class EntropyCropTest : FileTestBase + [GroupOutput("Transforms")] + public class EntropyCropTest { - public static readonly TheoryData EntropyCropValues - = new TheoryData + 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 : unmanaged, IPixel { - .25F, - .75F - }; + provider.RunValidatingProcessorTest(x => x.EntropyCrop(value), value, appendPixelTypeToFileName: false); + } [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(EntropyCropValues), DefaultPixelType)] - public void ImageShouldEntropyCrop(TestImageProvider provider, float value) - where TPixel : struct, IPixel + [WithBlankImages(40, 30, PixelTypes.Rgba32)] + [WithBlankImages(30, 40, PixelTypes.Rgba32)] + public void Entropy_WillNotCropWhiteImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.EntropyCrop(value)); - image.DebugSave(provider, value); - } + // arrange + using Image image = provider.GetImage(); + var expectedHeight = image.Height; + var expectedWidth = image.Width; + + // act + image.Mutate(img => img.EntropyCrop()); + + // assert + Assert.Equal(image.Width, expectedWidth); + Assert.Equal(image.Height, expectedHeight); } } -} \ 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 3c932bfaa6..c094febc95 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.Processing; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { [GroupOutput("Transforms")] @@ -26,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [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 + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest( ctx => ctx.Flip(flipMode), @@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [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 + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTestOnWrappedMemoryImage( ctx => ctx.Flip(flipMode), @@ -47,4 +46,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms 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 6cce62d14e..2ea8336401 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,12 +7,17 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public class PadTest : FileTestBase + public class PadTest { + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, TestImages.Png.Bike + }; + [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void ImageShouldPad(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -24,10 +29,33 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { for (int x = 0; x < 25; x++) { - Assert.Equal(default(TPixel), image[x, y]); + Assert.Equal(default, image[x, y]); + } + } + } + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] + public void ImageShouldPadWithBackgroundColor(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var 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); + + // Check pixels are filled + for (int y = 0; y < 25; y++) + { + for (int x = 0; x < 25; x++) + { + Assert.Equal(expected, image[x, y]); } } } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs new file mode 100644 index 0000000000..4691fc82b6 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +{ + 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); + } + + [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); + } + + [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); + } + + [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); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs new file mode 100644 index 0000000000..ceee3e7e02 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs @@ -0,0 +1,158 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +{ + public class ResizeHelperTests + { + [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); + + (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() + { + var sourceSize = new Size(5072, 6761); + var target = new Size(0, 450); + + var expectedSize = new Size(338, 450); + var expectedRectangle = new Rectangle(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() + { + var sourceSize = new Size(100, 100); + var target = new Size(25, 50); + + var expectedSize = new Size(25, 50); + var expectedRectangle = new Rectangle(-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() + { + var sourceSize = new Size(100, 100); + var target = new Size(120, 110); + + var expectedSize = new Size(120, 110); + var expectedRectangle = new Rectangle(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() + { + var sourceSize = new Size(100, 100); + var target = new Size(120, 110); + + var expectedSize = new Size(120, 110); + var expectedRectangle = new Rectangle(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() + { + var sourceSize = new Size(100, 100); + var target = new Size(57, 32); + + var expectedSize = new Size(57, 32); + var expectedRectangle = new Rectangle(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); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index 7d842c4e1e..da567f18c5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -26,7 +26,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; - public static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize, bool normalize = true) + 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; @@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms TolerantMath tolerantMath = TolerantMath.Default; double radius = tolerantMath.Ceiling(scale * sampler.Radius); - + var result = new List(); for (int i = 0; i < destinationSize; i++) @@ -104,8 +105,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public static implicit operator ReferenceKernel(ResizeKernel orig) { - return new ReferenceKernel(orig.Left, orig.Values.ToArray()); + 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 5d3790f071..991bca80ec 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -1,10 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; @@ -27,88 +25,81 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms /// /// resamplerName, srcSize, destSize /// - public static readonly TheoryData KernelMapData = new TheoryData + 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), 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 }, + { 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: - { nameof(KnownResamplers.Box), 378, 149 }, - { nameof(KnownResamplers.Box), 349, 174 }, + { KnownResamplers.Box, 378, 149 }, + { 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 }, + { 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 - { 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 }, + { KnownResamplers.Bicubic, 10, 50 }, + { KnownResamplers.Bicubic, 49, 301 }, + { KnownResamplers.Bicubic, 301, 49 }, + { KnownResamplers.Bicubic, 1680, 1200 }, + { KnownResamplers.Box, 13, 299 }, + { 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) + public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - IResampler resampler = TestUtils.GetResampler(resamplerName); - - var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false); + var kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); } [Theory] [MemberData(nameof(KernelMapData))] - public void KernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); + this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); } // Comprehensive but expensive tests, for ResizeKernelMap. @@ -123,14 +114,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } #endif - private void VerifyKernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - IResampler resampler = TestUtils.GetResampler(resamplerName); - - var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); - var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); + var referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize); + var kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); #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 @@ -146,15 +137,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms referenceKernel.Length == kernel.Length, $"referenceKernel.Length != kernel.Length: {referenceKernel.Length} != {kernel.Length}"); Assert.True( - referenceKernel.Left == kernel.Left, - $"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.Left}"); + 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); - - for (int x = 0; x < expectedValues.Length; x++) { Assert.True( @@ -164,11 +153,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - private static string PrintKernelMap(ResizeKernelMap kernelMap) => - PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ResizeKernelMap kernelMap) + => PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); - private static string PrintKernelMap(ReferenceKernelMap kernelMap) => - PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ReferenceKernelMap kernelMap) + => PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); private static string PrintKernelMap( TKernelMap kernelMap, @@ -203,7 +192,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms return bld.ToString(); } - private static TheoryData GenerateImageResizeData() { var result = new TheoryData(); @@ -235,4 +223,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms 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 034b66ae9a..51b8ee0264 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -1,28 +1,32 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; - +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; 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.ImageComparison; -using SixLabors.Primitives; - using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public class ResizeTests : FileTestBase + public class ResizeTests { - public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; + private const PixelTypes CommonNonDefaultPixelTypes = + PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); + private const PixelTypes DefaultPixelType = PixelTypes.Rgba32; public static readonly string[] AllResamplerNames = TestUtils.GetAllResamplerNames(); + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; + public static readonly string[] SmokeTestResamplerNames = { nameof(KnownResamplers.NearestNeighbor), @@ -31,97 +35,161 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms nameof(KnownResamplers.Lanczos5), }; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); + + [Fact] + public void Resize_PixelAgnostic() + { + string filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); + + using (var image = Image.Load(filePath)) + { + image.Mutate(x => x.Resize(image.Size() / 2)); + string path = System.IO.Path.Combine( + TestEnvironment.CreateOutputDirectory(nameof(ResizeTests)), + nameof(this.Resize_PixelAgnostic) + ".png"); + + image.Save(path); + } + } + + [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; + } + + provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024; + + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(destSize, destSize)); + image.DebugSave(provider, appendPixelTypeToFileName: false); + } + } + [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( + [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()) + { + 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); + } + } + + 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, - string samplerName, - float? ratio, - int? specificDestWidth, - int? specificDestHeight) - where TPixel : struct, IPixel + int workingBufferLimitInRows) + where TPixel : unmanaged, IPixel { - IResampler sampler = TestUtils.GetResampler(samplerName); + using (Image image0 = provider.GetImage()) + { + Size destSize = image0.Size() / 4; - // 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 configuration = Configuration.CreateDefaultInstance(); - var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); + int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; + var allocator = new TestMemoryAllocator(); + configuration.MemoryAllocator = allocator; + configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; - 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}"; - } + var verticalKernelMap = ResizeKernelMap.Calculate( + default, + destSize.Height, + image0.Height, + Configuration.Default.MemoryAllocator); + int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4; + verticalKernelMap.Dispose(); - 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); + 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.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)); + } + } } [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 + [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 { - provider.Configuration.MaxDegreeOfParallelism = - maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; + using Image expected = provider.GetImage(); + int width = expected.Width; + Size destSize = expected.Size() / 4; + expected.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); - FormattableString details = $"MDP{maxDegreeOfParallelism}"; + // Replace configuration: + provider.Configuration = Configuration.CreateDefaultInstance(); - provider.RunValidatingProcessorTest( - x => x.Resize(x.GetCurrentSize() / 2), - details, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); + // 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, DefaultPixelType)] public void Resize_Compand(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -132,10 +200,39 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, 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.Gif.Giphy, DefaultPixelType)] + 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 : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer); } @@ -143,63 +240,142 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image0 = provider.GetImage()) { - var mmg = TestMemoryManager.CreateAsCopyOf(image0.GetPixelSpan()); + Assert.True(image0.TryGetSinglePixelSpan(out Span imageSpan)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageSpan); 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)); - }); + () => { image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true)); }); } } } [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 + [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 : unmanaged, IPixel { - string details = compand ? "Compand" : ""; + provider.Configuration.MaxDegreeOfParallelism = + maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; + + FormattableString details = $"MDP{maxDegreeOfParallelism}"; provider.RunValidatingProcessorTest( - x => x.Resize(x.GetCurrentSize() / 2, compand), + x => x.Resize(x.GetCurrentSize() / 2), details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); } - + [Theory] - [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] - public void Resize_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + [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 : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.Bicubic)); + IResampler sampler = TestUtils.GetResampler(samplerName); - // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :( - image.DebugSave(provider, extension: Extensions.Gif); - } + // 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 + && 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.Resize((Size)newSize, sampler, false); + return testOutputDetails; + }, + comparer, + appendPixelTypeToFileName: false); } [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeFromSourceRectangle(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - var sourceRectangle = new Rectangle(image.Width / 8, image.Height / 8, image.Width / 4, image.Height / 4); + 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.Mutate( + x => x.Resize( + image.Width, + image.Height, + KnownResamplers.Bicubic, + sourceRectangle, + destRectangle, + false)); image.DebugSave(provider); image.CompareToReferenceOutput(ValidatorComparer, provider); @@ -208,26 +384,39 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWidthAndKeepAspect(TestImageProvider provider) - where TPixel : struct, IPixel + public void ResizeHeightAndKeepAspect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Resize(image.Width / 3, 0, false)); + image.Mutate(x => x.Resize(0, image.Height / 3, false)); image.DebugSave(provider); image.CompareToReferenceOutput(ValidatorComparer, provider); } } + [Theory] + [WithTestPatternImages(10, 100, DefaultPixelType)] + 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); + } + } + [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeHeightAndKeepAspect(TestImageProvider provider) - where TPixel : struct, IPixel + public void ResizeWidthAndKeepAspect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Resize(0, image.Height / 3, false)); + image.Mutate(x => x.Resize(image.Width / 3, 0, false)); image.DebugSave(provider); image.CompareToReferenceOutput(ValidatorComparer, provider); @@ -237,7 +426,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(100, 10, DefaultPixelType)] public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -247,29 +436,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - [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] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWithCropWidthMode(TestImageProvider provider) - where TPixel : struct, IPixel + public void ResizeWithBoxPadMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions { - Size = new Size(image.Width / 2, image.Height) + Size = new Size(image.Width + 200, image.Height + 200), + Mode = ResizeMode.BoxPad }; image.Mutate(x => x.Resize(options)); @@ -282,14 +459,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithCropHeightMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - var options = new ResizeOptions - { - Size = new Size(image.Width, image.Height / 2) - }; + var options = new ResizeOptions { Size = new Size(image.Width, image.Height / 2) }; image.Mutate(x => x.Resize(options)); @@ -300,16 +474,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWithPadMode(TestImageProvider provider) - where TPixel : struct, IPixel + public void ResizeWithCropWidthMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - var options = new ResizeOptions - { - Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad - }; + var options = new ResizeOptions { Size = new Size(image.Width / 2, image.Height) }; image.Mutate(x => x.Resize(options)); @@ -319,16 +489,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } [Theory] - [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWithBoxPadMode(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(TestImages.Jpeg.Issues.IncorrectResize1006, DefaultPixelType)] + public void CanResizeLargeImageWithCropMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions { - Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad + Size = new Size(480, 600), + Mode = ResizeMode.Crop }; image.Mutate(x => x.Resize(options)); @@ -341,15 +511,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithMaxMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - var options = new ResizeOptions - { - Size = new Size(300, 300), - Mode = ResizeMode.Max - }; + var options = new ResizeOptions { Size = new Size(300, 300), Mode = ResizeMode.Max }; image.Mutate(x => x.Resize(options)); @@ -361,13 +527,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithMinMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions { - Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)), + Size = new Size( + (int)Math.Round(image.Width * .75F), + (int)Math.Round(image.Height * .95F)), Mode = ResizeMode.Min }; @@ -380,15 +548,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWithStretchMode(TestImageProvider provider) - where TPixel : struct, IPixel + public void ResizeWithPadMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions { - Size = new Size(image.Width / 2, image.Height), - Mode = ResizeMode.Stretch + Size = new Size(image.Width + 200, image.Height), + Mode = ResizeMode.Pad }; image.Mutate(x => x.Resize(options)); @@ -399,59 +567,59 @@ 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) + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWithStretchMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - IResampler sampler = KnownResamplers.Bicubic; - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); - } + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Size = new Size(image.Width / 2, image.Height), + Mode = ResizeMode.Stretch + }; - [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); + image.Mutate(x => x.Resize(options)); - Assert.Equal(result, expected); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } } [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) + [WithFile(TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, DefaultPixelType)] + [WithFile(TestImages.Jpeg.Issues.ExifGetString750Transform, DefaultPixelType)] + [WithFile(TestImages.Jpeg.Issues.ExifResize1049, DefaultPixelType)] + public void CanResizeExifIssueImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - IResampler sampler = KnownResamplers.Lanczos3; - float result = sampler.GetValue(x); + // Test images are large so skip on 32bit for now. + if (!TestEnvironment.Is64BitProcess) + { + return; + } - Assert.Equal(result, expected); + 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)); + } } - [Theory] - [InlineData(-4, 0)] - [InlineData(-2, 0)] - [InlineData(0, 1)] - [InlineData(2, 0)] - [InlineData(4, 0)] - public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) + [Fact] + public void Issue1195() { - IResampler sampler = KnownResamplers.Lanczos5; - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); + using (var image = new Image(2, 300)) + { + var size = new Size(50, 50); + image.Mutate(x => x + .Resize( + new ResizeOptions + { + Size = size, + Mode = ResizeMode.Max + })); + } } } -} \ 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 d6376b1792..398039e433 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs @@ -1,15 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { using SixLabors.ImageSharp.Processing; - public class RotateFlipTests : FileTestBase + public class RotateFlipTests { public static readonly string[] FlipFiles = { TestImages.Bmp.F }; @@ -24,10 +24,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms }; [Theory] - [WithTestPatternImages(nameof(RotateFlipValues), 100, 50, DefaultPixelType)] - [WithTestPatternImages(nameof(RotateFlipValues), 50, 100, DefaultPixelType)] + [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 : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index 7801c71432..1e888a51a1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [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 + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [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 + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs deleted file mode 100644 index 29c51543fc..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.Reflection; - -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms -{ - public class SkewTest : FileTestBase - { - public static readonly TheoryData SkewValues - = new TheoryData - { - { 20, 10 }, - { -20, -10 } - }; - - public static readonly List ResamplerNames - = new List - { - 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), - }; - - [Theory] - [WithTestPatternImages(nameof(SkewValues), 100, 50, DefaultPixelType)] - public void ImageShouldSkew(TestImageProvider provider, float x, float y) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(i => i.Skew(x, y)); - image.DebugSave(provider, string.Join("_", x, y)); - } - } - - [Theory] - [WithTestPatternImages(nameof(SkewValues), 100, 50, DefaultPixelType)] - public void ImageShouldSkewWithSampler(TestImageProvider provider, float x, float y) - where TPixel : struct, IPixel - { - foreach (string resamplerName in ResamplerNames) - { - IResampler sampler = TestUtils.GetResampler(resamplerName); - using (Image image = provider.GetImage()) - { - image.Mutate(i => i.Skew(x, y, sampler)); - image.DebugSave(provider, string.Join("_", x, y, resamplerName)); - } - } - } - } -} \ 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 new file mode 100644 index 0000000000..2fd87de29e --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// 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; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +{ + [GroupOutput("Transforms")] + public class SkewTests + { + private const PixelTypes CommonPixelTypes = PixelTypes.Bgra32 | PixelTypes.Rgb24; + + 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 TheoryData + { + { 20, 10 }, + { -20, -10 } + }; + + 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] + [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); + + 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/Transforms/AffineTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs index 70159e18ac..1d28df8e22 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs @@ -1,15 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { - protected override AffineTransformBuilder CreateBuilder(Rectangle rectangle) => new AffineTransformBuilder(); + protected override AffineTransformBuilder CreateBuilder() + => new AffineTransformBuilder(); protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees) => builder.AppendRotationDegrees(degrees); @@ -68,4 +68,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms 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 deleted file mode 100644 index ed6d3ef2bc..0000000000 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs +++ /dev/null @@ -1,246 +0,0 @@ -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 bba4661db0..50fff725bf 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms @@ -16,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void AutoOrient_AutoOrientProcessor() { this.operations.AutoOrient(); - this.Verify>(); + 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 6731debd36..9fa75448b0 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -1,11 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 @@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void CropWidthHeightCropProcessorWithRectangleSet(int width, int height) { this.operations.Crop(width, height); - CropProcessor processor = this.Verify>(); + CropProcessor processor = this.Verify(); Assert.Equal(new Rectangle(0, 0, width, height), processor.CropRectangle); } @@ -30,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { var cropRectangle = new Rectangle(x, y, width, height); this.operations.Crop(cropRectangle); - CropProcessor processor = this.Verify>(); + CropProcessor processor = this.Verify(); Assert.Equal(cropRectangle, processor.CropRectangle); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs index 03a8628a56..f2ca8dee5f 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using Xunit; @@ -16,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void EntropyCropThresholdFloatEntropyCropProcessorWithThreshold(float threshold) { this.operations.EntropyCrop(threshold); - EntropyCropProcessor processor = this.Verify>(); + EntropyCropProcessor processor = this.Verify(); Assert.Equal(threshold, processor.Threshold); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs index 39adcaa3fa..3f6e26b8ee 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -1,10 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms @@ -14,7 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public class FlipTests : BaseImageOperationsExtensionTest { - [Theory] [InlineData(FlipMode.None)] [InlineData(FlipMode.Horizontal)] @@ -22,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void Flip_degreesFloat_RotateProcessorWithAnglesSetAndExpandTrue(FlipMode flip) { this.operations.Flip(flip); - FlipProcessor flipProcessor = this.Verify>(); + 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 82d7682558..3f49b0f02c 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -1,15 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Transforms; - public class PadTest : BaseImageOperationsExtensionTest { [Fact] @@ -20,11 +18,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms IResampler sampler = KnownResamplers.NearestNeighbor; this.operations.Pad(width, height); - ResizeProcessor resizeProcessor = this.Verify>(); + ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.Width); - Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); 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 d82cd1689d..d95992d6b5 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs @@ -1,28 +1,33 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { public class ProjectiveTransformBuilderTests : TransformBuilderTestBase { - protected override ProjectiveTransformBuilder CreateBuilder(Rectangle rectangle) => new ProjectiveTransformBuilder(); + protected override ProjectiveTransformBuilder CreateBuilder() + => 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 AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY) - => builder.AppendSkewDegrees(degreesX, degreesY); + => builder.AppendSkewDegrees(degreesX, degreesY); protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) => builder.AppendSkewDegrees(degreesX, degreesY, origin); @@ -45,7 +50,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms 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); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 056f66af7f..2fd5f2a7d4 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -10,8 +10,8 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Transforms { public class ProjectiveTransformTests @@ -45,19 +45,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { 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; @@ -65,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler sampler = GetResampler(resamplerName); using (Image image = provider.GetImage()) @@ -81,9 +77,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms } [Theory] - [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Rgba32.Red), PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Color.Red), PixelTypes.Rgba32)] public void Transform_WithTaperMatrix(TestImageProvider provider, TaperSide taperSide, TaperCorner taperCorner) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -101,9 +97,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] public void RawTransformMatchesDocumentedExample(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - // Printing some extra output to help investigating roundoff errors: + // 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 @@ -126,17 +122,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithSolidFilledImages(290, 154, 0, 0, 255, PixelTypes.Rgba32)] public void PerspectiveTransformMatchesCSS(TestImageProvider provider) - where TPixel : struct, IPixel + 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 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); +#pragma warning restore SA1117 // Parameters should be on same line or separate lines ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() .AppendMatrix(matrix); @@ -160,4 +158,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms 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 948c79d8dd..bf412739d9 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -1,10 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -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 @@ -17,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms int width = 50; int height = 100; this.operations.Resize(width, height); - ResizeProcessor resizeProcessor = this.Verify>(); + ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.Width); - Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); } [Fact] @@ -30,10 +28,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms int height = 100; IResampler sampler = KnownResamplers.Lanczos3; this.operations.Resize(width, height, sampler); - ResizeProcessor resizeProcessor = this.Verify>(); + ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.Width); - Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); } @@ -47,10 +45,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms // ReSharper disable once ConditionIsAlwaysTrueOrFalse this.operations.Resize(width, height, sampler, compand); - ResizeProcessor resizeProcessor = this.Verify>(); + ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.Width); - Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(compand, resizeProcessor.Compand); } @@ -73,10 +71,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms }; this.operations.Resize(resizeOptions); - ResizeProcessor resizeProcessor = this.Verify>(); + ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.Width); - Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(compand, resizeProcessor.Compand); @@ -88,4 +86,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms 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 dccf7afa6a..379d399669 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using Xunit; @@ -26,8 +25,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void RotateDegreesFloatRotateProcessorWithAnglesSet(RotateMode angle, FlipMode flip, float expectedAngle) { this.operations.RotateFlip(angle, flip); - RotateProcessor rotateProcessor = this.Verify>(0); - FlipProcessor flipProcessor = this.Verify>(1); + RotateProcessor rotateProcessor = this.Verify(0); + FlipProcessor flipProcessor = this.Verify(1); Assert.Equal(expectedAngle, rotateProcessor.Degrees); Assert.Equal(flip, flipProcessor.FlipMode); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs index ae312d7235..6f7dbd9de9 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -17,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void RotateDegreesFloatRotateProcessorWithAnglesSet(float angle) { this.operations.Rotate(angle); - RotateProcessor processor = this.Verify>(); + RotateProcessor processor = this.Verify(); Assert.Equal(angle, processor.Degrees); } @@ -30,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void RotateRotateTypeRotateProcessorWithAnglesConvertedFromEnum(RotateMode angle, float expectedAngle) { this.operations.Rotate(angle); // is this api needed ??? - RotateProcessor processor = this.Verify>(); + RotateProcessor processor = this.Verify(); Assert.Equal(expectedAngle, processor.Degrees); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs index 73754b9716..de276b427e 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; @@ -16,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void SkewXYCreateSkewProcessorWithAnglesSet() { this.operations.Skew(10, 20); - SkewProcessor processor = this.Verify>(); + SkewProcessor processor = this.Verify(); Assert.Equal(10, processor.DegreesX); Assert.Equal(20, processor.DegreesY); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index 71e3b71797..4306732e8a 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; using Xunit; @@ -26,11 +25,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [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: var size = new Size(123, 321); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.AppendScale(builder, new SizeF(scale)); this.AppendTranslation(builder, translate); @@ -50,11 +51,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [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: var size = new Size(456, 432); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.AppendTranslation(builder, translate); this.AppendScale(builder, new SizeF(scale)); @@ -69,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void LocationOffsetIsPrepended(int locationX, int locationY) { var rectangle = new Rectangle(locationX, locationY, 10, 10); - TBuilder builder = this.CreateBuilder(rectangle); + TBuilder builder = this.CreateBuilder(); this.AppendScale(builder, new SizeF(2, 2)); @@ -91,12 +94,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms float y) { var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.AppendRotationDegrees(builder, degrees); // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness - Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size); + Matrix3x2 matrix = TransformUtilities.CreateRotationMatrixDegrees(degrees, size); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); @@ -119,7 +122,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms float y) { var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); var centerPoint = new Vector2(cx, cy); this.AppendRotationDegrees(builder, degrees, centerPoint); @@ -146,11 +149,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms float y) { var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.AppendSkewDegrees(builder, degreesX, degreesY); - Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size); + Matrix3x2 matrix = TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); @@ -173,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms float y) { var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); var centerPoint = new Vector2(cx, cy); this.AppendSkewDegrees(builder, degreesX, degreesY, centerPoint); @@ -191,8 +194,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void AppendPrependOpposite() { var rectangle = new Rectangle(-1, -1, 3, 3); - TBuilder b1 = this.CreateBuilder(rectangle); - TBuilder b2 = this.CreateBuilder(rectangle); + TBuilder b1 = this.CreateBuilder(); + TBuilder b2 = this.CreateBuilder(); const float pi = (float)Math.PI; @@ -229,14 +232,24 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.ThrowsAny( () => { - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.Execute(builder, new Rectangle(Point.Empty, size), Vector2.Zero); }); } - protected TBuilder CreateBuilder(Size size) => this.CreateBuilder(new Rectangle(Point.Empty, size)); + [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 TBuilder CreateBuilder(Rectangle rectangle); + protected abstract TBuilder CreateBuilder(); protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); @@ -272,4 +285,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms 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 index 909e505357..81c415c065 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; using Xunit; @@ -15,21 +15,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { int xy = 1; - using (var img = new Image(xy, xy)) + 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); + img.Metadata.ExifProfile = profile; + profile.SetValue(ExifTag.PixelXDimension, xy + ushort.MaxValue); + profile.SetValue(ExifTag.PixelYDimension, xy + ushort.MaxValue); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); - TransformProcessorHelpers.UpdateDimensionalMetData(img); + TransformProcessorHelpers.UpdateDimensionalMetadata(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 index 65989556d2..78fb99802e 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,6 +13,9 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Abstractions; +// in this file, comments are used for disabling stuff for local execution +#pragma warning disable SA1515 + namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { public class JpegProfilingBenchmarks : MeasureFixture @@ -22,24 +25,28 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { } - public static readonly TheoryData DecodeJpegData = new TheoryData + 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, + { TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, 20 }, + { TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, 20 }, + { TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, 40 }, + // { TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, 10 }, + // { TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, 5 }, + { TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, 5 } }; [Theory(Skip = ProfilingSetup.SkipProfilingTests)] [MemberData(nameof(DecodeJpegData))] - public void DecodeJpeg(string fileName) + public void DecodeJpeg(string fileName, int executionCount) { - this.DecodeJpegBenchmarkImpl(fileName, new JpegDecoder()); + var decoder = new JpegDecoder() + { + IgnoreMetadata = true + }; + this.DecodeJpegBenchmarkImpl(fileName, decoder, executionCount); } - private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder) + private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder, int executionCount) { // do not run this on CI even by accident if (TestEnvironment.RunsOnCI) @@ -47,8 +54,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks return; } - const int ExecutionCount = 20; - if (!Vector.IsHardwareAccelerated) { throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); @@ -58,14 +63,16 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks byte[] bytes = File.ReadAllBytes(path); this.Measure( - ExecutionCount, + executionCount, () => { var img = Image.Load(bytes, decoder); img.Dispose(); }, - // ReSharper disable once ExplicitCallerInfoArgument +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + // ReSharper disable once ExplicitCallerInfoArgument $"Decode {fileName}"); +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } // Benchmark, enable manually! @@ -82,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks return; } - string[] testFiles = TestImages.Bmp.All + string[] testFiles = TestImages.Bmp.Benchmark .Concat(new[] { TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk }).ToArray(); Image[] testImages = testFiles.Select( @@ -101,8 +108,10 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks ms.Seek(0, SeekOrigin.Begin); } }, - // ReSharper disable once ExplicitCallerInfoArgument +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + // ReSharper disable once ExplicitCallerInfoArgument $@"Encode {testFiles.Length} images"); +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } foreach (Image image in testImages) @@ -111,4 +120,4 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs index 95fe4e48f1..2d67b0ebd7 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -28,7 +28,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks using (var ms = new MemoryStream()) { - this.Measure(30, + this.Measure( + 30, () => { using (var image = Image.Load(configuration, imageBytes)) @@ -36,9 +37,10 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks 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 f9a68d4e7c..5adec670fe 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 @@ -15,4 +14,4 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks "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 8b93559381..7d1a844d9a 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -20,13 +20,14 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } 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, + this.Measure( + this.ExecutionCount, () => { using (var image = new Image(this.configuration, width, height)) @@ -35,6 +36,5 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } }); } - } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs b/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs new file mode 100644 index 0000000000..9a8d8351b2 --- /dev/null +++ b/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Quantization +{ + public class PixelSamplingStrategyTests + { + public static readonly TheoryData DefaultPixelSamplingStrategy_Data = new TheoryData() + { + { 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 }, + }; + + [Fact] + public void ExtensivePixelSamplingStrategy_EnumeratesAll() + { + using Image image = CreateTestImage(100, 100, 100); + var strategy = new ExtensivePixelSamplingStrategy(); + + foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(image)) + { + PaintWhite(region); + } + + using Image expected = CreateTestImage(100, 100, 100, true); + + ImageComparer.Exact.VerifySimilarity(expected, image); + } + + [Theory] + [WithBlankImages(nameof(DefaultPixelSamplingStrategy_Data), 1, 1, PixelTypes.L8)] + public void DefaultPixelSamplingStrategy_IsFair(TestImageProvider dummyProvider, int width, int height, int noOfFrames, int maximumNumberOfPixels) + { + using Image image = CreateTestImage(width, height, noOfFrames); + + var strategy = new DefaultPixelSamplingStrategy(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}"); + } + + private static void PaintWhite(Buffer2DRegion region) + { + var white = new L8(255); + for (int y = 0; y < region.Height; y++) + { + region.GetRowSpan(y).Fill(white); + } + } + + private static Image CreateTestImage(int width, int height, int noOfFrames, bool paintWhite = false) + { + L8 bg = paintWhite ? new L8(255) : default; + var image = new Image(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 a0d7869e39..7d57d8c499 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -1,6 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -16,19 +18,34 @@ namespace SixLabors.ImageSharp.Tests public void QuantizersDitherByDefault() { var werner = new WernerPaletteQuantizer(); - var websafe = new WebSafePaletteQuantizer(); + 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.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); + } - 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 = webSafe.CreatePixelSpecificQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } + + using (IQuantizer quantizer = octree.CreatePixelSpecificQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } + + using (IQuantizer quantizer = wu.CreatePixelSpecificQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } } [Theory] @@ -37,21 +54,28 @@ namespace SixLabors.ImageSharp.Tests public void OctreeQuantizerYieldsCorrectTransparentPixel( TestImageProvider provider, bool dither) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - Assert.True(image[0, 0].Equals(default(TPixel))); + Assert.True(image[0, 0].Equals(default)); + + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } - var quantizer = new OctreeQuantizer(dither); + var quantizer = new OctreeQuantizer(options); foreach (ImageFrame frame in image.Frames) { - QuantizedFrame quantized = - quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration)) + using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) + { + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); + } } } } @@ -60,36 +84,44 @@ namespace SixLabors.ImageSharp.Tests [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 + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - Assert.True(image[0, 0].Equals(default(TPixel))); + Assert.True(image[0, 0].Equals(default)); - var quantizer = new WuQuantizer(dither); + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } + + var quantizer = new WuQuantizer(options); foreach (ImageFrame frame in image.Frames) { - QuantizedFrame quantized = - quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration)) + using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) + { + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); + } } } } - private int GetTransparentIndex(QuantizedFrame quantized) - where TPixel : struct, IPixel + 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; - Rgba32 trans = default; - for (int i = quantized.Palette.Length - 1; i >= 0; i--) - { - quantized.Palette[i].ToRgba32(ref trans); + ReadOnlySpan paletteSpan = quantized.Palette.Span; + Span colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length); - if (trans.Equals(default)) + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan); + for (int i = colorSpan.Length - 1; i >= 0; i--) + { + if (colorSpan[i].Equals(default)) { index = i; } @@ -98,4 +130,4 @@ namespace SixLabors.ImageSharp.Tests return index; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs new file mode 100644 index 0000000000..71a8702c70 --- /dev/null +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -0,0 +1,186 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 + { + [Fact] + public void SinglePixelOpaque() + { + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + + using var image = new Image(config, 1, 1, Color.Black); + 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)result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelRowSpan(0)[0]); + } + + [Fact] + public void SinglePixelTransparent() + { + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + + using var image = new Image(config, 1, 1, default(Rgba32)); + 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(default, result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelRowSpan(0)[0]); + } + + [Fact] + public void GrayScale() => TestScale(c => new Rgba32(c, c, c, 128)); + + [Fact] + public void RedScale() => TestScale(c => new Rgba32(c, 0, 0, 128)); + + [Fact] + public void GreenScale() => TestScale(c => new Rgba32(0, c, 0, 128)); + + [Fact] + public void BlueScale() => TestScale(c => new Rgba32(0, 0, c, 128)); + + [Fact] + public void AlphaScale() => TestScale(c => new Rgba32(0, 0, 0, c)); + + [Fact] + public void Palette256() + { + 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); + + image[0, i] = new Rgba32(r, g, b, a); + } + + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(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(256, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(256, result.Height); + + var actualImage = new Image(1, 256); + + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < actualImage.Height; y++) + { + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelRowSpan(y); + + for (int x = 0; x < actualImage.Width; x++) + { + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + } + } + + for (int y = 0; y < image.Height; y++) + { + Assert.True(image.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y))); + } + } + + [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; + var quantizer = new WuQuantizer(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 (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); + } + + for (int i = 0; i < 256; i++) + { + byte c = (byte)((i & ~7) + 4); + expectedImage[0, i] = pixelBuilder.Invoke(c); + } + + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(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(4 * 8, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(256, result.Height); + + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < actualImage.Height; y++) + { + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelRowSpan(y); + + for (int x = 0; x < actualImage.Width; x++) + { + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + } + } + } + + for (int y = 0; y < expectedImage.Height; y++) + { + Assert.True(expectedImage.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y))); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/RunExtendedTests.cmd b/tests/ImageSharp.Tests/RunExtendedTests.cmd deleted file mode 100644 index c2f4b9f537..0000000000 --- a/tests/ImageSharp.Tests/RunExtendedTests.cmd +++ /dev/null @@ -1,9 +0,0 @@ -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/TestDataIcc/IccTestDataArray.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs index 771e330389..41faf235ef 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Tests { 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 = @@ -14,14 +12,9 @@ namespace SixLabors.ImageSharp.Tests 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 - ( + public static readonly byte[] UInt16_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_0, IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, @@ -31,22 +24,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt16_6, IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.UInt16_8, - IccTestDataPrimitives.UInt16_9 - ); + 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 - ( + public static readonly byte[] Int16_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Int16_0, IccTestDataPrimitives.Int16_1, IccTestDataPrimitives.Int16_2, @@ -56,22 +43,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Int16_6, IccTestDataPrimitives.Int16_7, IccTestDataPrimitives.Int16_8, - IccTestDataPrimitives.Int16_9 - ); + 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 - ( + public static readonly byte[] UInt32_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, @@ -81,22 +62,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.UInt32_7, IccTestDataPrimitives.UInt32_8, - IccTestDataPrimitives.UInt32_9 - ); + 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 - ( + public static readonly byte[] Int32_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Int32_0, IccTestDataPrimitives.Int32_1, IccTestDataPrimitives.Int32_2, @@ -106,22 +81,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Int32_6, IccTestDataPrimitives.Int32_7, IccTestDataPrimitives.Int32_8, - IccTestDataPrimitives.Int32_9 - ); + 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 - ( + public static readonly byte[] UInt64_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt64_0, IccTestDataPrimitives.UInt64_1, IccTestDataPrimitives.UInt64_2, @@ -131,14 +100,11 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt64_6, IccTestDataPrimitives.UInt64_7, IccTestDataPrimitives.UInt64_8, - IccTestDataPrimitives.UInt64_9 - ); + 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 b39892a81d..1c47492e4d 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs @@ -1,86 +1,71 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataCurves { - #region Response - +#pragma warning disable SA1118 // Parameter should not span multiple lines /// /// Channels: 3 /// - public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve - ( + public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve( IccCurveMeasurementEncodings.StatusA, - new Vector3[] + new[] { IccTestDataNonPrimitives.XyzNumber_ValVar1, IccTestDataNonPrimitives.XyzNumber_ValVar2, - IccTestDataNonPrimitives.XyzNumber_ValVar3, + 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 }, - } - ); + }); +#pragma warning restore SA1118 // Parameter should not span multiple lines /// /// Channels: 3 /// - public static readonly byte[] Response_Grad = ArrayHelper.Concat - ( + 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 - ); + IccTestDataNonPrimitives.ResponseNumber_6); public static readonly object[][] ResponseCurveTestData = { 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 - ( + public static readonly byte[] Parametric_Var1 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, 0x00, 0x00, }, - IccTestDataPrimitives.Fix16_1 - ); + IccTestDataPrimitives.Fix16_1); - public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat( new byte[] { 0x00, 0x01, @@ -88,11 +73,9 @@ namespace SixLabors.ImageSharp.Tests }, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3 - ); + IccTestDataPrimitives.Fix16_3); - public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat( new byte[] { 0x00, 0x02, @@ -101,11 +84,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3, - IccTestDataPrimitives.Fix16_4 - ); + IccTestDataPrimitives.Fix16_4); - public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -115,11 +96,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3, IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_5 - ); + IccTestDataPrimitives.Fix16_5); - public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat( new byte[] { 0x00, 0x04, @@ -131,8 +110,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Fix16_7 - ); + IccTestDataPrimitives.Fix16_7); public static readonly object[][] ParametricCurveTestData = { @@ -143,16 +121,12 @@ namespace SixLabors.ImageSharp.Tests new object[] { Parametric_Var5, Parametric_ValVar5 }, }; - #endregion - - #region Formula Segment - + // 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 - ( + public static readonly byte[] Formula_Var1 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, @@ -161,11 +135,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4 - ); + IccTestDataPrimitives.Single_4); - public static readonly byte[] Formula_Var2 = ArrayHelper.Concat - ( + public static readonly byte[] Formula_Var2 = ArrayHelper.Concat( new byte[] { 0x00, 0x01, @@ -175,11 +147,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_5 - ); + IccTestDataPrimitives.Single_5); - public static readonly byte[] Formula_Var3 = ArrayHelper.Concat - ( + public static readonly byte[] Formula_Var3 = ArrayHelper.Concat( new byte[] { 0x00, 0x02, @@ -189,8 +159,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, - IccTestDataPrimitives.Single_6 - ); + IccTestDataPrimitives.Single_6); public static readonly object[][] FormulaCurveSegmentTestData = { @@ -199,17 +168,12 @@ namespace SixLabors.ImageSharp.Tests new object[] { Formula_Var3, Formula_ValVar3 }, }; - #endregion - - #region Sampled Segment - + // 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 - ( + public static readonly byte[] Sampled_Grad1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_9, - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, @@ -218,13 +182,10 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_6, IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, - IccTestDataPrimitives.Single_9 - ); + IccTestDataPrimitives.Single_9); - public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat - ( + public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_9, - IccTestDataPrimitives.Single_9, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_7, @@ -233,8 +194,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_2, - IccTestDataPrimitives.Single_1 - ); + IccTestDataPrimitives.Single_1); public static readonly object[][] SampledCurveSegmentTestData = { @@ -242,65 +202,51 @@ namespace SixLabors.ImageSharp.Tests 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 - ( + public static readonly byte[] Segment_Formula1 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var1 - ); + Formula_Var1); - public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var2 - ); + Formula_Var2); - public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var3 - ); + Formula_Var3); - public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat( new byte[] { 0x73, 0x61, 0x6D, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Sampled_Grad1 - ); + Sampled_Grad1); - public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat( new byte[] { 0x73, 0x61, 0x6D, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Sampled_Grad2 - ); + Sampled_Grad2); public static readonly object[][] CurveSegmentTestData = { @@ -311,28 +257,19 @@ namespace SixLabors.ImageSharp.Tests new object[] { Segment_Sampled2, Segment_ValSampled2 }, }; - #endregion - - #region One Dimensional - - public static readonly IccOneDimensionalCurve OneDimensional_ValFormula1 = new IccOneDimensionalCurve - ( + 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 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 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 } - ); + new IccCurveSegment[] { Segment_ValSampled1, Segment_ValSampled2, Segment_ValSampled1 }); - public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -342,11 +279,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Formula1, Segment_Formula2, - Segment_Formula3 - ); + Segment_Formula3); - public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -356,11 +291,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Formula3, Segment_Formula2, - Segment_Formula1 - ); + Segment_Formula1); - public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -370,8 +303,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Sampled1, Segment_Sampled2, - Segment_Sampled1 - ); + Segment_Sampled1); public static readonly object[][] OneDimensionalCurveTestData = { @@ -379,7 +311,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { OneDimensional_Formula2, OneDimensional_ValFormula2 }, new object[] { OneDimensional_Sampled, OneDimensional_ValSampled }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs index 4ab0b0f6b0..cf838d82e7 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs @@ -1,28 +1,34 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataLut { - #region LUT8 - public static readonly IccLut LUT8_ValGrad = CreateLUT8Val(); public static readonly byte[] LUT8_Grad = CreateLUT8(); private static IccLut CreateLUT8Val() { float[] result = new float[256]; - for (int i = 0; i < 256; i++) { result[i] = i / 255f; } + for (int i = 0; i < 256; i++) + { + result[i] = i / 255f; + } + return new IccLut(result); } private static byte[] CreateLUT8() { byte[] result = new byte[256]; - for (int i = 0; i < 256; i++) { result[i] = (byte)i; } + for (int i = 0; i < 256; i++) + { + result[i] = (byte)i; + } + return result; } @@ -31,10 +37,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { LUT8_Grad, LUT8_ValGrad }, }; - #endregion - - #region LUT16 - public static readonly IccLut LUT16_ValGrad = new IccLut(new float[] { 1f / ushort.MaxValue, @@ -50,8 +52,7 @@ namespace SixLabors.ImageSharp.Tests 1f }); - public static readonly byte[] LUT16_Grad = ArrayHelper.Concat - ( + public static readonly byte[] LUT16_Grad = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_3, @@ -62,20 +63,14 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt16_8, IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.UInt16_32768, - IccTestDataPrimitives.UInt16_Max - ); + 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 - ( + public static readonly IccClut CLUT8_ValGrad = new IccClut( new float[][] { new float[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue }, @@ -90,8 +85,8 @@ namespace SixLabors.ImageSharp.Tests 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 - ); + new byte[] { 3, 3 }, + IccClutDataType.UInt8); /// /// Input Channel Count: 2 @@ -118,12 +113,7 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUT8_Grad, CLUT8_ValGrad, 2, 3, new byte[] { 3, 3 } }, }; - #endregion - - #region CLUT16 - - public static readonly IccClut CLUT16_ValGrad = new IccClut - ( + public static readonly IccClut CLUT16_ValGrad = new IccClut( new float[][] { new float[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue }, @@ -138,8 +128,8 @@ namespace SixLabors.ImageSharp.Tests 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 - ); + new byte[] { 3, 3 }, + IccClutDataType.UInt16); /// /// Input Channel Count: 2 @@ -166,12 +156,7 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUT16_Grad, CLUT16_ValGrad, 2, 3, new byte[] { 3, 3 } }, }; - #endregion - - #region CLUTf32 - - public static readonly IccClut CLUTf32_ValGrad = new IccClut - ( + public static readonly IccClut CLUTf32_ValGrad = new IccClut( new float[][] { new float[] { 1f, 2f, 3f }, @@ -186,61 +171,65 @@ namespace SixLabors.ImageSharp.Tests new float[] { 4f, 5f, 6f }, new float[] { 7f, 8f, 9f }, }, - new byte[] { 3, 3 }, IccClutDataType.Float - ); + 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 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 - ( + 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 - ); + CLUT8_Grad); - public static readonly byte[] CLUT_16 = ArrayHelper.Concat - ( + 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 - ); + CLUT16_Grad); - public static readonly byte[] CLUT_f32 = ArrayHelper.Concat - ( + 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 - ); + CLUTf32_Grad); public static readonly object[][] ClutTestData = { @@ -248,7 +237,5 @@ namespace SixLabors.ImageSharp.Tests 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 1fb745c38f..0704e91368 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs @@ -1,17 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Tests { - using SixLabors.ImageSharp.Primitives; + using SixLabors.ImageSharp; internal static class IccTestDataMatrix { - #region 2D - /// /// 3x3 Matrix /// @@ -21,6 +18,7 @@ namespace SixLabors.ImageSharp.Tests { 4, 5, 6 }, { 7, 8, 9 }, }; + /// /// 3x3 Matrix /// @@ -54,56 +52,44 @@ namespace SixLabors.ImageSharp.Tests /// /// 3x3 Matrix /// - public static readonly byte[] Fix16_2D_Grad = ArrayHelper.Concat - ( + 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 - ); + IccTestDataPrimitives.Fix16_9); /// /// 3x3 Matrix /// - public static readonly byte[] Fix16_2D_Identity = ArrayHelper.Concat - ( + 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 - ); + IccTestDataPrimitives.Fix16_1); /// /// 3x3 Matrix /// - public static readonly byte[] Single_2D_Grad = ArrayHelper.Concat - ( + 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 - ); + IccTestDataPrimitives.Single_9); public static readonly object[][] Matrix2D_FloatArrayTestData = { @@ -126,14 +112,11 @@ namespace SixLabors.ImageSharp.Tests 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 /// @@ -142,22 +125,18 @@ namespace SixLabors.ImageSharp.Tests /// /// 3x1 Matrix /// - public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_7 - ); + IccTestDataPrimitives.Fix16_7); /// /// 3x1 Matrix /// - public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_7 - ); + IccTestDataPrimitives.Single_7); public static readonly object[][] Matrix1D_ArrayTestData = { @@ -170,7 +149,5 @@ namespace SixLabors.ImageSharp.Tests 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 fb648198da..0a0dcc6bb8 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs @@ -1,14 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { - internal static class IccTestDataMultiProcessElement + internal static class IccTestDataMultiProcessElements { - #region CurveSet - /// /// Input Channel Count: 3 /// Output Channel Count: 3 @@ -19,60 +17,48 @@ namespace SixLabors.ImageSharp.Tests IccTestDataCurves.OneDimensional_ValFormula2, IccTestDataCurves.OneDimensional_ValFormula1 }); + /// /// Input Channel Count: 3 /// Output Channel Count: 3 /// - public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat - ( + public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat( IccTestDataCurves.OneDimensional_Formula1, IccTestDataCurves.OneDimensional_Formula2, - IccTestDataCurves.OneDimensional_Formula1 - ); + 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 - ( + public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement( IccTestDataMatrix.Single_2DArray_ValGrad, - IccTestDataMatrix.Single_1DArray_ValGrad - ); + IccTestDataMatrix.Single_1DArray_ValGrad); + /// /// Input Channel Count: 3 /// Output Channel Count: 3 /// - public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat - ( + public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat( IccTestDataMatrix.Single_2D_Grad, - IccTestDataMatrix.Single_1D_Grad - ); + IccTestDataMatrix.Single_1D_Grad); public static readonly object[][] MatrixTestData = { new object[] { MatrixPE_Grad, MatrixPE_ValGrad, 3, 3 }, }; - - #endregion - - #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 @@ -84,48 +70,38 @@ namespace SixLabors.ImageSharp.Tests 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 - ( + public static readonly byte[] MPE_Matrix = ArrayHelper.Concat( new byte[] { 0x6D, 0x61, 0x74, 0x66, 0x00, 0x03, 0x00, 0x03, }, - MatrixPE_Grad - ); + MatrixPE_Grad); - public static readonly byte[] MPE_CLUT = ArrayHelper.Concat - ( + public static readonly byte[] MPE_CLUT = ArrayHelper.Concat( new byte[] { 0x63, 0x6C, 0x75, 0x74, 0x00, 0x02, 0x00, 0x03, }, - CLUTPE_Grad - ); + CLUTPE_Grad); - public static readonly byte[] MPE_Curve = ArrayHelper.Concat - ( + public static readonly byte[] MPE_Curve = ArrayHelper.Concat( new byte[] { 0x6D, 0x66, 0x6C, 0x74, 0x00, 0x03, 0x00, 0x03, }, - CurvePE_Grad - ); + CurvePE_Grad); public static readonly byte[] MPE_bACS = { @@ -151,7 +127,5 @@ namespace SixLabors.ImageSharp.Tests 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 f16da90c65..0b8bbf15d4 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs @@ -1,17 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Globalization; using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataNonPrimitives { - #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); @@ -63,10 +61,6 @@ namespace SixLabors.ImageSharp.Tests 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); @@ -85,10 +79,6 @@ namespace SixLabors.ImageSharp.Tests 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); @@ -113,10 +103,6 @@ namespace SixLabors.ImageSharp.Tests 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); @@ -132,10 +118,6 @@ namespace SixLabors.ImageSharp.Tests 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); @@ -151,10 +133,6 @@ namespace SixLabors.ImageSharp.Tests 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); @@ -187,43 +165,48 @@ namespace SixLabors.ImageSharp.Tests new object[] { ResponseNumber_Max, ResponseNumber_ValMax }, }; - #endregion - - #region NamedColor - - public static readonly IccNamedColor NamedColor_ValMin = new IccNamedColor - ( + 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 - ( + 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 - ( + 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 } - ); + 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) + private static byte[] CreateNamedColor(int devCoordCount, byte name, byte pCS, byte device) { - byte[] data = new byte[32 + 6 + devCoordCount * 2]; + byte[] data = new byte[32 + 6 + (devCoordCount * 2)]; for (int i = 0; i < data.Length; i++) { - 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 + 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 + { + data[i] = device; // Device Coordinates + } } + return data; } @@ -234,10 +217,6 @@ namespace SixLabors.ImageSharp.Tests 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"); @@ -249,98 +228,78 @@ namespace SixLabors.ImageSharp.Tests 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 - ( + 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 - ( + 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 - ); + new byte[] { 0x00 }); // Null terminator - public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription - ( - 1, 2, + public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription( + 1, + 2, IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, IccProfileTag.ProfileDescription, MultiLocalizedUnicode_Val.Texts, - MultiLocalizedUnicode_Val.Texts - ); + MultiLocalizedUnicode_Val.Texts); - public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription - ( - 1, 2, + public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription( + 1, + 2, IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, IccProfileTag.ProfileDescription, new IccLocalizedString[] { LocalizedString_Rand1 }, - new IccLocalizedString[] { LocalizedString_Rand1 } - ); + new IccLocalizedString[] { LocalizedString_Rand1 }); - public static readonly byte[] ProfileDescription_Rand1 = ArrayHelper.Concat - ( + 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 - ); + MultiLocalizedUnicode_Arr); - public static readonly byte[] ProfileDescription_Rand2 = ArrayHelper.Concat - ( + 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 - ); + TextDescription_Arr1); public static readonly object[][] ProfileDescriptionReadTestData = { @@ -353,30 +312,22 @@ namespace SixLabors.ImageSharp.Tests 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 - ( + 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 - ); + IccTestDataPrimitives.UInt16_3); - public static readonly byte[] ColorantTableEntry_Rand2 = ArrayHelper.Concat - ( + 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 - ); + IccTestDataPrimitives.UInt16_6); public static readonly object[][] ColorantTableEntryTestData = { @@ -384,33 +335,23 @@ namespace SixLabors.ImageSharp.Tests 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 - ( + public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Int32_7 - ); + IccTestDataPrimitives.Int32_7); - public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat - ( + public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_8, IccTestDataPrimitives.Fix16_5, - IccTestDataPrimitives.Int32_3 - ); + 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 b24e3f24a4..0bf62a83ed 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Tests { 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 }; @@ -20,10 +18,6 @@ namespace SixLabors.ImageSharp.Tests 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 }; @@ -37,10 +31,6 @@ namespace SixLabors.ImageSharp.Tests 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 }; @@ -53,7 +43,6 @@ namespace SixLabors.ImageSharp.Tests 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; @@ -64,10 +53,6 @@ namespace SixLabors.ImageSharp.Tests 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 }; @@ -81,10 +66,6 @@ namespace SixLabors.ImageSharp.Tests 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 }; @@ -97,10 +78,6 @@ namespace SixLabors.ImageSharp.Tests 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 }; @@ -114,10 +91,6 @@ namespace SixLabors.ImageSharp.Tests 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 }; @@ -131,21 +104,13 @@ namespace SixLabors.ImageSharp.Tests 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 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 }; @@ -168,12 +133,8 @@ namespace SixLabors.ImageSharp.Tests 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 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 }; @@ -194,12 +155,8 @@ namespace SixLabors.ImageSharp.Tests new object[] { UFix16_Max, UFix16_ValMax }, }; - #endregion - - #region U1Fix15 - public const float U1Fix15_ValMin = 0; - public const float U1Fix15_ValMax = 1f + 32767f / 32768f; + public const float U1Fix15_ValMax = 1f + (32767f / 32768f); public static readonly byte[] U1Fix15_0 = { 0x00, 0x00 }; public static readonly byte[] U1Fix15_1 = { 0x80, 0x00 }; @@ -212,12 +169,8 @@ namespace SixLabors.ImageSharp.Tests 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 const float UFix8_ValMax = byte.MaxValue + (255f / 256f); public static readonly byte[] UFix8_0 = { 0x00, 0x00 }; public static readonly byte[] UFix8_1 = { 0x01, 0x00 }; @@ -238,10 +191,6 @@ namespace SixLabors.ImageSharp.Tests 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"; @@ -283,10 +232,6 @@ namespace SixLabors.ImageSharp.Tests 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 = "$€β𐐷𤭢"; @@ -324,7 +269,5 @@ namespace SixLabors.ImageSharp.Tests 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 b037a7a9af..c1cedcc793 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { @@ -78,41 +78,46 @@ namespace SixLabors.ImageSharp.Tests 0x64, 0x63, 0x62, 0x61, // CreatorSignature }, profileId, +#pragma warning disable SA1118 // Parameter should not span multiple lines 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 + (byte)(nrOfEntries >> 24), + (byte)(nrOfEntries >> 16), + (byte)(nrOfEntries >> 8), + (byte)nrOfEntries }); +#pragma warning restore SA1118 // Parameter should not span multiple lines } - public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat(CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), + public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat( + CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), +#pragma warning disable SA1118 // Parameter should not span multiple lines 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) }, +#pragma warning restore SA1118 // Parameter should not span multiple lines IccTestDataTagDataEntry.TagDataEntryHeader_UnknownArr, - IccTestDataTagDataEntry.Unknown_Arr - ); + IccTestDataTagDataEntry.Unknown_Arr); - public static readonly IccProfile Profile_Random_Val = new IccProfile(CreateHeaderRandomValue(168, - Profile_Random_Id_Value, - "acsp"), - new IccTagDataEntry[] - { - IccTestDataTagDataEntry.Unknown_Val, - IccTestDataTagDataEntry.Unknown_Val - }); + public static readonly IccProfile Profile_Random_Val = new IccProfile( + CreateHeaderRandomValue( + 168, + Profile_Random_Id_Value, + "acsp"), + new IccTagDataEntry[] { IccTestDataTagDataEntry.Unknown_Val, IccTestDataTagDataEntry.Unknown_Val }); public static readonly byte[] Header_CorruptDataColorSpace_Array = { @@ -132,8 +137,10 @@ namespace SixLabors.ImageSharp.Tests 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, @@ -159,8 +166,10 @@ namespace SixLabors.ImageSharp.Tests 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, @@ -186,8 +195,10 @@ namespace SixLabors.ImageSharp.Tests 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, @@ -221,4 +232,4 @@ namespace SixLabors.ImageSharp.Tests 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 f7c4efa928..8953f5e07f 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs @@ -1,16 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Globalization; using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataTagDataEntry { - #region TagDataEntry Header - public static readonly IccTypeSignature TagDataEntryHeader_UnknownVal = IccTypeSignature.Unknown; public static readonly byte[] TagDataEntryHeader_UnknownArr = { @@ -39,10 +37,6 @@ namespace SixLabors.ImageSharp.Tests 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 }; @@ -51,63 +45,45 @@ namespace SixLabors.ImageSharp.Tests 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 - ( + 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 - ); + new byte[] { 0x00, 0x00, 0x0F, 0x5C }); // 0.060 - public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry - ( + public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry( new double[][] { new double[] { 1, 2 }, new double[] { 3, 4 }, - } - ); - public static readonly byte[] Chromaticity_Arr2 = ArrayHelper.Concat - ( + }); + + 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 - ); + IccTestDataPrimitives.UFix16_4); /// /// : channel count must be 3 for any enum other than /// - public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat - ( + public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.UInt16_1 - ); + IccTestDataPrimitives.UInt16_1); /// /// : invalid enum value /// - public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat - ( + public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_3, - IccTestDataPrimitives.UInt16_9 - ); + IccTestDataPrimitives.UInt16_9); public static readonly object[][] ChromaticityTagDataEntryTestData = { @@ -115,10 +91,6 @@ namespace SixLabors.ImageSharp.Tests 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 }); @@ -127,52 +99,37 @@ namespace SixLabors.ImageSharp.Tests new object[] { ColorantOrder_Arr, ColorantOrder_Val }, }; - #endregion - - #region ColorantTableTagDataEntry - - public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry - ( + public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry( new IccColorantTableEntry[] { IccTestDataNonPrimitives.ColorantTableEntry_ValRand1, IccTestDataNonPrimitives.ColorantTableEntry_ValRand2 - } - ); - public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat - ( + }); + + public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, IccTestDataNonPrimitives.ColorantTableEntry_Rand1, - IccTestDataNonPrimitives.ColorantTableEntry_Rand2 - ); + 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 - ( + public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, - IccTestDataPrimitives.UFix8_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 - ( + public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_3, IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3 - ); + IccTestDataPrimitives.UInt16_3); public static readonly object[][] CurveTagDataEntryTestData = { @@ -181,26 +138,20 @@ namespace SixLabors.ImageSharp.Tests new object[] { Curve_Arr_2, Curve_Val_2 }, }; - #endregion - - #region DataTagDataEntry - - public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry - ( + public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry( new byte[] { 0x01, 0x02, 0x03, 0x04 }, - false - ); + false); + public static readonly byte[] Data_ArrNoASCII = { 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04 }; - public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry - ( + public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry( new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' }, - true - ); + true); + public static readonly byte[] Data_ArrASCII = { 0x00, 0x00, 0x00, 0x01, @@ -213,10 +164,6 @@ namespace SixLabors.ImageSharp.Tests 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; @@ -225,89 +172,60 @@ namespace SixLabors.ImageSharp.Tests new object[] { DateTime_Arr, DateTime_Val }, }; - #endregion - - #region Lut16TagDataEntry - - public static readonly IccLut16TagDataEntry Lut16_Val = new IccLut16TagDataEntry - ( + 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 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 - ); + 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 - ( + 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 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 - ); + 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 - ( + private static readonly byte[] CurveFull_0 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_0 - ); - private static readonly byte[] CurveFull_1 = ArrayHelper.Concat - ( + Curve_Arr_0); + + private static readonly byte[] CurveFull_1 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_1 - ); - private static readonly byte[] CurveFull_2 = ArrayHelper.Concat - ( + Curve_Arr_1); + + private static readonly byte[] CurveFull_2 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_2 - ); + Curve_Arr_2); - public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry - ( + public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry( new IccCurveTagDataEntry[] { Curve_Val_0, @@ -316,23 +234,13 @@ namespace SixLabors.ImageSharp.Tests }, IccTestDataMatrix.Single_2DArray_ValGrad, IccTestDataMatrix.Single_1DArray_ValGrad, - new IccCurveTagDataEntry[] - { - Curve_Val_1, - Curve_Val_2, - Curve_Val_0, - }, + new IccCurveTagDataEntry[] { Curve_Val_1, Curve_Val_2, Curve_Val_0 }, IccTestDataLut.CLUT_Val16, - new IccCurveTagDataEntry[] - { - Curve_Val_2, - Curve_Val_1, - } - ); - public static readonly byte[] LutAToB_Arr = ArrayHelper.Concat - ( - new byte[] { 0x02, 0x03, 0x00, 0x00 }, + new IccCurveTagDataEntry[] { Curve_Val_2, Curve_Val_1 }); +#pragma warning disable SA1115 // Parameter should follow comma + 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 @@ -365,21 +273,17 @@ namespace SixLabors.ImageSharp.Tests CurveFull_2, // 18 bytes new byte[] { 0x00, 0x00 }, // Padding CurveFull_1, // 14 bytes - new byte[] { 0x00, 0x00 } // Padding - ); + new byte[] { 0x00, 0x00 }); // Padding + +#pragma warning restore SA1115 // Parameter should follow comma public static readonly object[][] LutAToBTagDataEntryTestData = { new object[] { LutAToB_Arr, LutAToB_Val }, }; - #endregion - - #region LutBToATagDataEntry - - public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry - ( - new IccCurveTagDataEntry[] + public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry( + new[] { Curve_Val_0, Curve_Val_1, @@ -388,15 +292,10 @@ namespace SixLabors.ImageSharp.Tests 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[] { Curve_Val_2, Curve_Val_1, Curve_Val_0 }); + +#pragma warning disable SA1115 // Parameter should follow comma + public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat( new byte[] { 0x02, 0x03, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 @@ -406,8 +305,8 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 // B - CurveFull_0, //12 bytes - CurveFull_1, //14 bytes + CurveFull_0, // 12 bytes + CurveFull_1, // 14 bytes new byte[] { 0x00, 0x00 }, // Padding // CLUT @@ -419,41 +318,34 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0x00, 0x00 }, // Padding CurveFull_1, // 14 bytes new byte[] { 0x00, 0x00 }, // Padding - CurveFull_0 // 12 bytes - ); + CurveFull_0); // 12 bytes + +#pragma warning restore SA1115 // Parameter should follow comma 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 IccMeasurementTagDataEntry Measurement_Val = new IccMeasurementTagDataEntry - ( - IccStandardObserver.Cie1931Observer, IccTestDataNonPrimitives.XyzNumber_ValVar1, - IccMeasurementGeometry.Degree0ToDOrDTo0, 1f, IccStandardIlluminant.D50 - ); - public static readonly byte[] Measurement_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Measurement_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UFix16_1, - IccTestDataPrimitives.UInt32_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); @@ -496,11 +388,13 @@ namespace SixLabors.ImageSharp.Tests 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, @@ -510,82 +404,60 @@ namespace SixLabors.ImageSharp.Tests }; public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr_enUS_deDE); - public static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat - ( + 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 - ); + IccTestDataPrimitives.Unicode_Rand3); public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val2 = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr_en_Invariant); - public static readonly byte[] MultiLocalizedUnicode_Arr2_Read = ArrayHelper.Concat - ( + 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 - ); + IccTestDataPrimitives.Unicode_Rand3); - public static readonly byte[] MultiLocalizedUnicode_Arr2_Write = ArrayHelper.Concat - ( + 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 - ); + 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 - ( + 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 - ); + IccTestDataPrimitives.Unicode_Rand2); public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Read = { @@ -601,55 +473,36 @@ namespace SixLabors.ImageSharp.Tests new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, }; - #endregion - - #region MultiProcessElementsTagDataEntry - - public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry - ( + public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry( new IccMultiProcessElement[] { - IccTestDataMultiProcessElement.MPE_ValCLUT, - IccTestDataMultiProcessElement.MPE_ValCLUT, - } - ); - public static readonly byte[] MultiProcessElements_Arr = ArrayHelper.Concat - ( + IccTestDataMultiProcessElements.MPE_ValCLUT, + IccTestDataMultiProcessElements.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 - - IccTestDataMultiProcessElement.MPE_CLUT, - IccTestDataMultiProcessElement.MPE_CLUT - ); + IccTestDataMultiProcessElements.MPE_CLUT, + IccTestDataMultiProcessElements.MPE_CLUT); public static readonly object[][] MultiProcessElementsTagDataEntryTestData = { new object[] { MultiProcessElements_Arr, MultiProcessElements_Val }, }; - #endregion - - #region NamedColor2TagDataEntry - - public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new IccNamedColor2TagDataEntry - ( + 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 - ( + 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, @@ -658,18 +511,13 @@ namespace SixLabors.ImageSharp.Tests ArrayHelper.Fill((byte)0x34, 31), new byte[] { 0x00 }, IccTestDataNonPrimitives.NamedColor_Min, - 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; @@ -678,118 +526,81 @@ namespace SixLabors.ImageSharp.Tests new object[] { ParametricCurve_Arr, ParametricCurve_Val }, }; - #endregion - - #region ProfileSequenceDescTagDataEntry - - public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry - ( + public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry( new IccProfileDescription[] { IccTestDataNonPrimitives.ProfileDescription_ValRand1, IccTestDataNonPrimitives.ProfileDescription_ValRand1 - } - ); - public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat - ( + }); + + public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, IccTestDataNonPrimitives.ProfileDescription_Rand1, - 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 - ( + 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, + }); + 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 } - ); + 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 - ( + public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new IccResponseCurveSet16TagDataEntry( new IccResponseCurve[] { IccTestDataCurves.Response_ValGrad, IccTestDataCurves.Response_ValGrad, - } - ); - public static readonly byte[] ResponseCurveSet16_Arr = ArrayHelper.Concat - ( + }); + + 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 - ); + 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 - ( + public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3 - ); + 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, }; @@ -798,10 +609,6 @@ namespace SixLabors.ImageSharp.Tests 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 }; @@ -810,78 +617,50 @@ namespace SixLabors.ImageSharp.Tests 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 - ( + public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UFix16_1, IccTestDataPrimitives.UFix16_2, - IccTestDataPrimitives.UFix16_3 - ); + 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 - ( + public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3 - ); + 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 - ( + public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, - IccTestDataPrimitives.UInt32_3 - ); + 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 - ( + public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt64_1, IccTestDataPrimitives.UInt64_2, - IccTestDataPrimitives.UInt64_3 - ); + 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 }; @@ -890,88 +669,66 @@ namespace SixLabors.ImageSharp.Tests new object[] { UInt8Array_Arr, UInt8Array_Val, 11u }, }; - #endregion - - #region ViewingConditionsTagDataEntry - - public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry - ( + public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry( IccTestDataNonPrimitives.XyzNumber_ValVar1, IccTestDataNonPrimitives.XyzNumber_ValVar2, - IccStandardIlluminant.D50 - ); - public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat - ( + IccStandardIlluminant.D50); + + public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat( IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataNonPrimitives.XyzNumber_Var2, - IccTestDataPrimitives.UInt32_1 - ); + 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 - ( + + public static readonly byte[] XYZ_Arr = ArrayHelper.Concat( IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataNonPrimitives.XyzNumber_Var2, - IccTestDataNonPrimitives.XyzNumber_Var3 - ); + 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 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 + 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 + 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 + new byte[] { 0x00, 0x00 }, // Null terminator + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 ArrayHelper.Fill((byte)0x41, 66), - new byte[] { 0x00 } // Null terminator - ); + 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 - ( + 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) - ); + ArrayHelper.Fill((byte)0x00, 67)); public static readonly object[][] TextDescriptionTagDataEntryTestData = { @@ -979,19 +736,14 @@ namespace SixLabors.ImageSharp.Tests 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.Ascii_ValRand4); + + public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.Ascii_Rand4, new byte[] { 0 }, @@ -1006,108 +758,74 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0 }, IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.Ascii_Rand4, - new byte[] { 0 } - ); + 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 - ( + 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 - ); + 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.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 } - ); + 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 - ( + public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, Curve_Arr_2, - new byte[] { 0x00, 0x00 } // padding - ); + new byte[] { 0x00, 0x00 }); // padding public static readonly IccTagDataEntry TagDataEntry_MultiLocalizedUnicodeVal = MultiLocalizedUnicode_Val; - public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat - ( + public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat( TagDataEntryHeader_MultiLocalizedUnicodeArr, MultiLocalizedUnicode_Arr, - new byte[] { 0x00, 0x00 } // padding - ); + 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_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 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/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 089249e217..338ccffbe0 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; @@ -27,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests /// The "Formats" directory, as lazy value /// // ReSharper disable once InconsistentNaming - private static readonly Lazy inputImagesDirectory = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); + private static readonly Lazy InputImagesDirectoryValue = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); /// /// The image (lazy initialized value) @@ -54,17 +52,17 @@ namespace SixLabors.ImageSharp.Tests public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.FullPath)); /// - /// The full path to file. + /// Gets the full path to file. /// public string FullPath { get; } /// - /// The file name. + /// Gets the file name. /// public string FileName => Path.GetFileName(this.FullPath); /// - /// The file name without extension. + /// Gets the file name without extension. /// public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); @@ -76,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the input image directory. /// - private static string InputImagesDirectory => inputImagesDirectory.Value; + private static string InputImagesDirectory => InputImagesDirectoryValue.Value; /// /// Gets the full qualified path to the input test file. @@ -134,7 +132,7 @@ namespace SixLabors.ImageSharp.Tests /// /// The . /// - public Image CreateImage() + public Image CreateRgba32Image() { return this.Image.Clone(); } @@ -145,9 +143,9 @@ namespace SixLabors.ImageSharp.Tests /// /// The . /// - public Image CreateImage(IImageDecoder decoder) + public Image CreateRgba32Image(IImageDecoder decoder) { - return ImageSharp.Image.Load(this.Image.GetConfiguration(), this.Bytes, 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 21ad4d2c1a..da43629567 100644 --- a/tests/ImageSharp.Tests/TestFileSystem.cs +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,29 +12,24 @@ namespace SixLabors.ImageSharp.Tests /// public class TestFileSystem : ImageSharp.IO.IFileSystem { + private readonly Dictionary> fileSystem = new Dictionary>(StringComparer.OrdinalIgnoreCase); - public static TestFileSystem Global { get; } = new TestFileSystem(); - - public static void RegisterGlobalTestFormat() - { - Configuration.Default.FileSystem = Global; - } - - Dictionary fileSystem = new Dictionary(StringComparer.OrdinalIgnoreCase); - - public void AddFile(string path, Stream data) + public void AddFile(string path, Func data) { - fileSystem.Add(path, data); + lock (this.fileSystem) + { + this.fileSystem.Add(path, data); + } } public Stream Create(string path) { // if we have injected a fake file use it instead - lock (fileSystem) + lock (this.fileSystem) { - if (fileSystem.ContainsKey(path)) + if (this.fileSystem.ContainsKey(path)) { - Stream stream = fileSystem[path]; + Stream stream = this.fileSystem[path](); stream.Position = 0; return stream; } @@ -43,15 +38,14 @@ namespace SixLabors.ImageSharp.Tests return File.Create(path); } - public Stream OpenRead(string path) { // if we have injected a fake file use it instead - lock (fileSystem) + lock (this.fileSystem) { - if (fileSystem.ContainsKey(path)) + if (this.fileSystem.ContainsKey(path)) { - Stream stream = fileSystem[path]; + Stream stream = this.fileSystem[path](); stream.Position = 0; return stream; } @@ -61,4 +55,3 @@ namespace SixLabors.ImageSharp.Tests } } } - diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs deleted file mode 100644 index ee65402f53..0000000000 --- a/tests/ImageSharp.Tests/TestFont.cs +++ /dev/null @@ -1,86 +0,0 @@ -// 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 new file mode 100644 index 0000000000..0da2e06183 --- /dev/null +++ b/tests/ImageSharp.Tests/TestFontUtilities.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors. +// 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(); + + AddFormatsDirectoryFromTestAssemblyPath(directories); + + string directory = directories.FirstOrDefault(Directory.Exists); + + 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 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 64357a17e1..7273a65f79 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -1,14 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 System.Numerics; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -18,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests /// public class TestFormat : IConfigurationModule, IImageFormat { + private readonly Dictionary sampleImages = new Dictionary(); + // 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(); @@ -30,9 +34,9 @@ namespace SixLabors.ImageSharp.Tests public List DecodeCalls { get; } = new List(); - public IImageEncoder Encoder { get; } + public TestEncoder Encoder { get; } - public IImageDecoder Decoder { get; } + public TestDecoder Decoder { get; } private byte[] header = Guid.NewGuid().ToByteArray(); @@ -45,19 +49,37 @@ namespace SixLabors.ImageSharp.Tests { ms.Write(marker, 0, marker.Length); } + ms.Position = 0; return ms; } - Dictionary _sampleImages = new Dictionary(); - + public Stream CreateAsyncSamaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512) + { + var buffer = new byte[size]; + this.header.CopyTo(buffer, 0); + var semaphoreStream = new SemaphoreReadMemoryStream(buffer, waitAfterPosition, notifyWaitPositionReachedSemaphore, continueSemaphore); + return seeakable ? (Stream)semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false); + } - public void VerifyDecodeCall(byte[] marker, Configuration config) + public void VerifySpecificDecodeCall(byte[] marker, Configuration config) + where TPixel : unmanaged, IPixel { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config)).ToArray(); + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); + Assert.True(discovered.Any(), "No calls to decode on this format with the provided options happened"); - Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend"); + foreach (DecodeOperation d in discovered) + { + this.DecodeCalls.Remove(d); + } + } + + public void VerifyAgnosticDecodeCall(byte[] marker, Configuration config) + { + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); + + Assert.True(discovered.Any(), "No calls to decode on this format with the provided options happened"); foreach (DecodeOperation d in discovered) { @@ -66,19 +88,21 @@ namespace SixLabors.ImageSharp.Tests } public Image Sample() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - lock (this._sampleImages) + lock (this.sampleImages) { - if (!this._sampleImages.ContainsKey(typeof(TPixel))) + if (!this.sampleImages.ContainsKey(typeof(TPixel))) { - this._sampleImages.Add(typeof(TPixel), new Image(1, 1)); + this.sampleImages.Add(typeof(TPixel), new Image(1, 1)); } - return (Image)this._sampleImages[typeof(TPixel)]; + return (Image)this.sampleImages[typeof(TPixel)]; } } + public Image SampleAgnostic() => this.Sample(); + public string MimeType => "img/test"; public string Extension => "test_ext"; @@ -101,6 +125,7 @@ namespace SixLabors.ImageSharp.Tests { return false; } + for (int i = 0; i < this.header.Length; i++) { if (header[i] != this.header[i]) @@ -108,6 +133,7 @@ namespace SixLabors.ImageSharp.Tests return false; } } + return true; } @@ -120,36 +146,37 @@ namespace SixLabors.ImageSharp.Tests public struct DecodeOperation { - public byte[] marker; - internal Configuration config; + public byte[] Marker; + internal Configuration Config; - public bool IsMatch(byte[] testMarker, Configuration config) - { + public Type PixelType; - if (this.config != config) + public bool IsMatch(byte[] testMarker, Configuration config, Type pixelType) + { + if (this.Config != config || this.PixelType != pixelType) { return false; } - if (testMarker.Length != this.marker.Length) + if (testMarker.Length != this.Marker.Length) { return false; } - for (int i = 0; i < this.marker.Length; i++) + for (int i = 0; i < this.Marker.Length; i++) { - if (testMarker[i] != this.marker[i]) + if (testMarker[i] != this.Marker[i]) { return false; } } + return true; } } public class TestHeader : IImageFormatDetector { - private TestFormat testFormat; public int HeaderSize => this.testFormat.HeaderSize; @@ -157,7 +184,9 @@ namespace SixLabors.ImageSharp.Tests public IImageFormat DetectFormat(ReadOnlySpan header) { if (this.testFormat.IsSupportedFileFormat(header)) + { return this.testFormat; + } return null; } @@ -167,7 +196,8 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } } - public class TestDecoder : ImageSharp.Formats.IImageDecoder + + public class TestDecoder : IImageDecoder, IImageInfoDetector { private TestFormat testFormat; @@ -176,29 +206,49 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } - public IEnumerable MimeTypes => new[] { testFormat.MimeType }; + public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; + + public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; - public IEnumerable FileExtensions => testFormat.SupportedExtensions; + public int HeaderSize => this.testFormat.HeaderSize; - public int HeaderSize => testFormat.HeaderSize; + public Image Decode(Configuration config, Stream stream) + where TPixel : unmanaged, IPixel + => this.DecodeImpl(config, stream, default).GetAwaiter().GetResult(); - public Image Decode(Configuration config, Stream stream) where TPixel : struct, IPixel + public Task> DecodeAsync(Configuration config, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => this.DecodeImpl(config, stream, cancellationToken); + private async Task> DecodeImpl(Configuration config, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { var ms = new MemoryStream(); - stream.CopyTo(ms); + await stream.CopyToAsync(ms, config.StreamProcessingBufferSize, cancellationToken); var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); this.testFormat.DecodeCalls.Add(new DecodeOperation { - marker = marker, - config = config + Marker = marker, + Config = config, + PixelType = typeof(TPixel) }); - // TODO record this happend so we can verify it. + // TODO record this happened so we can verify it. return this.testFormat.Sample(); } - public bool IsSupportedFileFormat(Span header) => testFormat.IsSupportedFileFormat(header); + public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); + + public IImageInfo Identify(Configuration configuration, Stream stream) => + this.IdentifyAsync(configuration, stream, default).GetAwaiter().GetResult(); + + public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeImpl(configuration, stream, cancellationToken); } public class TestEncoder : ImageSharp.Formats.IImageEncoder @@ -210,14 +260,93 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } - public IEnumerable MimeTypes => new[] { testFormat.MimeType }; + public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; + + public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; + + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + // TODO record this happened so we can verify it. + } + + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + // TODO record this happened so we can verify it. + return Task.CompletedTask; + } + } + + public struct TestPixelForAgnosticDecode : IPixel + { + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + public void FromScaledVector4(Vector4 vector) + { + } - public IEnumerable FileExtensions => testFormat.SupportedExtensions; + public Vector4 ToScaledVector4() => default; - public void Encode(Image image, Stream stream) where TPixel : struct, IPixel + public void FromVector4(Vector4 vector) { - // TODO record this happend so we can verify it. } + + public Vector4 ToVector4() => default; + + public void FromArgb32(Argb32 source) + { + } + + public void FromBgra5551(Bgra5551 source) + { + } + + public void FromBgr24(Bgr24 source) + { + } + + public void FromBgra32(Bgra32 source) + { + } + + public void FromL8(L8 source) + { + } + + public void FromL16(L16 source) + { + } + + public void FromLa16(La16 source) + { + } + + public void FromLa32(La32 source) + { + } + + public void FromRgb24(Rgb24 source) + { + } + + public void FromRgba32(Rgba32 source) + { + } + + public void ToRgba32(ref Rgba32 dest) + { + } + + public void FromRgb48(Rgb48 source) + { + } + + public void FromRgba64(Rgba64 source) + { + } + + public bool Equals(TestPixelForAgnosticDecode other) => false; } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d83fe4907d..fd5296c375 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Linq; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming // ReSharper disable MemberHidesStaticFromOuterClass namespace SixLabors.ImageSharp.Tests { @@ -27,8 +27,12 @@ namespace SixLabors.ImageSharp.Tests 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 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"; @@ -48,7 +52,11 @@ namespace SixLabors.ImageSharp.Tests 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 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"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; @@ -57,6 +65,12 @@ namespace SixLabors.ImageSharp.Tests public const string Filter3 = "Png/filter3.png"; public const string Filter4 = "Png/filter4.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"; @@ -75,12 +89,51 @@ namespace SixLabors.ImageSharp.Tests 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"; + public static class Bad { + public const string MissingDataChunk = "Png/xdtn0g01.png"; + public const string CorruptedChunk = "Png/big-corrupted-chunk.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"; - public const string CorruptedChunk = "Png/big-corrupted-chunk.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 static readonly string[] All = @@ -123,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests 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 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"; @@ -135,14 +188,18 @@ namespace SixLabors.ImageSharp.Tests 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 static readonly string[] All = { Cmyk, Ycck, Exif, Floorplan, - Calliphora, Turtle, GammaDalaiLamaGray, + Calliphora, Turtle420, GammaDalaiLamaGray, Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, - Ratio1x1, Testorig12bit + Ratio1x1, Testorig12bit, YcckSubsample1222 }; } @@ -167,8 +224,39 @@ namespace SixLabors.ImageSharp.Tests 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 InvalidJpegThrowsWrongException797 = "Jpg/issues/Issue797-InvalidImage.jpg"; - public const string AccessViolationException798 = "Jpg/issues/Issue798-AccessViolationException.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 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 const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg"; + } } public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); @@ -195,18 +283,30 @@ namespace SixLabors.ImageSharp.Tests 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 RLEInverted = "Bmp/RunLengthEncoded-inverted.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 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"; @@ -217,19 +317,38 @@ namespace SixLabors.ImageSharp.Tests 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"; - // Bitmap images with compression type BITFIELDS + // 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 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 static readonly string[] BitFields - = { + public static readonly string[] BitFields = + { Rgb32bfdef, Rgb32bf, Rgb16565, @@ -238,8 +357,15 @@ namespace SixLabors.ImageSharp.Tests Issue735, }; - public static readonly string[] All - = { + public static readonly string[] Miscellaneous = + { + Car, + F, + NegHeight + }; + + public static readonly string[] Benchmark = + { Car, F, NegHeight, @@ -247,7 +373,7 @@ namespace SixLabors.ImageSharp.Tests V5Header, RLE4, RLE8, - RLEInverted, + RLE8Inverted, Bit1, Bit1Pal1, Bit4, @@ -264,11 +390,21 @@ namespace SixLabors.ImageSharp.Tests 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"; + + // 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 { @@ -279,5 +415,80 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; } + + 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"; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 872a935ffe..b2f390dcdf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; using System.Numerics; -using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Tests { @@ -16,20 +15,20 @@ namespace SixLabors.ImageSharp.Tests IEqualityComparer, IEqualityComparer { - private readonly float Epsilon; + private readonly float epsilon; /// /// Initializes a new instance of the class. /// /// The comparison error difference epsilon to use. - public ApproximateFloatComparer(float epsilon = 1F) => this.Epsilon = epsilon; + public ApproximateFloatComparer(float epsilon = 1F) => this.epsilon = epsilon; /// public bool Equals(float x, float y) { float d = x - y; - return d >= -this.Epsilon && d <= this.Epsilon; + return d >= -this.epsilon && d <= this.epsilon; } /// @@ -61,4 +60,4 @@ namespace SixLabors.ImageSharp.Tests /// 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 e35cbfa422..e8a07a76a2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Linq; @@ -11,17 +11,18 @@ 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 arrays to concatenate. The order is kept /// The concatenated array - public static T[] Concat(params T[][] arrs) + public static T[] Concat(params T[][] arrays) { - var result = new T[arrs.Sum(t => t.Length)]; + var result = new T[arrays.Sum(t => t.Length)]; int offset = 0; - for (int i = 0; i < arrs.Length; i++) + for (int i = 0; i < arrays.Length; i++) { - arrs[i].CopyTo(result, offset); - offset += arrs[i].Length; + arrays[i].CopyTo(result, offset); + offset += arrays[i].Length; } + return result; } @@ -39,6 +40,7 @@ namespace SixLabors.ImageSharp.Tests { result[i] = value; } + return result; } @@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests /// The filled string public static string Fill(char value, int length) { - return "".PadRight(length, value); + return string.Empty.PadRight(length, value); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs new file mode 100644 index 0000000000..6a05ce0bcc --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +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 b2967058c0..4e7ead62ec 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Tests +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests { using System; @@ -14,4 +17,4 @@ 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 6107154d0e..0cf76a3890 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -18,12 +18,16 @@ namespace SixLabors.ImageSharp.Tests 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. /// - /// - /// - /// protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters) { this.PixelTypes = pixelTypes; @@ -32,12 +36,12 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Gets the member name + /// Gets the member name. /// public string MemberName { get; } /// - /// Gets the member type + /// Gets or sets the member type. /// public Type MemberType { get; set; } @@ -72,8 +76,8 @@ namespace SixLabors.ImageSharp.Tests addedRows = new[] { new object[0] }; } - bool firstIsprovider = this.FirstIsProvider(testMethod); - if (firstIsprovider) + bool firstIsProvider = this.FirstIsProvider(testMethod); + if (firstIsProvider) { return this.InnerGetData(testMethod, addedRows); } @@ -84,8 +88,7 @@ namespace SixLabors.ImageSharp.Tests /// /// 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(); @@ -98,12 +101,12 @@ namespace SixLabors.ImageSharp.Tests { Type factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value); - foreach (object[] originalFacoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType)) + foreach (object[] originalFactoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType)) { foreach (object[] row in memberData) { - object[] actualFactoryMethodArgs = new object[originalFacoryMethodArgs.Length + 2]; - Array.Copy(originalFacoryMethodArgs, actualFactoryMethodArgs, originalFacoryMethodArgs.Length); + object[] actualFactoryMethodArgs = new object[originalFactoryMethodArgs.Length + 2]; + Array.Copy(originalFactoryMethodArgs, actualFactoryMethodArgs, originalFactoryMethodArgs.Length); actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; @@ -153,6 +156,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the field accessor for the given type. /// + /// The field accessor. protected Func GetFieldAccessor(Type type, string memberName) { FieldInfo fieldInfo = null; @@ -160,11 +164,15 @@ namespace SixLabors.ImageSharp.Tests { fieldInfo = reflectionType.GetRuntimeField(memberName); if (fieldInfo != null) + { break; + } } - if (fieldInfo == null || !fieldInfo.IsStatic) + if (fieldInfo is null || !fieldInfo.IsStatic) + { return null; + } return () => fieldInfo.GetValue(null); } @@ -172,6 +180,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the property accessor for the given type. /// + /// The property accessor. protected Func GetPropertyAccessor(Type type, string memberName) { PropertyInfo propInfo = null; @@ -184,7 +193,7 @@ namespace SixLabors.ImageSharp.Tests } } - if (propInfo?.GetMethod == null || !propInfo.GetMethod.IsStatic) + if (propInfo?.GetMethod is null || !propInfo.GetMethod.IsStatic) { return null; } @@ -192,4 +201,4 @@ namespace SixLabors.ImageSharp.Tests 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 new file mode 100644 index 0000000000..03113e133e --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; + +namespace SixLabors.ImageSharp.Tests +{ + public class WithBasicTestPatternImagesAttribute : ImageDataAttributeBase + { + 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 }; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs deleted file mode 100644 index 796cba8554..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 new file mode 100644 index 0000000000..0fa1099330 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// 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 }; + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs index e896c18546..6c79b9541d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs index 1702513898..92556024dd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs index cdf5fcb089..8af058dc7e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,8 +9,8 @@ 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 + /// 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 { @@ -18,11 +18,11 @@ 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 + /// 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 + /// 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) { @@ -31,21 +31,9 @@ namespace SixLabors.ImageSharp.Tests protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { - MethodInfo m = testMethod.DeclaringType.GetMethod(this.memberMethodName); - - 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 }; + return new object[] { testMethod.DeclaringType.FullName, this.memberMethodName }; } 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 f95db45f71..9dbfdce561 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests /// /// The width of the requested image /// The height of the requested image - /// The referenced color name (name of property in + /// The referenced color name (name of property in ). /// The requested pixel types /// Additional theory parameter values public WithSolidFilledImagesAttribute( @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests /// 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 referenced color name (name of property in ). /// The requested pixel types /// Additional theory parameter values public WithSolidFilledImagesAttribute( @@ -141,22 +141,22 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Red + /// Gets the red component. /// public byte R { get; } /// - /// Green + /// Gets the green component. /// public byte G { get; } /// - /// Blue + /// Gets the blue component. /// public byte B { get; } /// - /// Alpha + /// Gets the alpha component. /// public byte A { get; } @@ -165,4 +165,4 @@ namespace SixLabors.ImageSharp.Tests 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 deleted file mode 100644 index 7c659c64fc..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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 new file mode 100644 index 0000000000..e7da40aa3d --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// 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 + { + /// + /// 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) => new object[] { this.Width, this.Height }; + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs new file mode 100644 index 0000000000..c004436687 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +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 = new Dictionary(); + + public const char Separator = ':'; + + private string DumpToString(Type type) + { + using var ms = new MemoryStream(); + using var writer = new StreamWriter(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 System.Convert.ToBase64String(data); + } + + private Type LoadDump(string dump) + { + byte[] data = System.Convert.FromBase64String(dump); + + using var ms = new MemoryStream(data); + using var reader = new StreamReader(ms); + var 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) + { + var serializer = new BasicSerializer(); + serializable.Serialize(serializer); + return serializer.DumpToString(serializable.GetType()); + } + + public static T Deserialize(string dump) + where T : IXunitSerializable + { + var serializer = new BasicSerializer(); + Type type = serializer.LoadDump(dump); + + var 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/GraphicsOptionsComparer.cs b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs new file mode 100644 index 0000000000..aababf434f --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +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/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index 462782ba5e..c6bcef4617 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -1,10 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public class ExactImageComparer : ImageComparer @@ -22,13 +23,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison int width = actual.Width; - // TODO: Comparing through Rgba64 may not be robust enough because of the existance of super high precision pixel types. - + // TODO: Comparing through Rgba64 may not be robust enough because of the existence of super high precision pixel types. var aBuffer = new Rgba64[width]; var bBuffer = new Rgba64[width]; var differences = new List(); - Configuration configuration = expected.Configuration; + Configuration configuration = expected.GetConfiguration(); for (int y = 0; y < actual.Height; y++) { @@ -54,4 +54,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison 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 d000f70938..e1cbf12ac9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; using System.Linq; @@ -21,6 +24,16 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.Append(Environment.NewLine); + // TODO: We should add OSX. + sb.AppendFormat("Test Environment OS : {0}", TestEnvironment.IsWindows ? "Windows" : "Linux"); + sb.Append(Environment.NewLine); + + sb.AppendFormat("Test Environment is CI : {0}", TestEnvironment.RunsOnCI); + sb.Append(Environment.NewLine); + + sb.AppendFormat("Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework); + sb.Append(Environment.NewLine); + int i = 0; foreach (ImageSimilarityReport r in reports) { @@ -29,7 +42,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.Append(Environment.NewLine); i++; } + return sb.ToString(); } } -} \ 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 e8f60ade31..7f4328959c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs @@ -1,20 +1,19 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public class ImageDimensionsMismatchException : ImagesSimilarityException { public ImageDimensionsMismatchException(Size expectedSize, Size actualSize) - : base((string)$"The image dimensions {actualSize} do not match the expected {expectedSize}!") + : base($"The image dimensions {actualSize} do not match the expected {expectedSize}!") { this.ExpectedSize = expectedSize; this.ActualSize = actualSize; } 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 bbdb6b5815..ebfa47c5bd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { using System; @@ -9,4 +12,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { } } -} \ 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 38dada063c..bd7c71f0d4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,8 +6,6 @@ using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public abstract class ImageComparer @@ -18,6 +16,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison /// 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) @@ -28,13 +27,15 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison /// /// Returns Tolerant(imageThresholdInPercents/100) /// + /// A ImageComparer instance. public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0) => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold); public abstract ImageSimilarityReport CompareImagesOrFrames( ImageFrame expected, ImageFrame actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel; + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel; } public static class ImageComparerExtensions @@ -43,7 +44,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { return comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame); } @@ -52,7 +54,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { var result = new List>(); @@ -60,6 +63,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { 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]); @@ -76,7 +80,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { if (expected.Size() != actual.Size()) { @@ -100,8 +105,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison Image expected, Image actual, Rectangle ignoredRegion) - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { if (expected.Size() != actual.Size()) { @@ -139,4 +144,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index f534079769..f6a2330341 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -63,6 +66,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.AppendLine(); sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); } + int max = Math.Min(5, this.Differences.Length); for (int i = 0; i < max; i++) @@ -73,17 +77,19 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.AppendFormat(";{0}", Environment.NewLine); } } + if (this.Differences.Length >= 5) { sb.Append("..."); } + return sb.ToString(); } } public class ImageSimilarityReport : ImageSimilarityReport - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { public ImageSimilarityReport( ImageFrame expectedImage, @@ -101,4 +107,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison 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 1ffeb60ad4..ba60633ac8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs @@ -1,5 +1,7 @@ -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { @@ -20,7 +22,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } public PixelDifference(Point position, Rgba64 expected, Rgba64 actual) - : this(position, + : this( + position, actual.R - expected.R, actual.G - expected.G, actual.B - expected.B, @@ -31,11 +34,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison 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 be12f56211..e87a83e4f9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -1,12 +1,13 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public class TolerantImageComparer : ImageComparer @@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison /// 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. + /// 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)); @@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison public float ImageThreshold { get; } /// - /// Gets the threshold of the individual pixels before they acumulate towards the overall difference. + /// 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 @@ -66,15 +67,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison int width = actual.Width; - // TODO: Comparing through Rgba64 may not robust enough because of the existance of super high precision pixel types. - + // TODO: Comparing through Rgba64 may not robust enough because of the existence of super high precision pixel types. var aBuffer = new Rgba64[width]; var bBuffer = new Rgba64[width]; float totalDifference = 0F; var differences = new List(); - Configuration configuration = expected.Configuration; + Configuration configuration = expected.GetConfiguration(); for (int y = 0; y < actual.Height; y++) { @@ -120,4 +120,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison [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 new file mode 100644 index 0000000000..409dea1c56 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +using SixLabors.ImageSharp.Advanced; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests +{ + public abstract partial class TestImageProvider : IXunitSerializable + { + public virtual TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + { + throw new NotSupportedException("GetExpectedBasicTestPatternPixelAt(x,y) only works with BasicTestPattern"); + } + + 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(); + + // Transparent purple: + private static readonly TPixel BottomRightColor = GetBottomRightColor(); + + public BasicTestPatternProvider(int width, int height) + : base(width, height) + { + } + + // This parameterless constructor is needed for xUnit deserialization + public BasicTestPatternProvider() + { + } + + public override string SourceFileOrDescription => TestUtils.AsInvariantString($"BasicTestPattern{this.Width}x{this.Height}"); + + public override Image GetImage() + { + var result = new Image(this.Configuration, this.Width, this.Height); + + int midY = this.Height / 2; + int midX = this.Width / 2; + + for (int y = 0; y < midY; y++) + { + Span row = result.GetPixelRowSpan(y); + + row.Slice(0, midX).Fill(TopLeftColor); + row.Slice(midX, this.Width - midX).Fill(TopRightColor); + } + + for (int y = midY; y < this.Height; y++) + { + Span row = result.GetPixelRowSpan(y); + + 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; + } + else + { + return x < midX ? BottomLeftColor : BottomRightColor; + } + } + + private static TPixel GetBottomRightColor() + { + TPixel bottomRightColor = default; + bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); + return bottomRightColor; + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index 7821d0b51a..5663934c26 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -1,16 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class BlankProvider : TestImageProvider, IXunitSerializable { @@ -20,6 +18,9 @@ namespace SixLabors.ImageSharp.Tests this.Height = height; } + /// + /// This parameterless constructor is needed for xUnit deserialization + /// public BlankProvider() { this.Width = 100; @@ -32,8 +33,7 @@ namespace SixLabors.ImageSharp.Tests protected int Width { get; private set; } - public override Image GetImage() => new Image(this.Width, this.Height); - + public override Image GetImage() => new Image(this.Configuration, this.Width, this.Height); public override void Deserialize(IXunitSerializationInfo info) { @@ -50,4 +50,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ 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 3ed696c472..440baaa63b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -1,11 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Reflection; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -13,8 +14,8 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class FileProvider : TestImageProvider, IXunitSerializable { @@ -22,14 +23,18 @@ namespace SixLabors.ImageSharp.Tests // are shared between PixelTypes.Color & PixelTypes.Rgba32 private class Key : IEquatable { - private Tuple commonValues; + private readonly Tuple commonValues; - private Dictionary decoderParameters; + private readonly Dictionary decoderParameters; - public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) + public Key(PixelTypes pixelType, string filePath, int allocatorBufferCapacity, IImageDecoder customDecoder) { Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple(pixelType, filePath, customType); + this.commonValues = new Tuple( + pixelType, + filePath, + customType, + allocatorBufferCapacity); this.decoderParameters = GetDecoderParameters(customDecoder); } @@ -48,8 +53,10 @@ namespace SixLabors.ImageSharp.Tests object value = p.GetValue(customDecoder); data[key] = value; } + type = type.GetTypeInfo().BaseType; } + return data; } @@ -81,11 +88,13 @@ namespace SixLabors.ImageSharp.Tests { return false; } + if (!object.Equals(kv.Value, otherVal)) { return false; } } + return true; } @@ -116,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests public static bool operator !=(Key left, Key right) => !Equals(left, right); } - private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> Cache = new ConcurrentDictionary>(); // Needed for deserialization! // ReSharper disable once UnusedMember.Local @@ -148,11 +157,21 @@ namespace SixLabors.ImageSharp.Tests return this.LoadImage(decoder); } - var key = new Key(this.PixelType, this.FilePath, decoder); + int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); + var key = new Key(this.PixelType, this.FilePath, bufferCapacity, decoder); - Image cachedImage = cache.GetOrAdd(key, _ => this.LoadImage(decoder)); + Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); + + return cachedImage.Clone(this.Configuration); + } + + public override Task> GetImageAsync(IImageDecoder decoder) + { + Guard.NotNull(decoder, nameof(decoder)); - return cachedImage.Clone(); + // Used in small subset of decoder tests, no caching. + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); + return Image.LoadAsync(this.Configuration, path, decoder); } public override void Deserialize(IXunitSerializationInfo info) @@ -181,4 +200,4 @@ namespace SixLabors.ImageSharp.Tests 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 new file mode 100644 index 0000000000..7f1a481ee3 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 deleted file mode 100644 index 5bd53a4c0c..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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 new file mode 100644 index 0000000000..68840d988c --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +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() + { + var 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 d68c37a768..3b496d5e64 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -1,20 +1,19 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. - +using SixLabors.ImageSharp.Advanced; +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 - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class SolidProvider : BlankProvider { @@ -35,6 +34,9 @@ namespace SixLabors.ImageSharp.Tests this.a = a; } + /// + /// This parameterless constructor is needed for xUnit deserialization + /// public SolidProvider() : base() { @@ -50,10 +52,9 @@ namespace SixLabors.ImageSharp.Tests public override Image GetImage() { Image image = base.GetImage(); - TPixel color = default(TPixel); - color.FromRgba32(new Rgba32(this.r, this.g, this.b, this.a)); + Color color = new Rgba32(this.r, this.g, this.b, this.a); - image.Mutate(x => x.Fill(color)); + image.GetRootFramePixelBuffer().FastMemoryGroup.Fill(color.ToPixel()); return image; } @@ -76,4 +77,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ 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 5b5e4740a3..700c40b726 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -1,8 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Reflection; +using System.Threading.Tasks; using Castle.Core.Internal; using SixLabors.ImageSharp.Formats; @@ -13,37 +14,37 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public interface ITestImageProvider - { - PixelTypes PixelType { get; } - ImagingTestCaseUtility Utility { get; } - string SourceFileOrDescription { get; } - - Configuration Configuration { get; set; } - } - /// /// Provides instances for parametric unit tests. /// - /// The pixel format of the image - public abstract partial class TestImageProvider : ITestImageProvider - where TPixel : struct, IPixel + /// The pixel format of the image. + public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable + where TPixel : unmanaged, IPixel { public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); - public virtual string SourceFileOrDescription => ""; + public virtual string SourceFileOrDescription => string.Empty; public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); /// - /// Utility instance to provide informations about the test image & manage input/output + /// Gets the utility instance to provide information 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, @@ -67,10 +68,11 @@ namespace SixLabors.ImageSharp.Tests } public static TestImageProvider Lambda( - Func> factoryFunc, + string declaringTypeName, + string methodName, MethodInfo testMethod = null, PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new LambdaProvider(factoryFunc).Init(testMethod, pixelTypeOverride); + => new MemberMethodProvider(declaringTypeName, methodName).Init(testMethod, pixelTypeOverride); public static TestImageProvider Solid( int width, @@ -88,6 +90,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns an instance to the test case with the necessary traits. /// + /// A test image. public abstract Image GetImage(); public virtual Image GetImage(IImageDecoder decoder) @@ -95,12 +98,18 @@ namespace SixLabors.ImageSharp.Tests throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); } + public virtual Task> GetImageAsync(IImageDecoder decoder) + { + 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 Image GetImage(Action> operationsToApply) + /// A test image. + public Image GetImage(Action operationsToApply) { - Image img = GetImage(); + Image img = this.GetImage(); img.Mutate(operationsToApply); return img; } @@ -126,16 +135,17 @@ namespace SixLabors.ImageSharp.Tests protected TestImageProvider Init( string typeName, string methodName, - string outputSubfolerName, + string outputSubfolderName, PixelTypes pixelTypeOverride) { if (pixelTypeOverride != PixelTypes.Undefined) { this.PixelType = pixelTypeOverride; } + this.TypeName = typeName; this.MethodName = methodName; - this.OutputSubfolderName = outputSubfolerName; + this.OutputSubfolderName = outputSubfolderName; this.Utility = new ImagingTestCaseUtility { @@ -145,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests if (methodName != null) { - this.Utility.Init(typeName, methodName, outputSubfolerName); + this.Utility.Init(typeName, methodName, outputSubfolderName); } return this; @@ -160,8 +170,7 @@ namespace SixLabors.ImageSharp.Tests public override string ToString() { - string provName = this.GetType().Name.Replace("Provider", ""); return $"{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 17e5369d48..f186ed318a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -7,26 +7,41 @@ using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { /// /// A test image provider that produces test patterns. /// private class TestPatternProvider : BlankProvider { - static readonly Dictionary> TestImages = new Dictionary>(); + private static readonly Dictionary> TestImages = new Dictionary>(); + + 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 + /// public TestPatternProvider() - : base() { } @@ -38,19 +53,19 @@ namespace SixLabors.ImageSharp.Tests { if (!TestImages.ContainsKey(this.SourceFileOrDescription)) { - Image image = new Image(this.Width, this.Height); + var image = new Image(this.Width, this.Height); DrawTestPattern(image); TestImages.Add(this.SourceFileOrDescription, image); } - } - return TestImages[this.SourceFileOrDescription].Clone(); + return TestImages[this.SourceFileOrDescription].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 rdaw on. private static void DrawTestPattern(Image image) { // first lets split the image into 4 quadrants @@ -64,7 +79,6 @@ namespace SixLabors.ImageSharp.Tests /// /// Fills the top right quadrant with alternating solid vertical bars. /// - /// private static void VerticalBars(Buffer2D pixels) { // topLeft @@ -78,12 +92,6 @@ namespace SixLabors.ImageSharp.Tests stride = 1; } - TPixel[] c = - { - NamedColors.HotPink, - NamedColors.Blue - }; - for (int y = top; y < bottom; y++) { int p = 0; @@ -92,9 +100,10 @@ namespace SixLabors.ImageSharp.Tests if (x % stride == 0) { p++; - p = p % c.Length; + p = p % PinkBluePixels.Length; } - pixels[x, y] = c[p]; + + pixels[x, y] = PinkBluePixels[p]; } } } @@ -102,7 +111,6 @@ namespace SixLabors.ImageSharp.Tests /// /// fills the top left quadrant with a black and white checker board. /// - /// private static void BlackWhiteChecker(Buffer2D pixels) { // topLeft @@ -111,38 +119,35 @@ namespace SixLabors.ImageSharp.Tests int top = 0; int bottom = pixels.Height / 2; int stride = pixels.Width / 6; - TPixel[] c = - { - NamedColors.Black, - NamedColors.White - }; int p = 0; for (int y = top; y < bottom; y++) { - if (y % stride == 0) + if (y % stride is 0) { p++; - p = p % c.Length; + p = p % BlackWhitePixels.Length; } + int pstart = p; for (int x = left; x < right; x++) { - if (x % stride == 0) + if (x % stride is 0) { p++; - p = p % c.Length; + p = p % BlackWhitePixels.Length; } - pixels[x, y] = c[p]; + + pixels[x, y] = BlackWhitePixels[p]; } + p = pstart; } } /// - /// Fills the bottom left quadrent with 3 horizental bars in Red, Green and Blue with a alpha gradient from left (transparent) to right (solid). + /// Fills the bottom left quadrant with 3 horizontal bars in Red, Green and Blue with a alpha gradient from left (transparent) to right (solid). /// - /// private static void TransparentGradients(Buffer2D pixels) { // topLeft @@ -152,11 +157,11 @@ namespace SixLabors.ImageSharp.Tests int bottom = pixels.Height; int height = (int)Math.Ceiling(pixels.Height / 6f); - 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 + var red = Color.Red.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + var green = Color.Green.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + var blue = Color.Blue.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - TPixel c = default(TPixel); + var c = default(TPixel); for (int x = left; x < right; x++) { @@ -168,12 +173,14 @@ namespace SixLabors.ImageSharp.Tests { 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++) @@ -184,10 +191,9 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Fills the bottom right quadrant with all the colors producable by converting itterating over a uint and unpacking it. + /// 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 /// - /// private static void Rainbow(Buffer2D pixels) { int left = pixels.Width / 2; @@ -198,15 +204,16 @@ namespace SixLabors.ImageSharp.Tests int pixelCount = left * top; uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount); TPixel c = default; - Rgba32 t = new Rgba32(0); + var t = new Rgba32(0); for (int x = left; x < right; x++) { for (int y = top; y < bottom; y++) { t.PackedValue += stepsPerPixel; - Vector4 v = t.ToVector4(); - //v.W = (x - left) / (float)left; + var v = t.ToVector4(); + + // v.W = (x - left) / (float)left; c.FromVector4(v); pixels[x, y] = c; } @@ -214,4 +221,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index c91ef56a1a..fcde6273f5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -19,25 +19,26 @@ namespace SixLabors.ImageSharp.Tests public class ImagingTestCaseUtility { /// - /// Name of the TPixel in the owner + /// Gets or sets the name of the TPixel in the owner /// public string PixelTypeName { get; set; } = string.Empty; /// - /// The name of the file which is provided by + /// 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. /// public string SourceFileOrDescription { get; set; } = string.Empty; /// - /// By default this is the name of the test class, but it's possible to change it + /// 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; public string OutputSubfolderName { get; set; } = string.Empty; /// - /// The name of the test case (by default) + /// Gets or sets the name of the test case (by default). /// public string TestName { get; set; } = string.Empty; @@ -54,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests string fn = appendSourceFileOrDescription ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) - : ""; + : string.Empty; if (string.IsNullOrWhiteSpace(extension)) { @@ -65,6 +66,7 @@ namespace SixLabors.ImageSharp.Tests { extension = ".bmp"; } + extension = extension.ToLower(); if (extension[0] != '.') @@ -77,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests fn = '_' + fn; } - string pixName = ""; + string pixName = string.Empty; if (appendPixelTypeToFileName) { @@ -137,8 +139,7 @@ namespace SixLabors.ImageSharp.Tests detailsString = string.Join( "_", properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) - .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}")) - ); + .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}"))); } } @@ -152,21 +153,20 @@ namespace SixLabors.ImageSharp.Tests /// /// 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 + /// 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. - /// Additional information to append to the test output file name - public string SaveTestOutputFile( - Image image, + /// 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) - where TPixel : struct, IPixel { string path = this.GetTestOutputFileName( extension, @@ -191,7 +191,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) { - string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); + string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); if (!Directory.Exists(baseDir)) { @@ -211,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests IImageEncoder encoder = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); @@ -243,8 +243,7 @@ namespace SixLabors.ImageSharp.Tests bool appendSourceFileOrDescription) { return TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription) - ); + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); } public string[] GetReferenceOutputFileNamesMultiFrame( @@ -277,10 +276,10 @@ namespace SixLabors.ImageSharp.Tests } public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) - where TPixel : struct, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, 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 + where TPixel : unmanaged, IPixel { TPixel pixel = img[x, y]; Rgba64 rgbaPixel = default; @@ -327,4 +326,4 @@ namespace SixLabors.ImageSharp.Tests img[x, y] = pixel; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs index 6d06ec5e93..d00dc67df5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs +++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Tests public class MeasureFixture { /// - /// Value indicating whether priniting is enabled. + /// Value indicating whether printing is enabled. /// - protected bool EnablePrinting = true; + protected bool enablePrinting = true; /// /// Measures and prints the execution time of an , executed multiple times. @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests /// The name of the operation to print to the output public void Measure(int times, Action action, [CallerMemberName] string operationName = null) { - if (this.EnablePrinting) + if (this.enablePrinting) { this.Output?.WriteLine($"{operationName} X {times} ..."); } @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests } sw.Stop(); - if (this.EnablePrinting) + if (this.enablePrinting) { this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); } @@ -79,4 +79,4 @@ namespace SixLabors.ImageSharp.Tests this.Output.WriteLine($"{this.operation} completed in {this.stopwatch.ElapsedMilliseconds}ms"); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index a051e577db..eb840231c0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,9 +12,10 @@ namespace SixLabors.ImageSharp.Tests [Flags] public enum PixelTypes { +#pragma warning disable SA1602 // Enumeration items should be documented Undefined = 0, - Alpha8 = 1 << 0, + A8 = 1 << 0, Argb32 = 1 << 1, @@ -58,9 +59,21 @@ namespace SixLabors.ImageSharp.Tests Rgb48 = 1 << 21, + Bgra5551 = 1 << 22, + + L8 = 1 << 23, + + L16 = 1 << 24, + + La16 = 1 << 25, + + La32 = 1 << 26, + // 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 + +#pragma warning restore SA1602 // Enumeration items should be documented } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 3dd330e4d3..de8278a33e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -1,14 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; using System.Runtime.InteropServices; - +using System.Threading; +using System.Threading.Tasks; using ImageMagick; - -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs @@ -17,45 +17,73 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static MagickReferenceDecoder Instance { get; } = new MagickReferenceDecoder(); + private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba32Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 4); + } + } + + private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba64Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 8); + } + } + + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => Task.FromResult(this.Decode(configuration, stream)); + public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var magickImage = new MagickImage(stream)) + using var magickImage = new MagickImage(stream); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + MemoryGroup resultPixels = result.GetRootFramePixelBuffer().FastMemoryGroup; + + using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) { - var result = new Image(configuration, magickImage.Width, magickImage.Height); - Span resultPixels = result.GetPixelSpan(); + if (magickImage.Depth == 8) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) + FromRgba32Bytes(configuration, data, resultPixels); + } + else if (magickImage.Depth == 16) { - 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, resultPixels); + } + else + { + throw new InvalidOperationException(); } - - return result; } + + return result; } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index 7e87c23db5..6d6e7bd769 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -24,12 +24,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs /// The input bitmap. /// Thrown if the image pixel format is not of type internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int w = bmp.Width; int h = bmp.Height; - var fullRect = new Rectangle(0, 0, w, h); + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); if (bmp.PixelFormat != PixelFormat.Format32bppArgb) { @@ -39,32 +39,39 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); - byte* sourcePtrBase = (byte*)data.Scan0; + var image = new Image(w, h); + try + { + byte* sourcePtrBase = (byte*)data.Scan0; - long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Bgra32); + long sourceRowByteCount = data.Stride; + long destRowByteCount = w * sizeof(Bgra32); - var image = new Image(w, h); - Configuration configuration = image.GetConfiguration(); + Configuration configuration = image.GetConfiguration(); - using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) - { - fixed (Bgra32* destPtr = &workBuffer.GetReference()) + using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) { - for (int y = 0; y < h; y++) + fixed (Bgra32* destPtr = &workBuffer.GetReference()) { - 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); + 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; } @@ -76,12 +83,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs /// The input bitmap. /// Thrown if the image pixel format is not of type internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int w = bmp.Width; int h = bmp.Height; - var fullRect = new Rectangle(0, 0, w, h); + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); if (bmp.PixelFormat != PixelFormat.Format24bppRgb) { @@ -91,69 +98,78 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); - byte* sourcePtrBase = (byte*)data.Scan0; + var image = new Image(w, h); + try + { + byte* sourcePtrBase = (byte*)data.Scan0; - long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Bgr24); + long sourceRowByteCount = data.Stride; + long destRowByteCount = w * sizeof(Bgr24); - var image = new Image(w, h); - Configuration configuration = image.GetConfiguration(); + Configuration configuration = image.GetConfiguration(); - using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) - { - fixed (Bgr24* destPtr = &workBuffer.GetReference()) + using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) { - for (int y = 0; y < h; y++) + fixed (Bgr24* destPtr = &workBuffer.GetReference()) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(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; } internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { 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); + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); - byte* destPtrBase = (byte*)data.Scan0; + try + { + byte* destPtrBase = (byte*)data.Scan0; - long destRowByteCount = data.Stride; - long sourceRowByteCount = w * sizeof(Bgra32); + long destRowByteCount = data.Stride; + long sourceRowByteCount = w * sizeof(Bgra32); - using (IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w)) - { - fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) + using (IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w)) { - for (int y = 0; y < h; y++) + fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) { - 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); + 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); + } } } } - - resultBitmap.UnlockBits(data); + finally + { + resultBitmap.UnlockBits(data); + } 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 427a565424..1eb1328ef2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -1,10 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; - +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs @@ -13,8 +15,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => Task.FromResult(this.Decode(configuration, stream)); + public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) { @@ -43,13 +49,21 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => Task.FromResult(this.Identify(configuration, stream)); + public IImageInfo Identify(Configuration configuration, Stream stream) { 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 new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); } } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, 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 46dae17a11..4c28079c1c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs @@ -1,9 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; using System.IO; - +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -23,12 +24,23 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public static SystemDrawingReferenceEncoder Bmp { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Bmp); public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) { sdBitmap.Save(stream, this.imageFormat); } } + + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + 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 new file mode 100644 index 0000000000..f03d2c4938 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +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/TestDataGenerator.cs b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs index e3d8bf3806..859a834ec6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -21,16 +21,21 @@ namespace SixLabors.ImageSharp.Tests /// The . public static float[] GenerateRandomFloatArray(this Random rnd, int length, float minVal, float maxVal) { - float[] values = new float[length]; + var values = new float[length]; - for (int i = 0; i < length; i++) - { - values[i] = GetRandomFloat(rnd, minVal, maxVal); - } + RandomFill(rnd, values, minVal, maxVal); return values; } + public static void RandomFill(this Random rnd, Span destination, float minVal, float 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. /// @@ -65,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests /// The . public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, float minVal, float maxVal) { - float[] values = new float[length]; + var values = new float[length]; for (int i = 0; i < length; i++) { @@ -83,14 +88,14 @@ namespace SixLabors.ImageSharp.Tests /// The . public static byte[] GenerateRandomByteArray(this Random rnd, int length) { - byte[] values = new byte[length]; + var 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]; + var values = new short[length]; for (int i = 0; i < values.Length; i++) { values[i] = (short)rnd.Next(minVal, maxVal); @@ -101,4 +106,4 @@ namespace SixLabors.ImageSharp.Tests 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.Features.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Features.cs new file mode 100644 index 0000000000..3568c1e5dc --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Features.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests +{ + public static partial class TestEnvironment + { + internal static class Features + { + public const string On = "1"; + public 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` + public const string EnableAES = "COMPlus_EnableAES"; + public const string EnableAVX = "COMPlus_EnableAVX"; + public const string EnableAVX2 = "COMPlus_EnableAVX2"; + public const string EnableBMI1 = "COMPlus_EnableBMI1"; + public const string EnableBMI2 = "COMPlus_EnableBMI2"; + public const string EnableFMA = "COMPlus_EnableFMA"; + public const string EnableHWIntrinsic = "COMPlus_EnableHWIntrinsic"; + public const string EnableLZCNT = "COMPlus_EnableLZCNT"; + public const string EnablePCLMULQDQ = "COMPlus_EnablePCLMULQDQ"; + public const string EnablePOPCNT = "COMPlus_EnablePOPCNT"; + public const string EnableSSE = "COMPlus_EnableSSE"; + public const string EnableSSE2 = "COMPlus_EnableSSE2"; + public const string EnableSSE3 = "COMPlus_EnableSSE3"; + public const string EnableSSE3_4 = "COMPlus_EnableSSE3_4"; + public const string EnableSSE41 = "COMPlus_EnableSSE41"; + public const string EnableSSE42 = "COMPlus_EnableSSE42"; + public const string EnableSSSE3 = "COMPlus_EnableSSSE3"; + public const string FeatureSIMD = "COMPlus_FeatureSIMD"; + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 7d06847223..6e204e2d48 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests @@ -53,8 +54,8 @@ namespace SixLabors.ImageSharp.Tests { var cfg = new Configuration( new JpegConfigurationModule(), - new GifConfigurationModule() - ); + new GifConfigurationModule(), + new TgaConfigurationModule()); // Magick codecs should work on all platforms IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new PngEncoder(); @@ -75,4 +76,4 @@ namespace SixLabors.ImageSharp.Tests 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 a5a3e332c7..1375b5763e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -1,11 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests { @@ -32,12 +34,18 @@ namespace SixLabors.ImageSharp.Tests private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); + static TestEnvironment() + { + PrepareRemoteExecutor(); + } + /// /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. /// internal static string NetCoreVersion => NetCoreVersionLazy.Value; // ReSharper disable once InconsistentNaming + /// /// Gets a value indicating whether test execution runs on CI. /// @@ -45,13 +53,12 @@ namespace SixLabors.ImageSharp.Tests internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; + private static readonly FileInfo TestAssemblyFile = + new FileInfo(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); + private static string GetSolutionDirectoryFullPathImpl() { - string assemblyLocation = typeof(TestEnvironment).GetTypeInfo().Assembly.Location; - - var assemblyFile = new FileInfo(assemblyLocation); - - DirectoryInfo directory = assemblyFile.Directory; + DirectoryInfo directory = TestAssemblyFile.Directory; while (!directory.EnumerateFiles(ImageSharpSolutionFileName).Any()) { @@ -62,20 +69,20 @@ namespace SixLabors.ImageSharp.Tests catch (Exception ex) { throw new Exception( - $"Unable to find ImageSharp solution directory from {assemblyLocation} because of {ex.GetType().Name}!", + $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", ex); } if (directory == null) { - throw new Exception($"Unable to find ImageSharp solution directory from {assemblyLocation}!"); + throw new Exception($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); } } return directory.FullName; } - private static string GetFullPath(string relativePath) => + private static string GetFullPath(string relativePath) => Path.Combine(SolutionDirectoryFullPath, relativePath) .Replace('\\', Path.DirectorySeparatorChar); @@ -83,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests /// 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.) /// @@ -100,13 +107,22 @@ namespace SixLabors.ImageSharp.Tests 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; + internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); + + /// + /// A dummy operation to enforce the execution of the static constructor. + /// + internal static void EnsureSharedInitializersDone() + { + } + /// /// Creates the image output directory. /// @@ -132,6 +148,109 @@ namespace SixLabors.ImageSharp.Tests return path; } + /// + /// 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) + { + return; + } + + string remoteExecutorConfigPath = + Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config"); + + if (File.Exists(remoteExecutorConfigPath)) + { + // Already initialized + return; + } + + string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; + File.Copy(testProjectConfigPath, remoteExecutorConfigPath); + + 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"); + + FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); + + string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); + + string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; + + if (File.Exists(remoteExecutorTmpPath)) + { + // Already initialized + return; + } + + File.Copy(remoteExecutorPath, remoteExecutorTmpPath); + + string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; + + var si = new ProcessStartInfo() + { + FileName = corFlagsFile.FullName, + Arguments = args, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using var 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}"); + } + + File.Delete(remoteExecutorPath); + File.Copy(remoteExecutorTmpPath, remoteExecutorPath); + + static FileInfo Find(DirectoryInfo root, string name) + { + FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); + if (fi != null) + { + return fi; + } + + foreach (DirectoryInfo dir in root.EnumerateDirectories()) + { + fi = Find(dir, name); + if (fi != null) + { + return fi; + } + } + + return null; + } + } + /// /// Solution borrowed from: /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 @@ -142,8 +261,11 @@ namespace SixLabors.ImageSharp.Tests 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 string.Empty; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 29d39596b7..073db1efed 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -1,17 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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.TestUtilities.ImageComparison; using Xunit; @@ -21,75 +21,48 @@ namespace SixLabors.ImageSharp.Tests public static class TestImageExtensions { /// - /// TODO: This should be a common processing method! The image.Opacity(val) multiplies the alpha channel! + /// TODO: Consider adding this private processor to the library /// - /// - /// - public static void MakeOpaque(this IImageProcessingContext ctx) - where TPixel : struct, IPixel - { - MemoryAllocator memoryAllocator = ctx.MemoryAllocator; - - 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.ToScaledVector4(configuration, pixelSpan, tempSpan); + public static void MakeOpaque(this IImageProcessingContext ctx) => + ctx.ApplyProcessor(new MakeOpaqueProcessor()); - for (int i = 0; i < tempSpan.Length; i++) - { - ref Vector4 v = ref tempSpan[i]; - v.W = 1F; - } - - PixelOperations.Instance.FromScaledVector4(configuration, tempSpan, pixelSpan); - } - } - }); - } - - public static Image DebugSave( - this Image image, + public static void DebugSave( + this Image image, ITestImageProvider provider, FormattableString testOutputDetails, string extension = "png", bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + bool appendSourceFileOrDescription = true, + IImageEncoder encoder = null) { - return image.DebugSave( + image.DebugSave( provider, (object)testOutputDetails, extension, appendPixelTypeToFileName, - appendSourceFileOrDescription); + appendSourceFileOrDescription, + encoder); } /// /// Saves the image only when not running in the CI server. /// - /// The pixel format - /// The image - /// The image provider + /// The image. + /// The image provider. /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension + /// 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, + /// 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) - where TPixel : struct, IPixel + bool appendSourceFileOrDescription = true, + IImageEncoder encoder = null) { if (TestEnvironment.RunsOnCI) { @@ -102,41 +75,39 @@ namespace SixLabors.ImageSharp.Tests extension, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); + appendSourceFileOrDescription: appendSourceFileOrDescription, + encoder: encoder); return image; } - public static Image DebugSave( - this Image image, + public static void DebugSave( + this Image image, ITestImageProvider provider, IImageEncoder encoder, FormattableString testOutputDetails, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel { - return image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); + image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); } /// /// 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, + public static void DebugSave( + this Image image, ITestImageProvider provider, IImageEncoder encoder, object testOutputDetails = null, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel { if (TestEnvironment.RunsOnCI) { - return image; + return; } // We are running locally then we want to save it out @@ -145,7 +116,6 @@ namespace SixLabors.ImageSharp.Tests encoder: encoder, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); - return image; } public static Image DebugSaveMultiFrame( @@ -154,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (TestEnvironment.RunsOnCI) { @@ -178,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareToReferenceOutput( provider, @@ -193,15 +163,15 @@ namespace SixLabors.ImageSharp.Tests /// 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 + /// 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, @@ -210,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return CompareToReferenceOutput( image, @@ -231,7 +201,7 @@ namespace SixLabors.ImageSharp.Tests string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareToReferenceOutput( comparer, @@ -246,16 +216,17 @@ namespace SixLabors.ImageSharp.Tests /// 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 + /// 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, @@ -264,15 +235,17 @@ namespace SixLabors.ImageSharp.Tests string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + bool appendSourceFileOrDescription = true, + IImageDecoder decoder = null) + where TPixel : unmanaged, IPixel { using (Image referenceImage = GetReferenceOutputImage( provider, testOutputDetails, extension, appendPixelTypeToFileName, - appendSourceFileOrDescription)) + appendSourceFileOrDescription, + decoder)) { comparer.VerifySimilarity(referenceImage, image); } @@ -289,7 +262,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareFirstFrameToReferenceOutput( comparer, @@ -310,7 +283,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) using (Image referenceImage = GetReferenceOutputImage( @@ -337,7 +310,7 @@ namespace SixLabors.ImageSharp.Tests string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image referenceImage = GetReferenceOutputImageMultiFrame( provider, @@ -352,12 +325,14 @@ namespace SixLabors.ImageSharp.Tests return image; } - public static Image GetReferenceOutputImage(this ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + 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, @@ -367,20 +342,21 @@ namespace SixLabors.ImageSharp.Tests if (!File.Exists(referenceOutputFile)) { - throw new System.IO.FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); + throw new FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(referenceOutputFile); + decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); return Image.Load(referenceOutputFile, decoder); } - public static Image GetReferenceOutputImageMultiFrame(this ITestImageProvider provider, - int frameCount, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + public static Image GetReferenceOutputImageMultiFrame( + this ITestImageProvider provider, + int frameCount, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel { string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( frameCount, @@ -413,7 +389,7 @@ namespace SixLabors.ImageSharp.Tests fi.Dispose(); } - // remove the initial empty frame: + // Remove the initial empty frame: result.Frames.RemoveFrame(0); return result; } @@ -425,7 +401,7 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image referenceImage = provider.GetReferenceOutputImage( testOutputDetails, @@ -439,10 +415,9 @@ namespace SixLabors.ImageSharp.Tests public static Image ComparePixelBufferTo( this Image image, Span expectedPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Span actualPixels = image.GetPixelSpan(); - + Assert.True(image.TryGetSinglePixelSpan(out Span actualPixels)); CompareBuffers(expectedPixels, actualPixels); return image; @@ -465,24 +440,44 @@ namespace SixLabors.ImageSharp.Tests /// /// 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 : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (ImageFrame imageFrame in image.Frames) { imageFrame.ComparePixelBufferTo(expectedPixel); } - + + return image; + } + + /// + /// 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; } /// /// 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 : struct, IPixel + where TPixel : unmanaged, IPixel { - Span actualPixels = imageFrame.GetPixelSpan(); + Assert.True(imageFrame.TryGetSinglePixelSpan(out Span actualPixels)); for (int i = 0; i < actualPixels.Length; i++) { @@ -491,14 +486,13 @@ namespace SixLabors.ImageSharp.Tests return imageFrame; } - + public static ImageFrame ComparePixelBufferTo( this ImageFrame image, Span expectedPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Span actual = image.GetPixelSpan(); - + Assert.True(image.TryGetSinglePixelSpan(out Span actual)); Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); for (int i = 0; i < expectedPixels.Length; i++) @@ -513,7 +507,7 @@ namespace SixLabors.ImageSharp.Tests this Image image, ITestImageProvider provider, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder); } @@ -523,7 +517,7 @@ namespace SixLabors.ImageSharp.Tests ITestImageProvider provider, ImageComparer comparer, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestImageProvider.GetFilePathOrNull(provider); if (path == null) @@ -534,7 +528,7 @@ namespace SixLabors.ImageSharp.Tests var testFile = TestFile.Create(path); referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(path); - + using (var original = Image.Load(testFile.Bytes, referenceDecoder)) { comparer.VerifySimilarity(original, image); @@ -547,7 +541,7 @@ namespace SixLabors.ImageSharp.Tests /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) /// 2. Executing DebugSave() - /// 3. Executing CopareToReferenceOutput() + /// 3. Executing CompareToReferenceOutput() /// internal static void VerifyOperation( this TestImageProvider provider, @@ -556,7 +550,7 @@ namespace SixLabors.ImageSharp.Tests FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -568,7 +562,8 @@ namespace SixLabors.ImageSharp.Tests appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - image.CompareToReferenceOutput(comparer, + image.CompareToReferenceOutput( + comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, @@ -580,7 +575,7 @@ namespace SixLabors.ImageSharp.Tests /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) /// 2. Executing DebugSave() - /// 3. Executing CopareToReferenceOutput() + /// 3. Executing CompareToReferenceOutput() /// internal static void VerifyOperation( this TestImageProvider provider, @@ -588,7 +583,7 @@ namespace SixLabors.ImageSharp.Tests FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation( ImageComparer.Tolerant(), @@ -602,7 +597,7 @@ namespace SixLabors.ImageSharp.Tests /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) /// 2. Executing DebugSave() - /// 3. Executing CopareToReferenceOutput() + /// 3. Executing CompareToReferenceOutput() /// internal static void VerifyOperation( this TestImageProvider provider, @@ -610,7 +605,7 @@ namespace SixLabors.ImageSharp.Tests Action> operation, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation( comparer, @@ -624,14 +619,14 @@ namespace SixLabors.ImageSharp.Tests /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) /// 2. Executing DebugSave() - /// 3. Executing CopareToReferenceOutput() + /// 3. Executing CompareToReferenceOutput() /// internal static void VerifyOperation( this TestImageProvider provider, Action> operation, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); } @@ -650,7 +645,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true, string referenceImageExtension = null, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string actualOutputFile = provider.Utility.SaveTestOutputFile( image, @@ -659,22 +654,29 @@ namespace SixLabors.ImageSharp.Tests testOutputDetails, appendPixelTypeToFileName); - referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(actualOutputFile); + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) + using (var encodedImage = Image.Load(actualOutputFile, referenceDecoder)) { ImageComparer comparer = customComparer ?? ImageComparer.Exact; - comparer.VerifySimilarity(actualImage, image); + comparer.VerifySimilarity(encodedImage, image); } } + internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( + this TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var allocator = (ArrayPoolMemoryAllocator)provider.Configuration.MemoryAllocator; + return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); + } + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) { var image = new Image(buffer.Width, buffer.Height); - Span pixels = image.Frames.RootFrame.GetPixelSpan(); - - Span bufferSpan = buffer.GetSpan(); + Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span pixels)); + Span bufferSpan = buffer.GetSingleSpan(); for (int i = 0; i < bufferSpan.Length; i++) { @@ -686,5 +688,89 @@ namespace SixLabors.ImageSharp.Tests return image; } + private class MakeOpaqueProcessor : IImageProcessor + { + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new MakeOpaqueProcessor(configuration, source, sourceRectangle); + } + + private class MakeOpaqueProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + { + public MakeOpaqueProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + } + + protected override void OnFrameApply(ImageFrame source) + { + Rectangle sourceRectangle = this.SourceRectangle; + Configuration configuration = this.Configuration; + + var operation = new RowOperation(configuration, sourceRectangle, source); + + ParallelRowIterator.IterateRowIntervals( + configuration, + sourceRectangle, + in operation); + } + + private readonly struct RowOperation : IRowIntervalOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly ImageFrame source; + + public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame source) + { + this.configuration = configuration; + this.bounds = bounds; + this.source = source; + } + + public void Invoke(in RowInterval rows, Span span) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span rowSpan = this.source.GetPixelRowSpan(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); + } + } + } + } + } + + internal class AllocatorBufferCapacityConfigurator + { + private readonly ArrayPoolMemoryAllocator allocator; + private readonly int pixelSizeInBytes; + + public AllocatorBufferCapacityConfigurator(ArrayPoolMemoryAllocator allocator, int pixelSizeInBytes) + { + this.allocator = allocator; + this.pixelSizeInBytes = pixelSizeInBytes; + } + + public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; + + public void InPixels(int totalPixels) => this.InBytes(totalPixels * this.pixelSizeInBytes); + + /// + /// Set the maximum buffer capacity to bytesSqrt^2 bytes. + /// + public void InBytesSqrt(int bytesSqrt) => this.InBytes(bytesSqrt * bytesSqrt); + + /// + /// 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 dc755e6827..ab9611d2fb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -1,40 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Buffers; +using System.Collections.Generic; +using System.Numerics; using System.Runtime.InteropServices; -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Memory { internal class TestMemoryAllocator : MemoryAllocator { + private readonly List allocationLog = new List(); + private readonly List returnLog = new List(); + public TestMemoryAllocator(byte dirtyValue = 42) { this.DirtyValue = dirtyValue; } /// - /// The value to initilazie the result buffer with, with non-clean options () + /// 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; + + public IReadOnlyList ReturnLog => this.returnLog; + + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { T[] array = this.AllocateArray(length, options); - - return new BasicArrayBuffer(array, length); + return new BasicArrayBuffer(array, length, this); } public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) { byte[] array = this.AllocateArray(length, options); - return new ManagedByteBuffer(array); + return new ManagedByteBuffer(array, this); } - + private T[] AllocateArray(int length, AllocationOptions options) where T : struct { var array = new T[length + 42]; + this.allocationLog.Add(AllocationRequest.Create(options, length, array)); if (options == AllocationOptions.None) { @@ -45,42 +61,84 @@ namespace SixLabors.ImageSharp.Tests.Memory 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) + { + this.ElementType = elementType; + this.AllocationOptions = allocationOptions; + this.Length = length; + this.LengthInBytes = lengthInBytes; + this.HashCodeOfBuffer = hashCodeOfBuffer; + + if (elementType == typeof(Vector4)) + { + } + } + + 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()); + } + + 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) + { + this.HashCodeOfBuffer = hashCodeOfBuffer; + } + + public int HashCodeOfBuffer { get; } + } + /// /// Wraps an array as an instance. /// private class BasicArrayBuffer : MemoryManager where T : struct { + private readonly TestMemoryAllocator allocator; private GCHandle pinHandle; - /// - /// Initializes a new instance of the class - /// - /// The array - /// The length of the buffer - public BasicArrayBuffer(T[] array, int length) + public BasicArrayBuffer(T[] array, int length, TestMemoryAllocator allocator) { + this.allocator = allocator; 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) + public BasicArrayBuffer(T[] array, TestMemoryAllocator allocator) + : this(array, array.Length, allocator) { } /// - /// Gets the array + /// Gets the array. /// public T[] Array { get; } /// - /// Gets the length + /// Gets the length. /// public int Length { get; } @@ -106,15 +164,19 @@ namespace SixLabors.ImageSharp.Tests.Memory /// protected override void Dispose(bool disposing) { + if (disposing) + { + this.allocator.Return(this); + } } } private class ManagedByteBuffer : BasicArrayBuffer, IManagedByteBuffer { - public ManagedByteBuffer(byte[] array) - : base(array) + public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) + : base(array, allocator) { } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs index 9274e5727c..20078564a0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs @@ -1,9 +1,12 @@ -using System; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Buffers; namespace SixLabors.ImageSharp.Tests { - class TestMemoryManager : MemoryManager + public class TestMemoryManager : MemoryManager where T : struct { public TestMemoryManager(T[] pixelArray) @@ -43,4 +46,4 @@ namespace SixLabors.ImageSharp.Tests 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 e998ccd3dc..818876065d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; -using System.Text; + using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; @@ -11,7 +10,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.TestUtilities { public class TestPixel : IXunitSerializable - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public TestPixel() { @@ -26,25 +25,23 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities } public float Red { get; set; } + public float Green { get; set; } + public float Blue { get; set; } - public float Alpha { 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); + var pix = default(TPixel); pix.FromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha)); return pix; } internal Span AsSpan() { - return new Span(new[] { AsPixel() }); + return new Span(new[] { this.AsPixel() }); } public void Deserialize(IXunitSerializationInfo info) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestType.cs b/tests/ImageSharp.Tests/TestUtilities/TestType.cs index 852aaf2d43..f131d7a0ff 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestType.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestType.cs @@ -1,10 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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 diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index e51aa28d8f..39ebf7f159 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,13 +6,15 @@ 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.TestUtilities.ImageComparison; -using SixLabors.Primitives; +using Xunit; namespace SixLabors.ImageSharp.Tests { @@ -39,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests ClrTypes2PixelTypes[defaultPixelFormatType] = PixelTypes.Rgba32; // Add PixelFormat types - string nameSpace = typeof(Alpha8).FullName; - nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(Alpha8).Name.Length - 1); + 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)) { string typeName = $"{nameSpace}.{pt}"; @@ -53,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (a.Width != b.Width || a.Height != b.Height) { @@ -105,8 +107,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns the enumerations for the given type. /// - /// - /// + /// The pixel type. public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) @@ -129,6 +130,7 @@ namespace SixLabors.ImageSharp.Tests result[pt] = pt.GetClrType(); } } + return result; } @@ -141,8 +143,37 @@ namespace SixLabors.ImageSharp.Tests /// The pixel types internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes)); + internal static Color GetColorByName(string colorName) + { + var f = (FieldInfo)typeof(Color).GetMember(colorName)[0]; + return (Color)f.GetValue(null); + } + internal static TPixel GetPixelOfNamedColor(string colorName) - where TPixel : struct, IPixel => (TPixel)typeof(NamedColors).GetTypeInfo().GetField(colorName).GetValue(null); + 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); + + var allocator = ArrayPoolMemoryAllocator.CreateDefault(); + 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: @@ -154,16 +185,16 @@ namespace SixLabors.ImageSharp.Tests /// 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, + Action process, object testOutputDetails = null, ImageComparer comparer = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -195,11 +226,11 @@ namespace SixLabors.ImageSharp.Tests internal static void RunValidatingProcessorTest( this TestImageProvider provider, - Func, FormattableString> processAndGetTestOutputDetails, + Func processAndGetTestOutputDetails, ImageComparer comparer = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -209,9 +240,7 @@ namespace SixLabors.ImageSharp.Tests using (Image image = provider.GetImage()) { FormattableString testOutputDetails = $""; - image.Mutate( - ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); } - ); + image.Mutate(ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); }); image.DebugSave( provider, @@ -234,13 +263,13 @@ namespace SixLabors.ImageSharp.Tests public static void RunValidatingProcessorTestOnWrappedMemoryImage( this TestImageProvider provider, - Action> process, + Action process, object testOutputDetails = null, ImageComparer comparer = null, string useReferenceOutputFrom = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -249,7 +278,8 @@ namespace SixLabors.ImageSharp.Tests using (Image image0 = provider.GetImage()) { - var mmg = TestMemoryManager.CreateAsCopyOf(image0.GetPixelSpan()); + Assert.True(image0.TryGetSinglePixelSpan(out Span imageSpan)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageSpan); using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) { @@ -284,14 +314,15 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Same as but with an additional parameter passed to 'process' + /// Same as 'RunValidatingProcessorTest{TPixel}' but with an additional parameter passed to 'process' /// internal static void RunRectangleConstrainedValidatingProcessorTest( this TestImageProvider provider, - Action, Rectangle> process, + Action process, object testOutputDetails = null, - ImageComparer comparer = null) - where TPixel : struct, IPixel + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -302,19 +333,19 @@ namespace SixLabors.ImageSharp.Tests { 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); + image.DebugSave(provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); + image.CompareToReferenceOutput(comparer, provider, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); } } /// - /// Same as but without the 'CompareToReferenceOutput()' step. + /// Same as 'RunValidatingProcessorTest{TPixel}' but without the 'CompareToReferenceOutput()' step. /// internal static void RunProcessorTest( this TestImageProvider provider, - Action> process, + Action process, object testOutputDetails = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -337,6 +368,18 @@ namespace SixLabors.ImageSharp.Tests return (IResampler)property.GetValue(null); } + public static IDither GetDither(string name) + { + PropertyInfo property = typeof(KnownDitherings).GetTypeInfo().GetProperty(name); + + if (property is null) + { + throw new Exception($"No dither named '{name}"); + } + + return (IDither)property.GetValue(null); + } + public static string[] GetAllResamplerNames(bool includeNearestNeighbour = true) { return typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) @@ -345,4 +388,4 @@ namespace SixLabors.ImageSharp.Tests .ToArray(); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs index 990258e0c2..055083ae24 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -21,8 +21,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities } 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) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs new file mode 100644 index 0000000000..28bd1cb44e --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; +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() + { + var obj = new DerivedObj() { 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/GroupOutputTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs index 061d42b0ac..03d0671161 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void OutputSubfolderName_ValueIsTakeFromGroupOutputAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.Equal("Foo", provider.Utility.OutputSubfolderName); } @@ -20,10 +23,10 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void GetTestOutputDir_ShouldDefineSubfolder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, 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 c935a4b982..9983ee3c8b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System.Collections.Generic; using System.Linq; @@ -6,7 +9,6 @@ using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; @@ -27,15 +29,15 @@ namespace SixLabors.ImageSharp.Tests [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0, 0)] public void TolerantImageComparer_ApprovesPerfectSimilarity( TestImageProvider provider, - float imageTheshold, + float imageThreshold, int pixelThreshold) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { using (Image clone = image.Clone()) { - var comparer = ImageComparer.Tolerant(imageTheshold, pixelThreshold); + var comparer = ImageComparer.Tolerant(imageThreshold, pixelThreshold); comparer.VerifySimilarity(image, clone); } } @@ -44,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(110, 110, PixelTypes.Rgba32)] public void TolerantImageComparer_ApprovesSimilarityBelowTolerance(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -61,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -84,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba64)] public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -104,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests [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 + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -121,11 +123,10 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void VerifySimilarity_WhenAnImageFrameIsDifferent_Reports(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -141,11 +142,10 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void ExactComparer_ApprovesExactEquality(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void ExactComparer_DoesNotTolerateAnyPixelDifference(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -179,4 +179,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ 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 b60439b488..6cb78e5419 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { using SixLabors.ImageSharp.PixelFormats; @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [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 + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(testImage); @@ -58,9 +58,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [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)] + [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.L16Bit)] public void MagickDecode_16BitDepthImage_IsApproximatelyEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(testImage); @@ -84,4 +84,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests } } } -} \ 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 033f0866a3..578af884be 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -49,48 +49,49 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkMagickPngDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - this.BenckmarkDecoderImpl(PngBenchmarkFiles, new MagickReferenceDecoder(), $@"Magick Decode Png"); + this.BenchmarkDecoderImpl(PngBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Png"); } [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkSystemDrawingPngDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - this.BenckmarkDecoderImpl(PngBenchmarkFiles, new SystemDrawingReferenceDecoder(), $@"System.Drawing Decode Png"); + this.BenchmarkDecoderImpl(PngBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Png"); } [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkMagickBmpDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - this.BenckmarkDecoderImpl(BmpBenchmarkFiles, new MagickReferenceDecoder(), $@"Magick Decode Bmp"); + this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Bmp"); } [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkSystemDrawingBmpDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - this.BenckmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), $@"System.Drawing Decode Bmp"); + this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Bmp"); } - private void BenckmarkDecoderImpl(IEnumerable testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount) + private void BenchmarkDecoderImpl(IEnumerable testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount) { var measure = new MeasureFixture(this.Output); - measure.Measure(times, + measure.Measure( + times, () => { foreach (string testFile in testFiles) { - Image image = TestFile.Create(testFile).CreateImage(decoder); + Image image = TestFile.Create(testFile).CreateRgba32Image(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 new file mode 100644 index 0000000000..87f8cb8c17 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class SemaphoreReadMemoryStreamTests + { + private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0); + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(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 3cdb67dbdb..5472f1e332 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Png; @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void From32bppArgbSystemDrawingBitmap(TestImageProvider dummyProvider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests } private static string SavePng(TestImageProvider provider, PngColorType pngColorType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image sourceImage = provider.GetImage()) { @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests sourceImage.Mutate(c => c.MakeOpaque()); } - var encoder = new PngEncoder() { ColorType = pngColorType }; + var encoder = new PngEncoder { ColorType = pngColorType }; return provider.Utility.SaveTestOutputFile(sourceImage, "png", encoder); } } @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void From32bppArgbSystemDrawingBitmap2(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (TestEnvironment.IsLinux) { @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgb24)] public void From24bppRgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = SavePng(provider, PngColorType.Rgb); @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void OpenWithReferenceDecoder(TestImageProvider dummyProvider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); using (var image = Image.Load(path, SystemDrawingReferenceDecoder.Instance)) @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Argb32)] public void SaveWithReferenceEncoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -133,4 +133,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests } } } -} \ 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 122234ae89..e72d953ac1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -1,12 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. 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; @@ -16,8 +13,8 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public class TestEnvironmentTests @@ -35,29 +32,6 @@ namespace SixLabors.ImageSharp.Tests Assert.True(Directory.Exists(path)); } - /// - /// 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); - } - } - [Fact] public void SolutionDirectoryFullPath() { @@ -93,7 +67,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) { - if (TestEnvironment.IsLinux) return; + if (TestEnvironment.IsLinux) + { + return; + } IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); Assert.IsType(expectedEncoderType, encoder); @@ -106,7 +83,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) { - if (TestEnvironment.IsLinux) return; + if (TestEnvironment.IsLinux) + { + return; + } IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); Assert.IsType(expectedDecoderType, decoder); @@ -119,7 +99,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) { - if (!TestEnvironment.IsLinux) return; + if (!TestEnvironment.IsLinux) + { + return; + } IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); Assert.IsType(expectedEncoderType, encoder); @@ -132,7 +115,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) { - if (!TestEnvironment.IsLinux) return; + 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 6a1582828a..5637892091 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + using System; using Moq; @@ -15,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -27,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -39,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_DoNotAppendPixelType( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -51,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceFileMissing_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -62,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void CompareToOriginal_WhenSimilar(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -76,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void CompareToOriginal_WhenDifferent_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -92,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] public void CompareToOriginal_WhenInputIsNotFromFile_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -103,4 +106,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ 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 cac7828e96..129d17f4df 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -1,116 +1,137 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Concurrent; using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; + using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public class TestImageProviderTests { + 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; } - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) - where TPixel : struct, IPixel => Assert.Empty(provider.Utility.OutputSubfolderName); + /// + /// 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 Image(3, 3); [Theory] - [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] - public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) - where TPixel : struct, IPixel + [MemberData(nameof(BasicData))] + public void Blank_MemberData(TestImageProvider provider) + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); - Assert.Equal(42, img.Width); - Assert.Equal(666, img.Height); - Assert.Equal("hello", message); + Assert.True(img.Width * img.Height > 0); } [Theory] - [WithBlankImages(42, 666, PixelTypes.All, "hello")] - public void Use_WithBlankImagesAttribute_WithAllPixelTypes( - TestImageProvider provider, - string message) - where TPixel : struct, IPixel + [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(); - Assert.Equal(42, img.Width); - Assert.Equal(666, img.Height); - Assert.Equal("hello", message); + Assert.True(img.Width * img.Height > 0); } [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.Bmp.Car, PixelTypes.All, 88)] - [WithFile(TestImages.Bmp.F, PixelTypes.All, 88)] - public void Use_WithFileAttribute(TestImageProvider provider, int yo) - where TPixel : struct, IPixel + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( + TestImageProvider provider) + where TPixel : unmanaged, IPixel { + if (!TestEnvironment.Is64BitProcess) + { + // We don't cache with the 32 bit build. + return; + } + Assert.NotNull(provider.Utility.SourceFileOrDescription); - Image img = provider.GetImage(); - Assert.True(img.Width * img.Height > 0); - Assert.Equal(88, yo); + TestDecoder.DoTestThreadSafe( + () => + { + string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache); - string fn = provider.Utility.GetTestOutputFileName("jpg"); - this.Output.WriteLine(fn); - } + var decoder = new TestDecoder(); + decoder.InitCaller(testName); - private class TestDecoder : IImageDecoder - { - public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel - { - invocationCounts[this.callerName]++; - return new Image(42, 42); - } + provider.GetImage(decoder); + Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); - // Couldn't make xUnit happy without this hackery: + provider.GetImage(decoder); + Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); + }); + } - private static readonly ConcurrentDictionary invocationCounts = new ConcurrentDictionary(); + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); - private string callerName = null; + TestDecoderWithParameters.DoTestThreadSafe( + () => + { + string testName = nameof(this + .GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); - internal void InitCaller(string name) - { - this.callerName = name; - invocationCounts[name] = 0; - } + var decoder1 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 42 }; + decoder1.InitCaller(testName); - internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + var decoder2 = new TestDecoderWithParameters { Param1 = "LoL", Param2 = 42 }; + decoder2.InitCaller(testName); - private static readonly object Monitor = new object(); + provider.GetImage(decoder1); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - public static void DoTestThreadSafe(Action action) - { - lock (Monitor) - { - action(); - } - } + provider.GetImage(decoder2); + Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); + }); } [Theory] [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache(TestImageProvider provider) - where TPixel : struct, IPixel + public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( + TestImageProvider provider) + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -120,126 +141,127 @@ namespace SixLabors.ImageSharp.Tests Assert.NotNull(provider.Utility.SourceFileOrDescription); - TestDecoder.DoTestThreadSafe(() => - { - string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache); + TestDecoderWithParameters.DoTestThreadSafe( + () => + { + string testName = nameof(this + .GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); - var decoder = new TestDecoder(); - decoder.InitCaller(testName); + var decoder1 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 666 }; + decoder1.InitCaller(testName); - provider.GetImage(decoder); - Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); + var decoder2 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 666 }; + decoder2.InitCaller(testName); - provider.GetImage(decoder); - Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); - }); - } - - private class TestDecoderWithParameters : IImageDecoder - { - public string Param1 { get; set; } - - public int Param2 { get; set; } + provider.GetImage(decoder1); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel - { - invocationCounts[this.callerName]++; - return new Image(42, 42); - } + provider.GetImage(decoder2); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + }); + } - private static readonly ConcurrentDictionary invocationCounts = new ConcurrentDictionary(); + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + Assert.Empty(provider.Utility.OutputSubfolderName); - private string callerName = null; + [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); - internal void InitCaller(string name) + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void SaveTestOutputFileMultiFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - this.callerName = name; - invocationCounts[name] = 0; - } - - internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; - - private static readonly object Monitor = new object(); + string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); - public static void DoTestThreadSafe(Action action) - { - lock (Monitor) + Assert.True(files.Length > 2); + foreach (string path in files) { - action(); + this.Output.WriteLine(path); + Assert.True(File.Exists(path)); } } } [Theory] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual(TestImageProvider provider) - where TPixel : struct, IPixel + [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 { - if (!TestEnvironment.Is64BitProcess) + using (Image img = provider.GetImage()) { - // We don't cache with the 32 bit build. - return; + img.DebugSave(provider); } + } - Assert.NotNull(provider.Utility.SourceFileOrDescription); - - TestDecoderWithParameters.DoTestThreadSafe(() => - { - string testName = - nameof(this.GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); - - var decoder1 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 666 }; - decoder1.InitCaller(testName); + [Theory] + [WithBlankImages(42, 666, PixelTypes.All, "hello")] + public void Use_WithBlankImagesAttribute_WithAllPixelTypes( + TestImageProvider provider, + string message) + where TPixel : unmanaged, IPixel + { + Image img = provider.GetImage(); - var decoder2 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 666 }; - decoder2.InitCaller(testName); + Assert.Equal(42, img.Width); + Assert.Equal(666, img.Height); + Assert.Equal("hello", message); + } - provider.GetImage(decoder1); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + [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(); - provider.GetImage(decoder2); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - }); + Assert.Equal(42, img.Width); + Assert.Equal(666, img.Height); + Assert.Equal("hello", message); } [Theory] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual(TestImageProvider provider) - where TPixel : struct, IPixel + [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); - - TestDecoderWithParameters.DoTestThreadSafe(() => + using (Image img = provider.GetImage()) { - string testName = - nameof(this.GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); - - var decoder1 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 42 }; - decoder1.InitCaller(testName); + Assert.True(img.Width * img.Height > 0); - var decoder2 = new TestDecoderWithParameters() { Param1 = "LoL", Param2 = 42 }; - decoder2.InitCaller(testName); + Assert.Equal(123, yo); - provider.GetImage(decoder1); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - - provider.GetImage(decoder2); - Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); - }); + string fn = provider.Utility.GetTestOutputFileName("jpg"); + this.Output.WriteLine(fn); + } } - - public static string[] AllBmpFiles = - { - TestImages.Bmp.F, - TestImages.Bmp.Bit8 - }; + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] + public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + EnsureCustomConfigurationIsApplied(provider); + } [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] public void Use_WithFileCollection(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); using (Image image = provider.GetImage()) @@ -249,27 +271,22 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void SaveTestOutputFileMultiFrame(TestImageProvider provider) - where TPixel : struct, IPixel + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] + public void Use_WithMemberFactoryAttribute(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + Image img = provider.GetImage(); + Assert.Equal(3, img.Width); + if (provider.PixelType == PixelTypes.Rgba32) { - string[] 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.IsType>(img); } } [Theory] [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Rgba32 | PixelTypes.Argb32)] public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); Assert.Equal(10, img.Width); @@ -291,75 +308,147 @@ namespace SixLabors.ImageSharp.Tests } } - /// - /// Need to us to create instance of when pixelType is StandardImageClass - /// - /// - /// - /// - public static Image CreateTestImage() - where TPixel : struct, IPixel => new Image(3, 3); - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] - public void Use_WithMemberFactoryAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] + public void Use_WithTestPatternImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - Image img = provider.GetImage(); - Assert.Equal(3, img.Width); - if (provider.PixelType == PixelTypes.Rgba32) + using (Image img = provider.GetImage()) { - Assert.IsType>(img); + img.DebugSave(provider); } - } [Theory] - [WithTestPatternImages(49,20, PixelTypes.Rgba32)] - public void Use_WithTestPatternImages(TestImageProvider provider) - where TPixel : struct, IPixel + [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] + public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image img = provider.GetImage()) + EnsureCustomConfigurationIsApplied(provider); + } + + private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (provider.GetImage()) { - img.DebugSave(provider); + var customConfiguration = Configuration.CreateDefaultInstance(); + provider.Configuration = customConfiguration; + + using (Image image2 = provider.GetImage()) + using (Image image3 = provider.GetImage()) + { + Assert.Same(customConfiguration, image2.GetConfiguration()); + Assert.Same(customConfiguration, image3.GetConfiguration()); + } } } - public static readonly TheoryData BasicData = new TheoryData() + private class TestDecoder : IImageDecoder { - TestImageProvider.Blank(10, 20), - TestImageProvider.Blank( - 10, - 20), - }; + // Couldn't make xUnit happy without this hackery: + private static readonly ConcurrentDictionary InvocationCounts = + new ConcurrentDictionary(); - [Theory] - [MemberData(nameof(BasicData))] - public void Blank_MemberData(TestImageProvider provider) - where TPixel : struct, IPixel - { - Image img = provider.GetImage(); + private static readonly ConcurrentDictionary InvocationCountsAsync = + new ConcurrentDictionary(); - Assert.True(img.Width * img.Height > 0); + private static readonly object Monitor = new object(); + + private string callerName; + + public static void DoTestThreadSafe(Action action) + { + lock (Monitor) + { + action(); + } + } + + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + InvocationCounts[this.callerName]++; + return new Image(42, 42); + } + + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + InvocationCountsAsync[this.callerName]++; + return Task.FromResult(new Image(42, 42)); + } + + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; + + internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName]; + + internal void InitCaller(string name) + { + this.callerName = name; + InvocationCounts[name] = 0; + InvocationCountsAsync[name] = 0; + } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); } - public static readonly TheoryData FileData = new TheoryData() + private class TestDecoderWithParameters : IImageDecoder { - TestImageProvider.File(TestImages.Bmp.Car), - TestImageProvider.File(TestImages.Bmp.F) - }; + private static readonly ConcurrentDictionary InvocationCounts = + new ConcurrentDictionary(); - [Theory] - [MemberData(nameof(FileData))] - public void File_MemberData(TestImageProvider provider) - where TPixel : struct, IPixel - { - this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); - this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); + private static readonly ConcurrentDictionary InvocationCountsAsync = + new ConcurrentDictionary(); - Image img = provider.GetImage(); + private static readonly object Monitor = new object(); - Assert.True(img.Width * img.Height > 0); + private string callerName; + + public string Param1 { get; set; } + + public int Param2 { get; set; } + + public static void DoTestThreadSafe(Action action) + { + lock (Monitor) + { + action(); + } + } + + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + InvocationCounts[this.callerName]++; + return new Image(42, 42); + } + + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + InvocationCountsAsync[this.callerName]++; + return Task.FromResult(new Image(42, 42)); + } + + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; + + internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName]; + + internal void InitCaller(string name) + { + this.callerName = name; + InvocationCounts[name] = 0; + InvocationCountsAsync[name] = 0; + } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 301d0cebe6..c8a2c6c4cb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests private ITestOutputHelper Output { get; } public static Image CreateTestImage() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var image = new Image(10, 10); @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests [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 + where TPixel : unmanaged, IPixel { Image a = provider.GetImage(); Image b = provider.GetImage(x => x.OilPaint(3, 2)); @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests [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 + where TPixel : unmanaged, IPixel { Image a = provider.GetImage(); Image b = provider.GetImage(); @@ -93,19 +93,18 @@ namespace SixLabors.ImageSharp.Tests IEnumerable> pixelTypesExp) { Assert.Contains(new KeyValuePair(pt, typeof(T)), pixelTypesExp); - } [Fact] public void ExpandAllTypes_1() { - PixelTypes pixelTypes = PixelTypes.Alpha8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; + PixelTypes pixelTypes = PixelTypes.A8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; IEnumerable> expanded = pixelTypes.ExpandAllTypes(); Assert.Equal(4, expanded.Count()); - AssertContainsPixelType(PixelTypes.Alpha8, expanded); + AssertContainsPixelType(PixelTypes.A8, expanded); AssertContainsPixelType(PixelTypes.Bgr565, expanded); AssertContainsPixelType(PixelTypes.HalfVector2, expanded); AssertContainsPixelType(PixelTypes.Rgba32, expanded); diff --git a/tests/ImageSharp.Tests/VectorAssert.cs b/tests/ImageSharp.Tests/VectorAssert.cs index 402d066555..144681af76 100644 --- a/tests/ImageSharp.Tests/VectorAssert.cs +++ b/tests/ImageSharp.Tests/VectorAssert.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors. // 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; @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests public static class VectorAssert { public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Equal(expected.ToVector4(), actual.ToVector4(), precision); } @@ -48,25 +48,23 @@ namespace SixLabors.ImageSharp.Tests public bool Equals(Vector2 x, Vector2 y) { - return Equals(x.X, y.X) && - Equals(x.Y, y.Y); - + return this.Equals(x.X, y.X) && + this.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); - + 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 Equals(x.W, y.W) && - Equals(x.X, y.X) && - Equals(x.Y, y.Y) && - Equals(x.Z, y.Z); - + 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) @@ -78,10 +76,12 @@ namespace SixLabors.ImageSharp.Tests { return obj.GetHashCode(); } + public int GetHashCode(Vector3 obj) { return obj.GetHashCode(); } + public int GetHashCode(Vector2 obj) { return obj.GetHashCode(); diff --git a/tests/ImageSharp.Tests/xunit.runner.json b/tests/ImageSharp.Tests/xunit.runner.json index 5204242f03..749ece4387 100644 --- a/tests/ImageSharp.Tests/xunit.runner.json +++ b/tests/ImageSharp.Tests/xunit.runner.json @@ -1,5 +1,5 @@ { - "shadowCopy": false, - "methodDisplay": "method", - "diagnosticMessages": true -} \ No newline at end of file + "shadowCopy": false, + "methodDisplay": "method", + "diagnosticMessages": true +} diff --git a/tests/Images/External b/tests/Images/External index 74995302e3..6a00308067 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 74995302e3c9a5913f1cf09d71b15f888d0ec022 +Subproject commit 6a003080674d1fedc66292c13ce5a357b2a33083 diff --git a/tests/Images/Input/Bmp/9S.BMP b/tests/Images/Input/Bmp/9S.BMP new file mode 100644 index 0000000000..f6f937901c --- /dev/null +++ b/tests/Images/Input/Bmp/9S.BMP @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b54244f4ef0968568e0d8255707763f6c2027238ff66a22580ae12a019c33a9f +size 2684 diff --git a/tests/Images/Input/Bmp/DIAMOND.BMP b/tests/Images/Input/Bmp/DIAMOND.BMP new file mode 100644 index 0000000000..512b156358 --- /dev/null +++ b/tests/Images/Input/Bmp/DIAMOND.BMP @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a7133850f670732c39cb343615f4443c934272002edfa5373a7286c1967e2cc +size 2684 diff --git a/tests/Images/Input/Bmp/GMARBLE.BMP b/tests/Images/Input/Bmp/GMARBLE.BMP new file mode 100644 index 0000000000..52a0fb7f0f --- /dev/null +++ b/tests/Images/Input/Bmp/GMARBLE.BMP @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..1bd55e4b19 --- /dev/null +++ b/tests/Images/Input/Bmp/PINES.BMP @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6b6aa7b3f34cf370f7e34912fe5079b8e72cb5475722d658867d1a164957672 +size 61377 diff --git a/tests/Images/Input/Bmp/SKATER.BMP b/tests/Images/Input/Bmp/SKATER.BMP new file mode 100644 index 0000000000..0ba7113bee --- /dev/null +++ b/tests/Images/Input/Bmp/SKATER.BMP @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..e61e5e0c76 --- /dev/null +++ b/tests/Images/Input/Bmp/SPADE.BMP @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..08fb3070e2 --- /dev/null +++ b/tests/Images/Input/Bmp/SUNFLOW.BMP @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..db9128bf37 --- /dev/null +++ b/tests/Images/Input/Bmp/WARPD.BMP @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..a787229ac2 --- /dev/null +++ b/tests/Images/Input/Bmp/ba-bm.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ec70510334952d3fbeae51a9a49d4e50e5afc292a1f9232970a7cf22b1a18fc +size 9000 diff --git a/tests/Images/Input/Bmp/invalidPaletteSize.bmp b/tests/Images/Input/Bmp/invalidPaletteSize.bmp new file mode 100644 index 0000000000..8a4b231166 --- /dev/null +++ b/tests/Images/Input/Bmp/invalidPaletteSize.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eaea78be2e8e5579e2469400b8d811b125be805459888c3bd390570d21ffeab8 +size 9270 diff --git a/tests/Images/Input/Bmp/pal4rlecut.bmp b/tests/Images/Input/Bmp/pal4rlecut.bmp new file mode 100644 index 0000000000..f67004039b --- /dev/null +++ b/tests/Images/Input/Bmp/pal4rlecut.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..674abdaff8 --- /dev/null +++ b/tests/Images/Input/Bmp/pal4rletrns.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fcbaa0f387c57ba678ced91dfb2db5b2e544e2ed28a7875459e551b514daf84 +size 4326 diff --git a/tests/Images/Input/Bmp/pal8gs.bmp b/tests/Images/Input/Bmp/pal8gs.bmp new file mode 100644 index 0000000000..359499c7a5 --- /dev/null +++ b/tests/Images/Input/Bmp/pal8gs.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..8734a5b1c3 --- /dev/null +++ b/tests/Images/Input/Bmp/pal8offs.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15ad578b6a6253334616b9e7e3a958f827b885eeffac1e3cf7e5247f96aca342 +size 9354 diff --git a/tests/Images/Input/Bmp/pal8os2sp.bmp b/tests/Images/Input/Bmp/pal8os2sp.bmp new file mode 100644 index 0000000000..7d3e89d84c --- /dev/null +++ b/tests/Images/Input/Bmp/pal8os2sp.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e58eb53dcbf1c0920ad5af8d07f576ee794c1c5d051e1d950060ab6f6f253f50 +size 8974 diff --git a/tests/Images/Input/Bmp/pal8oversizepal.bmp b/tests/Images/Input/Bmp/pal8oversizepal.bmp new file mode 100644 index 0000000000..e80319df9a --- /dev/null +++ b/tests/Images/Input/Bmp/pal8oversizepal.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b3e46321cc --- /dev/null +++ b/tests/Images/Input/Bmp/pal8rlecut.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..22f5629186 --- /dev/null +++ b/tests/Images/Input/Bmp/pal8rletrns.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:445b856331c5d03054887d6555f56a8882f0aabbe0d59e322d9ff0be7f0eee94 +size 9212 diff --git a/tests/Images/Input/Bmp/rgb16.bmp b/tests/Images/Input/Bmp/rgb16.bmp new file mode 100644 index 0000000000..fde234fbb5 --- /dev/null +++ b/tests/Images/Input/Bmp/rgb16.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcecd482048c853d08255430c5bac462821522683f4429626442580058aa5e5e +size 16438 diff --git a/tests/Images/Input/Bmp/rgb24jpeg.bmp b/tests/Images/Input/Bmp/rgb24jpeg.bmp new file mode 100644 index 0000000000..6f5ae3b56c --- /dev/null +++ b/tests/Images/Input/Bmp/rgb24jpeg.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..ac46d03da8 --- /dev/null +++ b/tests/Images/Input/Bmp/rgb24largepal.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..40969196e7 --- /dev/null +++ b/tests/Images/Input/Bmp/rgb24png.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..0e0731dd54 --- /dev/null +++ b/tests/Images/Input/Bmp/rgb24rle24.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:351d358824671a79dc63147a78fc555d46cbee357661674e80c898e133e0b5c5 +size 21432 diff --git a/tests/Images/Input/Bmp/rgb32h52.bmp b/tests/Images/Input/Bmp/rgb32h52.bmp new file mode 100644 index 0000000000..114c03fbd7 --- /dev/null +++ b/tests/Images/Input/Bmp/rgb32h52.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9baf23d23b75ad2a641decce0e5b59640b3359ff327eecfa1b888595a6b502d6 +size 32578 diff --git a/tests/Images/Input/Bmp/rgba32abf.bmp b/tests/Images/Input/Bmp/rgba32abf.bmp new file mode 100644 index 0000000000..0f4c4b76ec --- /dev/null +++ b/tests/Images/Input/Bmp/rgba32abf.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d2183466e05c768b5a9d2ce5d132d72db2272bb331c7b27560d24f197f4165e +size 32582 diff --git a/tests/Images/Input/Bmp/rgba32v4.bmp b/tests/Images/Input/Bmp/rgba32v4.bmp new file mode 100644 index 0000000000..6f9bf07bf8 --- /dev/null +++ b/tests/Images/Input/Bmp/rgba32v4.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..137d38647a --- /dev/null +++ b/tests/Images/Input/Bmp/rle24rlecut.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..bc5dc14a96 --- /dev/null +++ b/tests/Images/Input/Bmp/rle24rletrns.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..a52aad3d89 --- /dev/null +++ b/tests/Images/Input/Bmp/rle4-delta-320x240.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..d1d4496073 --- /dev/null +++ b/tests/Images/Input/Bmp/rle8-blank-160x120.bmp @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..ff8ee7a303 --- /dev/null +++ b/tests/Images/Input/Bmp/rle8-delta-320x240.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:522a154731ea38e4ee29b4f27672b7d881bcfb1f0954fae126e79a27a63d5f06 +size 4646 diff --git a/tests/Images/Input/Gif/GlobalQuantizationTest.gif b/tests/Images/Input/Gif/GlobalQuantizationTest.gif new file mode 100644 index 0000000000..8fa4e7f99c --- /dev/null +++ b/tests/Images/Input/Gif/GlobalQuantizationTest.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c67df4b08561c14054ed911e6cfc99f1fc726b32e2c7a5e2dbb8392e24109b5a +size 101868 diff --git a/tests/Images/Input/Gif/image-zero-height.gif b/tests/Images/Input/Gif/image-zero-height.gif new file mode 100644 index 0000000000..f4f70ab6a4 --- /dev/null +++ b/tests/Images/Input/Gif/image-zero-height.gif @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..c2bccffc49 --- /dev/null +++ b/tests/Images/Input/Gif/image-zero-size.gif @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..642be49ad4 --- /dev/null +++ b/tests/Images/Input/Gif/image-zero-width.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cde31fe4bcc863f70f66c5a57d62647b11512920328fc5658399ef566ebebef +size 30 diff --git a/tests/Images/Input/Gif/large_comment.gif b/tests/Images/Input/Gif/large_comment.gif new file mode 100644 index 0000000000..e9ee362e54 --- /dev/null +++ b/tests/Images/Input/Gif/large_comment.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dd294f02004595498918567295a52d1f95243933f7c068ccbcce936a88d1b70 +size 1236 diff --git a/tests/Images/Input/Gif/max-height.gif b/tests/Images/Input/Gif/max-height.gif new file mode 100644 index 0000000000..fcec4bd936 --- /dev/null +++ b/tests/Images/Input/Gif/max-height.gif @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..bb0e131acc --- /dev/null +++ b/tests/Images/Input/Gif/max-width.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:008e2a84afed6c31b6635aa9d8c7ee2176f01a0eb0a04143883a8533d7ca33c9 +size 405 diff --git a/tests/Images/Input/Gif/receipt.gif b/tests/Images/Input/Gif/receipt.gif new file mode 100644 index 0000000000..ce800a8197 --- /dev/null +++ b/tests/Images/Input/Gif/receipt.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bac212852eee73f3c29f30be0be375e5caccbe86e5f4adfaa8c0a7a3673a91ab +size 50686 diff --git a/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg b/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg new file mode 100644 index 0000000000..d16a2864d1 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50f6359d228079ec5e6ead84046119eda84136026c1651c753e6d270405cd4b7 +size 187216 diff --git a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt index 2c03157afe..a557d16c13 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 cb74eb88f5..3ec02b50d9 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/iptc-psAPP13-wIPTCempty.jpg b/tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg new file mode 100644 index 0000000000..9300dced9a --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..adb12621fb --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/iptc.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c8a0747d9282bfd7e8e7f4a0119c53c702bf600384b786ef9b5263457f38ada +size 18611 diff --git a/tests/Images/Input/Jpg/baseline/ycck-subsample-1222.jpg b/tests/Images/Input/Jpg/baseline/ycck-subsample-1222.jpg new file mode 100644 index 0000000000..17c23f1358 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/ycck-subsample-1222.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca7c16a2984d14ebc483780d9d1e8c131eca01d89f53512cc7add538c93a38c6 +size 31527 diff --git a/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF .jpg b/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF.jpg similarity index 100% rename from tests/Images/Input/Jpg/issues/Issue214-CriticalEOF .jpg rename to tests/Images/Input/Jpg/issues/Issue214-CriticalEOF.jpg diff --git a/tests/Images/Input/Jpg/issues/Issue845-Incorrect-Quality99.jpg b/tests/Images/Input/Jpg/issues/Issue845-Incorrect-Quality99.jpg new file mode 100644 index 0000000000..639fce534e --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue845-Incorrect-Quality99.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4917339488d46f25ef9ffba6bcfe42272fb190808da7c24e7a3d3f31e0b68a8c +size 2270501 diff --git a/tests/Images/Input/Jpg/issues/Issue797-InvalidImage.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg similarity index 100% rename from tests/Images/Input/Jpg/issues/Issue797-InvalidImage.jpg rename to tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg diff --git a/tests/Images/Input/Jpg/issues/Issue798-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg similarity index 100% rename from tests/Images/Input/Jpg/issues/Issue798-AccessViolationException.jpg rename to tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg new file mode 100644 index 0000000000..23e95f1238 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44f42f4b5c0491b7e8f5fe4b6a580c99bc5246cfe95238c601c75a7c5926e4b1 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg new file mode 100644 index 0000000000..8a6fd02913 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15107f755765118c8a1afac4a58f2945b3bd503ddabcc5b831219d1a159209d9 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg new file mode 100644 index 0000000000..a404664aeb --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0aa96e68b91b56c4a6536e421e9a87a8388d1f205c3e1c3f7fd8f954a547fb26 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg new file mode 100644 index 0000000000..7df0579b03 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19f76ba0a41511f384118214395f6f0911909b2e2304b7b2caf464e512c59006 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg new file mode 100644 index 0000000000..396cddb599 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b75a87a3beb879c04bfac9ff5b2f11d939ac579d4d5e04fcc912ac95d38101e +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg new file mode 100644 index 0000000000..4bd164ddd6 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ee6b88dad441210f443cad24c6edba460fcc26404fcbbf0c134f9ccb3e92b0d +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg new file mode 100644 index 0000000000..c80d7ba050 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d17827181a497a5e0ca78d41347385f329eaa7fe25e8adb7cd7842ae1a656c8 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg new file mode 100644 index 0000000000..8d01b7427e --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:617a0b15d8ddd07cd8c1d9a2e9fb1d09fa1b4756db7b91c5fbe1de233787ea1a +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg new file mode 100644 index 0000000000..6430317995 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4834f4b8274e32ed44872a5365dd3cedd219b58538af592abf1692698afb3a95 +size 411 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg new file mode 100644 index 0000000000..cc686eee0d --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0d3d7230934adb7f44875cfba7ce1688a10093e322712dc7fb2f2f07dfb93c6 +size 308 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg new file mode 100644 index 0000000000..132c9043f7 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1478c189574a9c5f9492ae1f9ea2093a74255299d0f293d7fe9c41a76be381e3 +size 308 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg new file mode 100644 index 0000000000..845a526350 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c095034c82addf710d5d9127447b894bb052642800c40b803517547c0b316054 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg new file mode 100644 index 0000000000..57dd33e234 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b12df8907f0a9a0a89ac24ff73db43d8e85d47558a20cb186764cc0533bdb214 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg new file mode 100644 index 0000000000..2e595e36ae --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78ca8307aaacd980939be0002210dacbe09e133a8249f0d671e89f45b229abc0 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg new file mode 100644 index 0000000000..7c3db77d85 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6dba5ec29565cbc9a70b0ea70fe03409d73cfef6af9a8519b579bc4947252bf +size 380 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg new file mode 100644 index 0000000000..5766e13d53 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dfd61ccf7df77b9eff15bca98fad4df0eef095c8c8ca690344fc459d9706a50c +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg new file mode 100644 index 0000000000..625ecc61d4 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60a198357053526cbfc0fd9fc90d8690cdc72b9b75e88a926d6e7b7664a89343 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg new file mode 100644 index 0000000000..fa11080385 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38c6415405ac1303d359a0327a3b2517e7bad99df9fd3c0f4cbae979be56f0d7 +size 409 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg new file mode 100644 index 0000000000..177bb082a9 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7282c0dbea030f2e18eef12685dc17fa37b03e0f67cf71d8917106d470b94f +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg new file mode 100644 index 0000000000..b411b970a7 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b37ac1cc34680db9721542ef5354f7509860afd37888a24f6350f29f5b63eea1 +size 397 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg new file mode 100644 index 0000000000..ef43dc2cfe --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..6cc3531a0b --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue-1076-invalid-subsampling.jpg @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..c034c90249 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue-1221-identify-multi-frame.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93f5c637aca626a04471ebc990edf9e8d47896db60618cfb7c5360891071709a +size 2263840 diff --git a/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg b/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg new file mode 100644 index 0000000000..3880b869ed --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b3abb7d2f5 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue1049-exif-resize.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f313e48c5b9d4d1af2df44057970f03ddafc809862d14e8593f3e1fc0aef2c1 +size 718443 diff --git a/tests/Images/Input/Jpg/issues/issue855-incorrect-colorspace.jpg b/tests/Images/Input/Jpg/issues/issue855-incorrect-colorspace.jpg new file mode 100644 index 0000000000..77c0327680 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue855-incorrect-colorspace.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fabf628f032dce427a6a0e0f252404d66dc4ce2cd7ee2d7ec72a1fbe79c625d0 +size 142656 diff --git a/tests/Images/Input/Png/Bradley01.png b/tests/Images/Input/Png/Bradley01.png new file mode 100644 index 0000000000..5af2913e60 --- /dev/null +++ b/tests/Images/Input/Png/Bradley01.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..917bf9310f --- /dev/null +++ b/tests/Images/Input/Png/Bradley02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a73ebf6e35d5336bdf194d5098bcbe0ad240bbd09cd357816aacb1e0e7e6a614 +size 26467 diff --git a/tests/Images/Input/Png/InvalidTextData.png b/tests/Images/Input/Png/InvalidTextData.png new file mode 100644 index 0000000000..6e15cae961 --- /dev/null +++ b/tests/Images/Input/Png/InvalidTextData.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78ce7b4b15fc97d3e5b136510de941af9d807b10ce92320da65eb801712e6440 +size 383 diff --git a/tests/Images/Input/Png/PngWithMetaData.png b/tests/Images/Input/Png/PngWithMetaData.png new file mode 100644 index 0000000000..8db95fa632 --- /dev/null +++ b/tests/Images/Input/Png/PngWithMetaData.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a37d2d31c2148b94bfd732c8964808dcc2dcdb6d2c187bb5d0403dc09af9ab46 +size 60544 diff --git a/tests/Images/Input/Png/basn3p01.png b/tests/Images/Input/Png/basn3p01.png new file mode 100644 index 0000000000..15673642fa --- /dev/null +++ b/tests/Images/Input/Png/basn3p01.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..1065847eff --- /dev/null +++ b/tests/Images/Input/Png/basn3p02.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..05e361b1e5 --- /dev/null +++ b/tests/Images/Input/Png/basn3p04.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..68cb909bfb --- /dev/null +++ b/tests/Images/Input/Png/basn3p08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d58256cd2eb16b5740d4c1403d25ce43d8dd03e270627ab709d2fb141e3d904c +size 1286 diff --git a/tests/Images/Input/Png/bike-small.png b/tests/Images/Input/Png/bike-small.png new file mode 100644 index 0000000000..46eee03cf6 --- /dev/null +++ b/tests/Images/Input/Png/bike-small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fb48e3c495d7834df09a17d6a6cadbce047a0e791b0cb78ca3a6d334d309b13 +size 75628 diff --git a/tests/Images/Input/Png/david.png b/tests/Images/Input/Png/david.png new file mode 100644 index 0000000000..c1e3b5cd5a --- /dev/null +++ b/tests/Images/Input/Png/david.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7e3b46a2f62251950f8c17f94c9d9a434ae643a98c058679644b5a0c5633b6 +size 27218 diff --git a/tests/Images/Input/Png/iftbbn0g01.png b/tests/Images/Input/Png/iftbbn0g01.png new file mode 100644 index 0000000000..f11869beac --- /dev/null +++ b/tests/Images/Input/Png/iftbbn0g01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1eea6f2448e9d3f4365b34d1f849c057c7199564f18cecad07c3a362926b11db +size 214 diff --git a/tests/Images/Input/Png/iftbbn0g02.png b/tests/Images/Input/Png/iftbbn0g02.png new file mode 100644 index 0000000000..a1e2e9f1e6 --- /dev/null +++ b/tests/Images/Input/Png/iftbbn0g02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82324d48b769628d4d8a4b6e7830c6d7dc778e5656d6e1e7556f4e53737a1dfc +size 211 diff --git a/tests/Images/Input/Png/iftbbn0g04.png b/tests/Images/Input/Png/iftbbn0g04.png new file mode 100644 index 0000000000..64b662e352 --- /dev/null +++ b/tests/Images/Input/Png/iftbbn0g04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47d393150de4cec7152f1ed61ea25bca9de628865e37ad735e7f162c58340916 +size 489 diff --git a/tests/Images/Input/Png/issues/Issue_1014_1.png b/tests/Images/Input/Png/issues/Issue_1014_1.png new file mode 100644 index 0000000000..2bdd826d63 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_1.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..224ee915ae --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_2.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b288f4380e --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_3.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..1fb3dd5397 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_4.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..98a06472cc --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_5.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..77871b29c7 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_6.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..7d5a53a9e5 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1047.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..6101102e5d --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1127.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..2d851e31bf --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1177_1.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..efd043b38c --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1177_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7067af724977e1ecd8fc761f50226eaaa9e9d4142be963b4edbbf0918b8eba1d +size 57125 diff --git a/tests/Images/Input/Png/issues/Issue_410.png b/tests/Images/Input/Png/issues/Issue_410.png new file mode 100644 index 0000000000..1ca3be3eaa --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_410.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe3c66fb0f52b989f7398bc6bcaa18e83625120a53b4972023705a7a5925eab1 +size 674 diff --git a/tests/Images/Input/Png/low-variance.png b/tests/Images/Input/Png/low-variance.png new file mode 100644 index 0000000000..7d8b1043eb --- /dev/null +++ b/tests/Images/Input/Png/low-variance.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70c224ef674b546db0ec3281008bb6e2b879e95b0be5625f0af8aff980eee583 +size 7844 diff --git a/tests/Images/Input/Png/rollsroyce.png b/tests/Images/Input/Png/rollsroyce.png new file mode 100644 index 0000000000..c2b9199ac6 --- /dev/null +++ b/tests/Images/Input/Png/rollsroyce.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90017d23999f41be7ac525648d8f435dcf8d1481ab98ebfd01a9ce87241e534f +size 25400 diff --git a/tests/Images/Input/Png/versioning-1_1.png b/tests/Images/Input/Png/versioning-1_1.png index 0eb37aab87..86345fa61d 100644 --- a/tests/Images/Input/Png/versioning-1_1.png +++ b/tests/Images/Input/Png/versioning-1_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce623255656921d491b5c389cd46931fbd6024575b87522c55d67a496dd761f0 -size 22781 +oid sha256:311478eb291cbf5e58be17d52057e7be8b703b84536ef8812557fa2b3759b9e6 +size 22102 diff --git a/tests/Images/Input/Png/xc1n0g08.png b/tests/Images/Input/Png/xc1n0g08.png new file mode 100644 index 0000000000..2afec8533f --- /dev/null +++ b/tests/Images/Input/Png/xc1n0g08.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..549a4924af --- /dev/null +++ b/tests/Images/Input/Png/xc9n2c08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e252a0e7df3e794e52ce4a831edafef76e7043d0d8d84019db0f7fd0b30e20f4 +size 145 diff --git a/tests/Images/Input/Png/xd0n2c08.png b/tests/Images/Input/Png/xd0n2c08.png new file mode 100644 index 0000000000..df7548a6db --- /dev/null +++ b/tests/Images/Input/Png/xd0n2c08.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..db5cec0c4b --- /dev/null +++ b/tests/Images/Input/Png/xd3n2c08.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..96c906fa8e --- /dev/null +++ b/tests/Images/Input/Png/xdtn0g01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9d1fb2a708703518368c392c74765a6e3e5b49dbb9717df3974452291032df9 +size 61 diff --git a/tests/Images/Input/Png/zlib-overflow.png b/tests/Images/Input/Png/zlib-overflow.png new file mode 100644 index 0000000000..07bf660b8a --- /dev/null +++ b/tests/Images/Input/Png/zlib-overflow.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2705125a7b108c7ef4e13872be88b991cd06ba97d81a306f70f58749cec53514 +size 10725 diff --git a/tests/Images/Input/Png/zlib-overflow2.png b/tests/Images/Input/Png/zlib-overflow2.png new file mode 100644 index 0000000000..bb95cfee8d --- /dev/null +++ b/tests/Images/Input/Png/zlib-overflow2.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..0eb37aab87 --- /dev/null +++ b/tests/Images/Input/Png/zlib-ztxt-bad-header.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce623255656921d491b5c389cd46931fbd6024575b87522c55d67a496dd761f0 +size 22781 diff --git a/tests/Images/Input/Tga/16bit_noalphabits.tga b/tests/Images/Input/Tga/16bit_noalphabits.tga new file mode 100644 index 0000000000..cff4abf945 --- /dev/null +++ b/tests/Images/Input/Tga/16bit_noalphabits.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b1bbb8c548 --- /dev/null +++ b/tests/Images/Input/Tga/16bit_rle_noalphabits.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..903eca4594 --- /dev/null +++ b/tests/Images/Input/Tga/32bit_no_alphabits.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0aea1128a1bd7477dfa0d007a1eba25907be24847284c48a5f9fbd61bcea3cf0 +size 61522 diff --git a/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga b/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga new file mode 100644 index 0000000000..b21dad5e0d --- /dev/null +++ b/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98a198392bd527523f8649d6126af81e5a588ad7265dc3d48a1da7b5a6cb6ff7 +size 230578 diff --git a/tests/Images/Input/Tga/ccm8.tga b/tests/Images/Input/Tga/ccm8.tga new file mode 100644 index 0000000000..ab92516355 --- /dev/null +++ b/tests/Images/Input/Tga/ccm8.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..13ae52c37e --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_LL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..01c71b81c5 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..7670e83f1d --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..a33d3aa2e1 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..ebc3781349 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_LL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..1d142b5c1d --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..bd6c256270 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..ce2bf4dc82 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..3434cc86cb --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_rle_LL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..75850f39cf --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_rle_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..ed77308e56 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_rle_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..04945dc617 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_rle_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..766d3884c9 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_rle_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..699e7ae5b8 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_rle_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..c61503db81 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_rle_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..659c3bcea8 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..da2a3f8ef9 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..a497383ab8 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..e074f253b1 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_LL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..aa361fa74d --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..19b0b36fc2 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..9b783a88aa --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..147cc91011 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_rle_LL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..6859107d0d --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_rle_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..be44253d20 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_rle_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b308ff7347 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_rle_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..6576d515a0 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_rle_LL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..2c14e37644 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_rle_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..0a06b3a865 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_rle_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..1e68e545e7 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_rle_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a709594fd475dbe042c16671959bfbc0031e64db8e74375f01c29685d2e384ec +size 30500 diff --git a/tests/Images/Input/Tga/rgb15.tga b/tests/Images/Input/Tga/rgb15.tga new file mode 100644 index 0000000000..870295b45a --- /dev/null +++ b/tests/Images/Input/Tga/rgb15.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..a45940fc98 --- /dev/null +++ b/tests/Images/Input/Tga/rgb15rle.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..bfaeae686c --- /dev/null +++ b/tests/Images/Input/Tga/rgb24_top_left.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..bb6a8a9c8c --- /dev/null +++ b/tests/Images/Input/Tga/rgb_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b7a7754fea --- /dev/null +++ b/tests/Images/Input/Tga/rgb_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..786eb7b7d3 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_LL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..312af4c0de --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..7ee3a52128 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..12d7b5a798 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..ceac831b82 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_rle_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..0ea58fd1d6 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_rle_UL.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..e6eebbdaff --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_rle_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..11146a812f --- /dev/null +++ b/tests/Images/Input/Tga/rgb_rle_LR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..4c9e540d37 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_rle_UR.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..6c4143c2ee --- /dev/null +++ b/tests/Images/Input/Tga/targa_16bit.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b25def7798 --- /dev/null +++ b/tests/Images/Input/Tga/targa_16bit_pal.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..49ef0e998b --- /dev/null +++ b/tests/Images/Input/Tga/targa_16bit_rle.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..82c22e2425 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..abfbf588a6 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit_pal.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b8c4071745 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..d6af44c0a6 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit_rle.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..9310c51a70 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..8b2a57c810 --- /dev/null +++ b/tests/Images/Input/Tga/targa_32bit.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..b021a2cc15 --- /dev/null +++ b/tests/Images/Input/Tga/targa_32bit_rle.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..9b0512971e --- /dev/null +++ b/tests/Images/Input/Tga/targa_8bit.tga @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000..d6a66def15 --- /dev/null +++ b/tests/Images/Input/Tga/targa_8bit_rle.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a18d7fd98bc9ab62276103b4e7b474be93b3d7241f4f06aa564e32150e205a71 +size 13145 diff --git a/tests/coverlet.runsettings b/tests/coverlet.runsettings new file mode 100644 index 0000000000..ee408a5f04 --- /dev/null +++ b/tests/coverlet.runsettings @@ -0,0 +1,14 @@ + + + + + + + lcov + [SixLabors.*]* + true + + + + +