diff --git a/.editorconfig b/.editorconfig index b725c5cce..8f0e28eec 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,20 +1,371 @@ -# 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_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, +# don't generate expression-bodied constructors, +# generate expression-bodied indexers, +# generate expression-bodied lambdas, +# don't generate expression-bodied methods, +# don't 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 +############################################################################### +[*.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 = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false: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 = false:warning +csharp_style_var_elsewhere = false:warning +csharp_style_var_when_type_is_apparent = true:warning diff --git a/.gitattributes b/.gitattributes index a664be3a8..f39b679c5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,49 +1,108 @@ -*.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 +############################################################################### +# Set explicit file behavior to: +# treat as text and +# normalize to Unix-style line endings +############################################################################### +*.asm text eol=lf +*.c text eol=lf +*.clj text eol=lf +*.cmd text eol=lf +*.cpp text eol=lf +*.css text eol=lf +*.cxx text eol=lf +*.config text eol=lf +*.DotSettings text eol=lf +*.erl text eol=lf +*.fs text eol=lf +*.fsx text eol=lf +*.h text eol=lf +*.htm text eol=lf +*.html text eol=lf +*.hs text eol=lf +*.hxx text eol=lf +*.java text eol=lf +*.js text eol=lf +*.json text eol=lf +*.less text eol=lf +*.lisp text eol=lf +*.lua text eol=lf +*.m text eol=lf +*.md text eol=lf +*.php text eol=lf +*.props text eol=lf +*.ps1 text eol=lf +*.py text eol=lf +*.rb text eol=lf +*.resx text eol=lf +*.runsettings text eol=lf +*.ruleset text eol=lf +*.sass text eol=lf +*.scss text eol=lf +*.sh text eol=lf +*.sql text eol=lf +*.svg text eol=lf +*.targets text eol=lf +*.tt text eol=lf +*.ttinclude text eol=crlf +*.txt text eol=lf +*.vb text eol=lf +*.yml text eol=lf -*.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 +############################################################################### +# Set explicit file behavior to: +# treat as text +# normalize to Unix-style line endings and +# diff as csharp +############################################################################### +*.cs text eol=lf diff=csharp -*.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: +# treat as text +# normalize to Unix-style line endings and +# 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 + +############################################################################### +# Set explicit file behavior to: +# treat as text +# normalize to Windows-style line endings and +# use a union merge when resoling conflicts +############################################################################### +*.sln text eol=crlf merge=union + +############################################################################### +# Set explicit file behavior to: +# treat as binary +############################################################################### +*.bmp binary +*.dll binary +*.exe binary +*.gif binary +*.jpg binary +*.png binary +*.ttf binary +*.snk binary + +############################################################################### +# Set explicit file behavior to: +# diff as plain text +############################################################################### +*.doc diff=astextplain +*.docx diff=astextplain +*.dot diff=astextplain +*.pdf diff=astextplain +*.pptx diff=astextplain +*.rtf diff=astextplain diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..ee7a10862 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: imagesharp \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index e7972649f..37ef701cd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +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 diff --git a/.travis.yml b/.travis.yml index 2515ca82a..6fd38484d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,10 @@ solution: ImageSharp.sln matrix: include: - - os: linux # Ubuntu 14.04 - dist: trusty + - os: linux # Ubuntu 16.04 + dist: xenial sudo: required - dotnet: 2.1.401 + dotnet: 2.1.603 mono: latest # - os: osx # OSX 10.11 # osx_image: xcode7.3.1 diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..bf004921e --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,81 @@ + + + + + + + + $(MSBuildThisFileDirectory)artifacts/ + $(ImageSharpProjectCategory)/$(MSBuildProjectName) + https://github.com/SixLabors/ImageSharp/ + + + + + true + $(BaseArtifactsPath)obj/$(BaseArtifactsPathSuffix)/ + portable + full + disable + true + false + true + + + + true + + + + + false + + + + + Six Labors and contributors + $(BaseArtifactsPath)bin/$(BaseArtifactsPathSuffix)/ + Six Labors + $(BaseArtifactsPath)pkg/$(BaseArtifactsPathSuffix)/$(Configuration)/ + SixLabors.ImageSharp + 0.0.1 + $(PackageVersion) + + + + + + $(MSBuildThisFileDirectory)standards/SixLabors.snk + Copyright © Six Labors and Contributors + strict;IOperation + true + 7.3 + en + true + https://raw.githubusercontent.com/SixLabors/Branding/master/icons/imagesharp/sixlabors.imagesharp.128.png + Apache-2.0 + $(RepositoryUrl) + true + git + + https://www.myget.org/F/sixlabors/api/v3/index.json; + https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json; + https://api.nuget.org/v3/index.json; + + 002400000c8000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97 + true + + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..d1183e5d4 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,47 @@ + + + + + + + + $(DefineConstants);$(OS) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageSharp.ruleset b/ImageSharp.ruleset deleted file mode 100644 index d318b75c2..000000000 --- a/ImageSharp.ruleset +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/ImageSharp.sln b/ImageSharp.sln index 8f3bc6860..1fd5e2d8b 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,46 +1,356 @@  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 .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + .gitmodules = .gitmodules .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 + build.cmd = build.cmd + build.ps1 = build.ps1 codecov.yml = codecov.yml CodeCoverage.runsettings = CodeCoverage.runsettings - .github\CONTRIBUTING.md = .github\CONTRIBUTING.md - .github\ISSUE_TEMPLATE\feature-request.md = .github\ISSUE_TEMPLATE\feature-request.md - features.md = features.md - ImageSharp.ruleset = ImageSharp.ruleset + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets ImageSharp.sln.DotSettings = ImageSharp.sln.DotSettings - NuGet.config = NuGet.config - .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md + LICENSE = LICENSE README.md = README.md run-tests.ps1 = run-tests.ps1 + stylecop.json = stylecop.json EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{815C0625-CD3D-440F-9F80-2D83856AB7AE}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799C43E-5C54-4A8F-8D64-B1475241DB0D}" ProjectSection(SolutionItems) = preProject - src\README.md = src\README.md + .github\CONTRIBUTING.md = .github\CONTRIBUTING.md + .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{FBE8C1AD-5AEC-4514-9B64-091D8E145865}" + ProjectSection(SolutionItems) = preProject + .github\ISSUE_TEMPLATE\ask-question.md = .github\ISSUE_TEMPLATE\ask-question.md + .github\ISSUE_TEMPLATE\bug-report.md = .github\ISSUE_TEMPLATE\bug-report.md + .github\ISSUE_TEMPLATE\feature-request.md = .github\ISSUE_TEMPLATE\feature-request.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vscode", ".vscode", "{0274D4CF-9932-47CC-8E89-54DC05B8F06E}" + ProjectSection(SolutionItems) = preProject + .vscode\launch.json = .vscode\launch.json + .vscode\tasks.json = .vscode\tasks.json + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E919DF0B-2607-4462-8FC0-5C98FE50F8C9}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9E574A07-F879-4811-9C41-5CBDC6BAFDB7}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "icons", "icons", "{2B02E303-7CC6-4E15-97EE-DBE86B287553}" ProjectSection(SolutionItems) = preProject - src\Shared\AssemblyInfo.Common.cs = src\Shared\AssemblyInfo.Common.cs + build\icons\imagesharp-logo-128.png = build\icons\imagesharp-logo-128.png + build\icons\imagesharp-logo-256.png = build\icons\imagesharp-logo-256.png + build\icons\imagesharp-logo-32.png = build\icons\imagesharp-logo-32.png + build\icons\imagesharp-logo-512.png = build\icons\imagesharp-logo-512.png + build\icons\imagesharp-logo-64.png = build\icons\imagesharp-logo-64.png + build\icons\imagesharp-logo.png = build\icons\imagesharp-logo.png + build\icons\imagesharp-logo.svg = build\icons\imagesharp-logo.svg + 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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Drawing", "src\ImageSharp.Drawing\ImageSharp.Drawing.csproj", "{2E33181E-6E28-4662-A801-E2E7DC206029}" 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}") = "CodeCoverage", "CodeCoverage", "{D4C5EC58-F8E6-4636-B9EE-C99D2578E5C6}" + ProjectSection(SolutionItems) = preProject + tests\CodeCoverage\CodeCoverage.cmd = tests\CodeCoverage\CodeCoverage.cmd + tests\CodeCoverage\packages.config = tests\CodeCoverage\packages.config + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{FA55F5DE-11A6-487D-ABA4-BC93A02717DD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Input", "Input", "{9DA226A1-8656-49A8-A58A-A8B5C081AD66}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bmp", "Bmp", "{1A82C5F6-90E0-4E97-BE16-A825C046B493}" + ProjectSection(SolutionItems) = preProject + tests\Images\Input\Bmp\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("{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("{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}" @@ -122,9 +432,25 @@ Global 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} + {2B02E303-7CC6-4E15-97EE-DBE86B287553} = {E919DF0B-2607-4462-8FC0-5C98FE50F8C9} {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {2E33181E-6E28-4662-A801-E2E7DC206029} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} + {D4C5EC58-F8E6-4636-B9EE-C99D2578E5C6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {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} diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index 432f4524a..ece3dddb3 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -382,9 +382,12 @@ True True True + True True True True True True + True + True \ No newline at end of file diff --git a/NuGet.config b/NuGet.config deleted file mode 100644 index 322105d4d..000000000 --- a/NuGet.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index a72074f8f..a5c3457fa 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Designed to democratize image processing, ImageSharp brings you an incredibly po Compared to `System.Drawing` we have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments. -Built against .NET Standard 1.3 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. +Built against .NET Standard 1.3, ImageSharp can be used in device, cloud, and embedded/IoT scenarios. ### Documentation For all SixLabors projects, including ImageSharp: @@ -41,15 +41,15 @@ Install stable releases via Nuget; development releases are available via MyGet. 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. + - 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 - **SixLabors.ImageSharp.Drawing** - - Brushes and various drawing algorithms, including drawing images. + - Brushes and various drawing algorithms, including drawing images - Various vector drawing methods for drawing paths, polygons etc. - - Text drawing. + - Text drawing ### Build Status @@ -72,6 +72,7 @@ On platforms supporting netstandard 1.3+ ```csharp using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.PixelFormats; // Image.Load(string path) is a shortcut for our default type. // Other pixel formats use Image.Load(string path)) @@ -117,12 +118,20 @@ Alternatively, you can work from command line and/or with a lightweight editor o - [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 Windows" button above or run the following git commands: ```bash git clone https://github.com/SixLabors/ImageSharp ``` +### Submodules + +This repository contains [git submodules](https://blog.github.com/2016-02-01-working-with-submodules/). To add the submodules to the project, navigate to the repository root and type: + +``` 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. diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..cd3d5e8cb --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,41 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.props + src + + + + + + $(MSBuildThisFileDirectory)..\standards\SixLabors.ruleset + true + + + + + + + + + + + + + + + + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 000000000..c0e01ae58 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,55 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.targets + + + + + + $(IntermediateOutputPath)$(MSBuildProjectName).InternalsVisibleTo$(DefaultLanguageSourceExtension) + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index a6f529ee3..a092e3604 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -1,50 +1,23 @@ - - - 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 - - - - - - - - - - - - - All - - - - ..\..\ImageSharp.ruleset - SixLabors.ImageSharp - - - true - - \ No newline at end of file + + SixLabors.ImageSharp.Drawing + SixLabors.ImageSharp.Drawing + An extension to ImageSharp that allows the drawing of images, paths, and text. + SixLabors.ImageSharp.Drawing + Image Draw Shape Path Font + SixLabors.ImageSharp + netcoreapp2.1;netstandard1.3;netstandard2.0 + + + + + + + + + + + + diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj.DotSettings b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj.DotSettings new file mode 100644 index 000000000..a728b5497 --- /dev/null +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs index 0c6e0d3b4..7e75d7eff 100644 --- a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets the blend percentage /// - protected GraphicsOptions Options { get; private set; } + protected GraphicsOptions Options { get; } /// /// Gets the color for a single pixel. diff --git a/src/ImageSharp.Drawing/Processing/Brushes.cs b/src/ImageSharp.Drawing/Processing/Brushes.cs index c5e7a3e9f..bd73e1f1b 100644 --- a/src/ImageSharp.Drawing/Processing/Brushes.cs +++ b/src/ImageSharp.Drawing/Processing/Brushes.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Processing /// /// A collection of methods for creating generic brushes. /// - /// A New + /// A New public static class Brushes { /// @@ -94,163 +94,131 @@ namespace SixLabors.ImageSharp.Processing /// 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); + /// A New + public static SolidBrush Solid(Color color) => 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); + /// A New + public static PatternBrush Percent10(Color foreColor) => + new PatternBrush(foreColor, Color.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); + /// A New + public static PatternBrush Percent10(Color foreColor, Color backColor) => + 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); + /// A New + public static PatternBrush Percent20(Color foreColor) => + new PatternBrush(foreColor, Color.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); + /// A New + public static PatternBrush Percent20(Color foreColor, Color backColor) => + 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); + /// A New + public static PatternBrush Horizontal(Color foreColor) => + new PatternBrush(foreColor, Color.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); + /// A New + public static PatternBrush Horizontal(Color foreColor, Color backColor) => + 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); + /// A New + public static PatternBrush Min(Color foreColor) => new PatternBrush(foreColor, Color.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); + /// A New + public static PatternBrush Min(Color foreColor, Color backColor) => + 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); + /// A New + public static PatternBrush Vertical(Color foreColor) => + new PatternBrush(foreColor, Color.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); + /// A New + public static PatternBrush Vertical(Color foreColor, Color backColor) => + 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); + /// A New + public static PatternBrush ForwardDiagonal(Color foreColor) => + new PatternBrush(foreColor, Color.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); + /// A New + public static PatternBrush ForwardDiagonal(Color foreColor, Color backColor) => + 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); + /// A New + public static PatternBrush BackwardDiagonal(Color foreColor) => + new PatternBrush(foreColor, Color.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); + /// A New + public static PatternBrush BackwardDiagonal(Color foreColor, Color backColor) => + 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.cs similarity index 79% rename from src/ImageSharp.Drawing/Processing/ColorStop{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/ColorStop.cs index 7fd0ba7cd..aee559469 100644 --- a/src/ImageSharp.Drawing/Processing/ColorStop{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/ColorStop.cs @@ -10,17 +10,15 @@ 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 + public readonly struct ColorStop { /// - /// Initializes a new instance of the struct. + /// 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) + public ColorStop(float ratio, in Color color) { this.Ratio = ratio; this.Color = color; @@ -34,6 +32,6 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets the color to be used. /// - public TPixel Color { get; } + public Color Color { get; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs deleted file mode 100644 index a8ee4d90b..000000000 --- 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/DrawingHelpers.cs b/src/ImageSharp.Drawing/Processing/DrawingHelpers.cs new file mode 100644 index 000000000..b4ce0c5cb --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawingHelpers.cs @@ -0,0 +1,22 @@ +// 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 +{ + internal static class DrawingHelpers + { + /// + /// Convert a to a of the given pixel type. + /// + public static DenseMatrix ToPixelMatrix(this DenseMatrix colorMatrix, Configuration configuration) + where TPixel : struct, IPixel + { + DenseMatrix result = new DenseMatrix(colorMatrix.Columns, colorMatrix.Rows); + Color.ToPixel(configuration, colorMatrix.Span, result.Span); + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs similarity index 82% rename from src/ImageSharp.Drawing/Processing/EllipticGradientBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs index 8af01564c..91da332a1 100644 --- a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs @@ -14,17 +14,15 @@ namespace SixLabors.ImageSharp.Processing /// 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 + public sealed class EllipticGradientBrush : GradientBrush { - private readonly Point center; + private readonly PointF center; - private readonly Point referenceAxisEnd; + private readonly PointF referenceAxisEnd; private readonly float axisRatio; - /// + /// /// The center of the elliptical gradient and 0 for the color stops. /// The end point of the reference axis of the ellipse. /// @@ -35,11 +33,11 @@ namespace SixLabors.ImageSharp.Processing /// Defines how the colors of the gradients are repeated. /// the color stops as defined in base class. public EllipticGradientBrush( - Point center, - Point referenceAxisEnd, + PointF center, + PointF referenceAxisEnd, float axisRatio, GradientRepetitionMode repetitionMode, - params ColorStop[] colorStops) + params ColorStop[] colorStops) : base(repetitionMode, colorStops) { this.center = center; @@ -47,12 +45,12 @@ namespace SixLabors.ImageSharp.Processing this.axisRatio = axisRatio; } - /// - public override BrushApplicator CreateApplicator( + /// + public override BrushApplicator CreateApplicator( ImageFrame source, RectangleF region, GraphicsOptions options) => - new RadialGradientBrushApplicator( + new RadialGradientBrushApplicator( source, options, this.center, @@ -62,11 +60,12 @@ namespace SixLabors.ImageSharp.Processing this.RepetitionMode); /// - private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase + private sealed class RadialGradientBrushApplicator : GradientBrushApplicator + where TPixel : struct, IPixel { - private readonly Point center; + private readonly PointF center; - private readonly Point referenceAxisEnd; + private readonly PointF referenceAxisEnd; private readonly float axisRatio; @@ -85,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing private readonly float referenceRadiusSquared; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The target image /// The options @@ -99,10 +98,10 @@ namespace SixLabors.ImageSharp.Processing public RadialGradientBrushApplicator( ImageFrame target, GraphicsOptions options, - Point center, - Point referenceAxisEnd, + PointF center, + PointF referenceAxisEnd, float axisRatio, - ColorStop[] colorStops, + ColorStop[] colorStops, GradientRepetitionMode repetitionMode) : base(target, options, colorStops, repetitionMode) { @@ -129,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing } /// - protected override float PositionOnGradient(int xt, int yt) + protected override float PositionOnGradient(float xt, float yt) { float x0 = xt - this.center.X; float y0 = yt - this.center.Y; @@ -150,8 +149,7 @@ namespace SixLabors.ImageSharp.Processing { var vA = a - junction; var vB = b - junction; - return (float)(Math.Atan2(vB.Y, vB.X) - - Math.Atan2(vA.Y, vA.X)); + return MathF.Atan2(vB.Y, vB.X) - MathF.Atan2(vA.Y, vA.X); } private float DistanceBetween( @@ -163,7 +161,7 @@ namespace SixLabors.ImageSharp.Processing float dY = p1.Y - p2.Y; float dYsquared = dY * dY; - return (float)Math.Sqrt(dXsquared + dYsquared); + return MathF.Sqrt(dXsquared + dYsquared); } } } diff --git a/src/ImageSharp.Drawing/Processing/DrawBezierExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs similarity index 56% rename from src/ImageSharp.Drawing/Processing/DrawBezierExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs index 782f5d4d7..85addd5f2 100644 --- a/src/ImageSharp.Drawing/Processing/DrawBezierExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs @@ -15,80 +15,93 @@ namespace SixLabors.ImageSharp.Processing /// /// 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))); + public static IImageProcessingContext DrawBeziers( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + float thickness, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawBeziers( + this IImageProcessingContext source, + IBrush brush, + float thickness, + params PointF[] points) => + 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); + public static IImageProcessingContext DrawBeziers( + this IImageProcessingContext source, + Color color, + float thickness, + params PointF[] points) => + 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); + public static IImageProcessingContext DrawBeziers( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float thickness, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawBeziers( + this IImageProcessingContext source, + GraphicsOptions options, + IPen pen, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawBeziers( + this IImageProcessingContext source, + IPen pen, + params PointF[] points) => + source.Draw(pen, new Path(new CubicBezierLineSegment(points))); } } diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs new file mode 100644 index 000000000..981cf1bef --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs @@ -0,0 +1,175 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.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 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) => + 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 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, + GraphicsOptions.Default.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) => + 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 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, + GraphicsOptions.Default.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)); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawLineExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs similarity index 56% rename from src/ImageSharp.Drawing/Processing/DrawLineExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs index 9084b30ef..b7b419be6 100644 --- a/src/ImageSharp.Drawing/Processing/DrawLineExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs @@ -15,80 +15,93 @@ namespace SixLabors.ImageSharp.Processing /// /// 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))); + public static IImageProcessingContext DrawLines( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + float thickness, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawLines( + this IImageProcessingContext source, + IBrush brush, + float thickness, + params PointF[] points) => + 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); + public static IImageProcessingContext DrawLines( + this IImageProcessingContext source, + Color color, + float thickness, + params PointF[] points) => + 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); + public static IImageProcessingContext DrawLines( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float thickness, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawLines( + this IImageProcessingContext source, + GraphicsOptions options, + IPen pen, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawLines( + this IImageProcessingContext source, + IPen pen, + params PointF[] points) => + 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/Extensions/DrawPathCollectionExtensions.cs similarity index 59% rename from src/ImageSharp.Drawing/Processing/DrawPathCollectionExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs index 0d3abf297..79b4fd0a8 100644 --- a/src/ImageSharp.Drawing/Processing/DrawPathCollectionExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs @@ -14,14 +14,16 @@ namespace SixLabors.ImageSharp.Processing /// /// 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 + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + IPen pen, + IPathCollection paths) { foreach (IPath path in paths) { @@ -34,67 +36,76 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext + Draw(this IImageProcessingContext source, IPen pen, IPathCollection paths) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + float thickness, + IPathCollection paths) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + IBrush brush, + float thickness, + IPathCollection paths) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float thickness, + IPathCollection paths) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + Color color, + float thickness, + IPathCollection paths) => + 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/Extensions/DrawPathExtensions.cs similarity index 58% rename from src/ImageSharp.Drawing/Processing/DrawPathExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs index 4dbe942f2..efafdde5a 100644 --- a/src/ImageSharp.Drawing/Processing/DrawPathExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs @@ -15,80 +15,90 @@ namespace SixLabors.ImageSharp.Processing /// /// 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)); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + IPen pen, + IPath path) => + 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); + public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, IPath path) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + float thickness, + IPath path) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + IBrush brush, + float thickness, + IPath path) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float thickness, + IPath path) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + Color color, + float thickness, + IPath path) => + 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/Extensions/DrawPolygonExtensions.cs similarity index 56% rename from src/ImageSharp.Drawing/Processing/DrawPolygonExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs index 4dcfe00aa..324e05e87 100644 --- a/src/ImageSharp.Drawing/Processing/DrawPolygonExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs @@ -15,80 +15,93 @@ namespace SixLabors.ImageSharp.Processing /// /// 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))); + public static IImageProcessingContext DrawPolygon( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + float thickness, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawPolygon( + this IImageProcessingContext source, + IBrush brush, + float thickness, + params PointF[] points) => + 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); + public static IImageProcessingContext DrawPolygon( + this IImageProcessingContext source, + Color color, + float thickness, + params PointF[] points) => + 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); + public static IImageProcessingContext DrawPolygon( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float thickness, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawPolygon( + this IImageProcessingContext source, + IPen pen, + params PointF[] points) => + 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))); + public static IImageProcessingContext DrawPolygon( + this IImageProcessingContext source, + GraphicsOptions options, + IPen pen, + params PointF[] points) => + 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/Extensions/DrawRectangleExtensions.cs similarity index 57% rename from src/ImageSharp.Drawing/Processing/DrawRectangleExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs index 918fb1e73..d0a6c8ccd 100644 --- a/src/ImageSharp.Drawing/Processing/DrawRectangleExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs @@ -15,80 +15,90 @@ namespace SixLabors.ImageSharp.Processing /// /// 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)); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + IPen pen, + RectangleF shape) => + 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); + public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, RectangleF shape) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + float thickness, + RectangleF shape) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + IBrush brush, + float thickness, + RectangleF shape) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float thickness, + RectangleF shape) => + 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); + public static IImageProcessingContext Draw( + this IImageProcessingContext source, + Color color, + float thickness, + RectangleF shape) => + 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/Extensions/DrawTextExtensions.cs similarity index 59% rename from src/ImageSharp.Drawing/Processing/DrawTextExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs index 46061ce9b..163a676bb 100644 --- a/src/ImageSharp.Drawing/Processing/DrawTextExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs @@ -16,7 +16,6 @@ namespace SixLabors.ImageSharp.Processing /// /// 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. @@ -25,14 +24,17 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext DrawText( + this IImageProcessingContext source, + string text, + Font font, + Color color, + PointF location) => + 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. @@ -42,14 +44,18 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext DrawText( + this IImageProcessingContext source, + TextGraphicsOptions options, + string text, + Font font, + Color color, + PointF location) => + 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. @@ -58,14 +64,17 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext DrawText( + this IImageProcessingContext source, + string text, + Font font, + IBrush brush, + PointF location) => + 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. @@ -75,14 +84,18 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext DrawText( + this IImageProcessingContext source, + TextGraphicsOptions options, + string text, + Font font, + IBrush brush, + PointF location) => + 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. @@ -91,14 +104,17 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext DrawText( + this IImageProcessingContext source, + string text, + Font font, + IPen pen, + PointF location) => + 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. @@ -108,14 +124,18 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext DrawText( + this IImageProcessingContext source, + TextGraphicsOptions options, + string text, + Font font, + IPen pen, + PointF location) => + 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. @@ -125,14 +145,18 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext DrawText( + this IImageProcessingContext source, + string text, + Font font, + IBrush brush, + IPen pen, + PointF location) => + 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. @@ -143,8 +167,14 @@ namespace SixLabors.ImageSharp.Processing /// /// 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)); + public static IImageProcessingContext DrawText( + this IImageProcessingContext source, + TextGraphicsOptions options, + string text, + Font font, + IBrush brush, + IPen pen, + PointF location) => + source.ApplyProcessor(new DrawTextProcessor(options, text, font, brush, pen, location)); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/FillPathBuilderExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs similarity index 61% rename from src/ImageSharp.Drawing/Processing/FillPathBuilderExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs index ff4de3ff8..ca7eab93f 100644 --- a/src/ImageSharp.Drawing/Processing/FillPathBuilderExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs @@ -15,14 +15,16 @@ namespace SixLabors.ImageSharp.Processing /// /// 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 + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + Action path) { var pb = new PathBuilder(); path(pb); @@ -33,38 +35,42 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + IBrush brush, + Action path) => + 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + Action path) => + 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + Color color, + Action path) => + 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/Extensions/FillPathCollectionExtensions.cs similarity index 62% rename from src/ImageSharp.Drawing/Processing/FillPathCollectionExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs index da2dd35b6..d750ff455 100644 --- a/src/ImageSharp.Drawing/Processing/FillPathCollectionExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs @@ -14,14 +14,16 @@ namespace SixLabors.ImageSharp.Processing /// /// 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 + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + IPathCollection paths) { foreach (IPath s in paths) { @@ -34,38 +36,42 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + IBrush brush, + IPathCollection paths) => + 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + IPathCollection paths) => + 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + Color color, + IPathCollection paths) => + 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/Extensions/FillPathExtensions.cs similarity index 58% rename from src/ImageSharp.Drawing/Processing/FillPathExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs index da1062111..718016a9e 100644 --- a/src/ImageSharp.Drawing/Processing/FillPathExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs @@ -1,7 +1,6 @@ // 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; @@ -15,51 +14,51 @@ namespace SixLabors.ImageSharp.Processing /// /// 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)); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + IPath path) => + 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)); + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, IPath path) => + 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + IPath path) => + 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); + public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color, IPath path) => + 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/Extensions/FillPolygonExtensions.cs similarity index 56% rename from src/ImageSharp.Drawing/Processing/FillPolygonExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs index 970ca2264..8150c7381 100644 --- a/src/ImageSharp.Drawing/Processing/FillPolygonExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs @@ -15,51 +15,57 @@ namespace SixLabors.ImageSharp.Processing /// /// 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))); + public static IImageProcessingContext FillPolygon( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + params PointF[] points) => + 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))); + public static IImageProcessingContext FillPolygon( + this IImageProcessingContext source, + IBrush brush, + params PointF[] points) => + 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))); + public static IImageProcessingContext FillPolygon( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + params PointF[] points) => + 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))); + public static IImageProcessingContext FillPolygon( + this IImageProcessingContext source, + Color color, + params PointF[] points) => + 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/Extensions/FillRectangleExtensions.cs similarity index 58% rename from src/ImageSharp.Drawing/Processing/FillRectangleExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs index 26bf214f7..ad512015e 100644 --- a/src/ImageSharp.Drawing/Processing/FillRectangleExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs @@ -15,51 +15,53 @@ namespace SixLabors.ImageSharp.Processing /// /// 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)); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + RectangleF shape) => + 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)); + public static IImageProcessingContext + Fill(this IImageProcessingContext source, IBrush brush, RectangleF shape) => + 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + RectangleF shape) => + 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); + public static IImageProcessingContext + Fill(this IImageProcessingContext source, Color color, RectangleF shape) => + 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/Extensions/FillRegionExtensions.cs similarity index 55% rename from src/ImageSharp.Drawing/Processing/FillRegionExtensions.cs rename to src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs index e566d0323..294e57514 100644 --- a/src/ImageSharp.Drawing/Processing/FillRegionExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs @@ -1,7 +1,6 @@ // 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; @@ -15,85 +14,82 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush) => + 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)); + public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color) => + 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); + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, Region region) => + 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); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + Region region) => + 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); + public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color, Region region) => + 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)); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush, + Region region) => + 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)); + public static IImageProcessingContext Fill( + this IImageProcessingContext source, + GraphicsOptions options, + IBrush brush) => + 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/GradientBrush.cs similarity index 78% rename from src/ImageSharp.Drawing/Processing/GradientBrushBase{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/GradientBrush.cs index 00141a8d8..9826748c4 100644 --- a/src/ImageSharp.Drawing/Processing/GradientBrushBase{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/GradientBrush.cs @@ -13,16 +13,14 @@ namespace SixLabors.ImageSharp.Processing /// /// Base class for Gradient brushes /// - /// The pixel format - public abstract class GradientBrushBase : IBrush - where TPixel : struct, IPixel + public abstract class GradientBrush : IBrush { - /// + /// /// Defines how the colors are repeated beyond the interval [0..1] /// The gradient colors. - protected GradientBrushBase( + protected GradientBrush( GradientRepetitionMode repetitionMode, - params ColorStop[] colorStops) + params ColorStop[] colorStops) { this.RepetitionMode = repetitionMode; this.ColorStops = colorStops; @@ -36,34 +34,38 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets the list of color stops for this gradient. /// - protected ColorStop[] ColorStops { get; } + protected ColorStop[] ColorStops { get; } - /// - public abstract BrushApplicator CreateApplicator( + /// + public abstract BrushApplicator CreateApplicator( ImageFrame source, RectangleF region, - GraphicsOptions options); + GraphicsOptions options) + where TPixel : struct, IPixel; /// /// Base class for gradient brush applicators /// - protected abstract class GradientBrushApplicatorBase : BrushApplicator + internal abstract class GradientBrushApplicator : BrushApplicator + where TPixel : struct, IPixel { - private readonly ColorStop[] colorStops; + private static readonly TPixel Transparent = Color.Transparent.ToPixel(); + + private readonly ColorStop[] colorStops; private readonly GradientRepetitionMode repetitionMode; /// - /// Initializes a new instance of the class. + /// 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( + protected GradientBrushApplicator( ImageFrame target, GraphicsOptions options, - ColorStop[] colorStops, + ColorStop[] colorStops, GradientRepetitionMode repetitionMode) : base(target, options) { @@ -81,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing { get { - float positionOnCompleteGradient = this.PositionOnGradient(x, y); + float positionOnCompleteGradient = this.PositionOnGradient(x + 0.5f, y + 0.5f); switch (this.repetitionMode) { @@ -103,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing case GradientRepetitionMode.DontFill: if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0) { - return NamedColors.Transparent; + return Transparent; } break; @@ -111,17 +113,17 @@ namespace SixLabors.ImageSharp.Processing throw new ArgumentOutOfRangeException(); } - (ColorStop from, ColorStop to) = this.GetGradientSegment(positionOnCompleteGradient); + (ColorStop from, ColorStop to) = this.GetGradientSegment(positionOnCompleteGradient); if (from.Color.Equals(to.Color)) { - return from.Color; + return from.Color.ToPixel(); } else { var fromAsVector = from.Color.ToVector4(); var toAsVector = to.Color.ToVector4(); - float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / to.Ratio; + float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio); // TODO: this should be changeble for different gradienting functions Vector4 result = PorterDuffFunctions.NormalSrcOver( @@ -137,27 +139,27 @@ namespace SixLabors.ImageSharp.Processing } /// - /// calculates the position on the gradient for a given pixel. + /// calculates the position on the gradient for a given point. /// This method is abstract as it's content depends on the shape of the gradient. /// - /// The x coordinate of the pixel - /// The y coordinate of the pixel + /// The x coordinate of the point + /// The y coordinate of the point /// - /// The position the given pixel has on the gradient. + /// The position the given point has on the gradient. /// The position is not bound to the [0..1] interval. /// Values outside of that interval may be treated differently, /// e.g. for the enum. /// - protected abstract float PositionOnGradient(int x, int y); + protected abstract float PositionOnGradient(float x, float y); - private (ColorStop from, ColorStop to) GetGradientSegment( + private (ColorStop from, ColorStop to) GetGradientSegment( float positionOnCompleteGradient) { - ColorStop localGradientFrom = this.colorStops[0]; - ColorStop localGradientTo = default; + 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) + foreach (ColorStop colorStop in this.colorStops) { localGradientTo = colorStop; diff --git a/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs b/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs index c156153be..6aed8a030 100644 --- a/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs +++ b/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs @@ -21,16 +21,16 @@ namespace SixLabors.ImageSharp.Processing /// /// Reflect the gradient. - /// Similar to , but each other repetition uses inverse order of s. + /// 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, + /// 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. + /// For and it's beyond 1.0. /// DontFill } diff --git a/src/ImageSharp.Drawing/Processing/IBrush.cs b/src/ImageSharp.Drawing/Processing/IBrush.cs index a3c94a1b5..0cd2e20fd 100644 --- a/src/ImageSharp.Drawing/Processing/IBrush.cs +++ b/src/ImageSharp.Drawing/Processing/IBrush.cs @@ -9,17 +9,16 @@ 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 . + /// logic for retrieving pixel values for specific locations. /// - public interface IBrush - where TPixel : struct, IPixel + public interface IBrush { /// /// Creates the applicator for this brush. /// + /// The pixel type. /// The source image. /// The region the brush will be applied to. /// The graphic options @@ -30,6 +29,10 @@ namespace SixLabors.ImageSharp.Processing /// 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); + BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options) + where TPixel : struct, IPixel; } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/IPen.cs b/src/ImageSharp.Drawing/Processing/IPen.cs index 6f63dcfd0..af7517905 100644 --- a/src/ImageSharp.Drawing/Processing/IPen.cs +++ b/src/ImageSharp.Drawing/Processing/IPen.cs @@ -2,28 +2,21 @@ // Licensed under the Apache License, Version 2.0. using System; + using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing { /// - /// Interface representing a Pen + /// Interface representing the pattern and size of the stroke to apply with a Pen. /// - /// The type of the color. - public interface IPen : IPen - where TPixel : struct, IPixel + public interface IPen { /// /// Gets the stroke fill. /// - IBrush StrokeFill { get; } - } + 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 /// @@ -34,4 +27,4 @@ namespace SixLabors.ImageSharp.Processing /// ReadOnlySpan StrokePattern { get; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/ImageBrush.cs similarity index 67% rename from src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/ImageBrush.cs index 1ef4bb9ec..33f7b831a 100644 --- a/src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/ImageBrush.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -14,46 +15,50 @@ 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 + public class ImageBrush : IBrush { /// /// The image to paint. /// - private readonly ImageFrame image; + private readonly Image image; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The image. - public ImageBrush(ImageFrame image) + public ImageBrush(Image 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) + where TPixel : struct, IPixel { - } + if (this.image is Image specificImage) + { + return new ImageBrushApplicator(source, specificImage, region, options, false); + } - /// - public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) - => new ImageBrushApplicator(source, this.image, region, options); + specificImage = this.image.CloneAs(); + + return new ImageBrushApplicator(source, specificImage, region, options, true); + } /// /// The image brush applicator. /// - private class ImageBrushApplicator : BrushApplicator + private class ImageBrushApplicator : BrushApplicator + where TPixel : struct, IPixel { - /// - /// The source image. - /// - private readonly ImageFrame source; + private ImageFrame sourceFrame; + + private Image sourceImage; + + private readonly bool shouldDisposeImage; /// /// The y-length. @@ -76,16 +81,24 @@ namespace SixLabors.ImageSharp.Processing private readonly int offsetX; /// - /// Initializes a new instance of the class. + /// 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) + /// Whether to dispose the image on disposal of the applicator. + public ImageBrushApplicator( + ImageFrame target, + Image image, + RectangleF region, + GraphicsOptions options, + bool shouldDisposeImage) : base(target, options) { - this.source = image; + this.sourceImage = image; + this.sourceFrame = image.Frames.RootFrame; + this.shouldDisposeImage = shouldDisposeImage; this.xLength = image.Width; this.yLength = image.Height; this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); @@ -106,14 +119,19 @@ namespace SixLabors.ImageSharp.Processing { int srcX = (x - this.offsetX) % this.xLength; int srcY = (y - this.offsetY) % this.yLength; - return this.source[srcX, srcY]; + return this.sourceFrame[srcX, srcY]; } } /// public override void Dispose() { - this.source.Dispose(); + if (this.shouldDisposeImage) + { + this.sourceImage?.Dispose(); + this.sourceImage = null; + this.sourceFrame = null; + } } /// @@ -128,7 +146,7 @@ namespace SixLabors.ImageSharp.Processing int sourceY = (y - this.offsetY) % this.yLength; int offsetX = x - this.offsetX; - Span sourceRow = this.source.GetPixelRowSpan(sourceY); + Span sourceRow = this.sourceFrame.GetPixelRowSpan(sourceY); for (int i = 0; i < scanline.Length; i++) { @@ -141,7 +159,7 @@ namespace SixLabors.ImageSharp.Processing Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); this.Blender.Blend( - this.source.Configuration, + this.sourceFrame.Configuration, destinationRow, destinationRow, overlaySpan, diff --git a/src/ImageSharp.Drawing/Processing/LinearGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs similarity index 76% rename from src/ImageSharp.Drawing/Processing/LinearGradientBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs index 765bf5499..bb99eeb26 100644 --- a/src/ImageSharp.Drawing/Processing/LinearGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs @@ -13,26 +13,24 @@ namespace SixLabors.ImageSharp.Processing /// 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 + public sealed class LinearGradientBrush : GradientBrush { - private readonly Point p1; + private readonly PointF p1; - private readonly Point p2; + private readonly PointF p2; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Start point /// End point /// defines how colors are repeated. /// public LinearGradientBrush( - Point p1, - Point p2, + PointF p1, + PointF p2, GradientRepetitionMode repetitionMode, - params ColorStop[] colorStops) + params ColorStop[] colorStops) : base(repetitionMode, colorStops) { this.p1 = p1; @@ -40,17 +38,27 @@ namespace SixLabors.ImageSharp.Processing } /// - public override BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) - => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, this.RepetitionMode, options); + 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 sealed class LinearGradientBrushApplicator : GradientBrushApplicator + where TPixel : struct, IPixel { - private readonly Point start; + private readonly PointF start; - private readonly Point end; + private readonly PointF end; /// /// the vector along the gradient, x component @@ -83,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing private readonly float length; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The source /// start point of the gradient @@ -93,9 +101,9 @@ namespace SixLabors.ImageSharp.Processing /// the graphics options public LinearGradientBrushApplicator( ImageFrame source, - Point start, - Point end, - ColorStop[] colorStops, + PointF start, + PointF end, + ColorStop[] colorStops, GradientRepetitionMode repetitionMode, GraphicsOptions options) : base(source, options, colorStops, repetitionMode) @@ -113,18 +121,18 @@ namespace SixLabors.ImageSharp.Processing // some helpers: this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY); - this.length = (float)Math.Sqrt(this.alongsSquared); + this.length = MathF.Sqrt(this.alongsSquared); } - protected override float PositionOnGradient(int x, int y) + protected override float PositionOnGradient(float x, float y) { if (this.acrossX == 0) { - return (x - this.start.X) / (float)(this.end.X - this.start.X); + return (x - this.start.X) / (this.end.X - this.start.X); } else if (this.acrossY == 0) { - return (y - this.start.Y) / (float)(this.end.Y - this.start.Y); + return (y - this.start.Y) / (this.end.Y - this.start.Y); } else { @@ -137,9 +145,7 @@ namespace SixLabors.ImageSharp.Processing 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)); + float distance = MathF.Sqrt(MathF.Pow(x4 - this.start.X, 2) + MathF.Pow(y4 - this.start.Y, 2)); // get and return ratio float ratio = distance / this.length; diff --git a/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/PatternBrush.cs similarity index 83% rename from src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/PatternBrush.cs index 46ed36f68..a7a6785b9 100644 --- a/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/PatternBrush.cs @@ -34,38 +34,36 @@ namespace SixLabors.ImageSharp.Processing /// 0 /// /// - /// The pixel format. - public class PatternBrush : IBrush - where TPixel : struct, IPixel + public class PatternBrush : IBrush { /// /// The pattern. /// - private readonly DenseMatrix pattern; + private readonly DenseMatrix pattern; private readonly DenseMatrix patternVector; /// - /// Initializes a new instance of the class. + /// 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) + public PatternBrush(Color foreColor, Color backColor, bool[,] pattern) : this(foreColor, backColor, new DenseMatrix(pattern)) { } /// - /// Initializes a new instance of the class. + /// 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) + internal PatternBrush(Color foreColor, Color backColor, in DenseMatrix pattern) { var foreColorVector = foreColor.ToVector4(); var backColorVector = backColor.ToVector4(); - this.pattern = new DenseMatrix(pattern.Columns, pattern.Rows); + 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++) { @@ -83,25 +81,32 @@ namespace SixLabors.ImageSharp.Processing } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The brush. - internal PatternBrush(PatternBrush 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); - } + public BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options) + where TPixel : struct, IPixel => + new PatternBrushApplicator( + source, + this.pattern.ToPixelMatrix(source.Configuration), + this.patternVector, + options); /// /// The pattern brush applicator. /// - private class PatternBrushApplicator : BrushApplicator + private class PatternBrushApplicator : BrushApplicator + where TPixel : struct, IPixel { /// /// The pattern. @@ -110,13 +115,13 @@ namespace SixLabors.ImageSharp.Processing private readonly DenseMatrix patternVector; /// - /// Initializes a new instance of the class. + /// 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) + public PatternBrushApplicator(ImageFrame source, in DenseMatrix pattern, DenseMatrix patternVector, GraphicsOptions options) : base(source, options) { this.pattern = pattern; diff --git a/src/ImageSharp.Drawing/Processing/Pen{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Pen.cs similarity index 69% rename from src/ImageSharp.Drawing/Processing/Pen{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/Pen.cs index 26c21a0e5..c9d60a50e 100644 --- a/src/ImageSharp.Drawing/Processing/Pen{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Pen.cs @@ -9,7 +9,6 @@ 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 @@ -18,29 +17,28 @@ namespace SixLabors.ImageSharp.Processing /// 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 + public class Pen : IPen { private readonly float[] pattern; /// - /// Initializes a new instance of the class. + /// 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) + public Pen(Color color, float width, float[] pattern) + : this(new SolidBrush(color), width, pattern) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The brush. /// The width. /// The pattern. - public Pen(IBrush brush, float width, float[] pattern) + public Pen(IBrush brush, float width, float[] pattern) { this.StrokeFill = brush; this.StrokeWidth = width; @@ -48,27 +46,27 @@ namespace SixLabors.ImageSharp.Processing } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The color. /// The width. - public Pen(TPixel color, float width) - : this(new SolidBrush(color), width) + public Pen(Color color, float width) + : this(new SolidBrush(color), width) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The brush. /// The width. - public Pen(IBrush brush, float width) + public Pen(IBrush brush, float width) : this(brush, width, Pens.EmptyPattern) { } /// - public IBrush StrokeFill { get; } + public IBrush StrokeFill { get; } /// public float StrokeWidth { get; } diff --git a/src/ImageSharp.Drawing/Processing/Pens.cs b/src/ImageSharp.Drawing/Processing/Pens.cs index 90253a3cb..4b8f478c9 100644 --- a/src/ImageSharp.Drawing/Processing/Pens.cs +++ b/src/ImageSharp.Drawing/Processing/Pens.cs @@ -21,109 +21,79 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + public static Pen Solid(Color color, float width) => 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); + public static Pen Solid(IBrush brush, float width) => 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); + public static Pen Dash(Color color, float width) => 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); + public static Pen Dash(IBrush brush, float width) => 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); + public static Pen Dot(Color color, float width) => 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); + public static Pen Dot(IBrush brush, float width) => 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); + public static Pen DashDot(Color color, float width) => 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); + public static Pen DashDot(IBrush brush, float width) => 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); + public static Pen DashDotDot(Color color, float width) => 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); + public static Pen DashDotDot(IBrush brush, float width) => new Pen(brush, width, DashDotDotPattern); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs index 0957904c6..54b7315b2 100644 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -1,12 +1,10 @@ // 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; @@ -16,84 +14,86 @@ 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 + public class DrawImageProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The image to blend with the currently processing image. + /// 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. Must be between 0 and 1. - public DrawImageProcessor(Image image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) + /// The opacity of the image to blend. + 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; + this.ColorBlendingMode = colorBlendingMode; + this.AlphaCompositionMode = alphaCompositionMode; + this.Opacity = opacity; } /// - /// Gets the image to blend + /// Gets the image to blend. /// - public Image Image { get; } + public Image Image { get; } /// - /// Gets the opacity of the image to blend + /// Gets the location to draw the blended image. /// - public float Opacity { get; } + public Point Location { get; } /// - /// Gets the pixel blender + /// Gets the blending mode to use when drawing the image. /// - public PixelBlender Blender { get; } + public PixelColorBlendingMode ColorBlendingMode { get; } /// - /// Gets the location to draw the blended image + /// Gets the Alpha blending mode to use when drawing the image. /// - public Point Location { get; } + public PixelAlphaCompositionMode AlphaCompositionMode { 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(); + /// + /// Gets the opacity of the image to blend. + /// + public float Opacity { get; } - 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; + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixelBg : struct, IPixel + { + var visitor = new ProcessorFactoryVisitor(this); + this.Image.AcceptVisitor(visitor); + return visitor.Result; + } - int minY = Math.Max(this.Location.Y, sourceRectangle.Y); - int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + private class ProcessorFactoryVisitor : IImageVisitor + where TPixelBg : struct, IPixel + { + private readonly DrawImageProcessor definition; - int width = maxX - minX; + public ProcessorFactoryVisitor(DrawImageProcessor definition) + { + this.definition = definition; + } - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + public IImageProcessor Result { get; private set; } - 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); - } - }); + public void Visit(Image image) + where TPixelFg : struct, IPixel + { + this.Result = new DrawImageProcessor( + image, + this.definition.Location, + this.definition.ColorBlendingMode, + this.definition.AlphaCompositionMode, + this.definition.Opacity); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs new file mode 100644 index 000000000..21599bf78 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors and contributors. +// 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.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 : struct, IPixel + where TPixelFg : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The blending mode to use when drawing the image. + /// The Alpha blending mode to use when drawing the image. + /// The opacity of the image to blend. Must be between 0 and 1. + public DrawImageProcessor( + Image image, + Point location, + PixelColorBlendingMode colorBlendingMode, + PixelAlphaCompositionMode alphaCompositionMode, + float opacity) + { + Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); + + this.Image = image; + this.Opacity = opacity; + this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); + this.Location = location; + } + + /// + /// Gets the image to blend + /// + public Image Image { get; } + + /// + /// Gets the opacity of the image to blend + /// + public float Opacity { get; } + + /// + /// Gets the pixel blender + /// + public PixelBlender Blender { get; } + + /// + /// Gets the location to draw the blended image + /// + public Point Location { get; } + + /// + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + Image targetImage = this.Image; + PixelBlender blender = this.Blender; + int locationY = this.Location.Y; + + // Align start/end positions. + Rectangle bounds = targetImage.Bounds(); + + int minX = Math.Max(this.Location.X, sourceRectangle.X); + int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right); + int targetX = minX - this.Location.X; + + int minY = Math.Max(this.Location.Y, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + + int width = maxX - minX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + // not a valid operation because rectangle does not overlap with this image. + if (workingRect.Width <= 0 || workingRect.Height <= 0) + { + throw new ImageProcessingException( + "Cannot draw image because the source image does not overlap the target image."); + } + + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span background = source.GetPixelRowSpan(y).Slice(minX, width); + Span foreground = + targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); + blender.Blend(configuration, background, background, foreground, this.Opacity); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs index ed6c86951..d6254c7cf 100644 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs @@ -1,124 +1,45 @@ // 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. + /// Defines a processor to fill an with the given + /// using blending defined by the given . /// - /// The pixel format. - internal class FillProcessor : ImageProcessor - where TPixel : struct, IPixel + public class FillProcessor : IImageProcessor { /// - /// The brush. + /// Initializes a new instance of the class. /// - 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) + /// The brush to use for filling. + /// The defining how to blend the brush pixels over the image pixels. + public FillProcessor(IBrush brush, GraphicsOptions options) { - this.brush = brush; - this.options = 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; + /// + /// Gets the used for filling the destination image. + /// + public IBrush Brush { get; } - applicator.Apply(amount.GetSpan(), offsetX, offsetY); - } - }); - } - } - } + /// + /// Gets the defining how to blend the brush pixels over the image pixels. + /// + public GraphicsOptions Options { get; } - private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - solidBrush = this.brush as SolidBrush; - - if (solidBrush == null) - { - return false; - } - - return this.options.IsOpaqueColorWithoutBlending(solidBrush.Color); + return new FillProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs new file mode 100644 index 000000000..68aef82e2 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs @@ -0,0 +1,118 @@ +// 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.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +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 + { + private readonly FillProcessor definition; + + public FillProcessor(FillProcessor definition) + { + this.definition = definition; + } + + /// + 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); + + IBrush brush = this.definition.Brush; + GraphicsOptions options = this.definition.Options; + + // If there's no reason for blending, then avoid it. + if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) + { + ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); + + TPixel colorPixel = solidBrush.Color.ToPixel(); + + ParallelHelper.IterateRows( + workingRect, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + source.GetPixelRowSpan(y).Slice(minX, width).Fill(colorPixel); + } + }); + } + 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 = brush.CreateApplicator( + source, + sourceRectangle, + 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.definition.Brush as SolidBrush; + + if (solidBrush == null) + { + return false; + } + + return this.definition.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 index 550c021ca..1b207d4cd 100644 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs @@ -1,36 +1,25 @@ // 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 + /// Defines a processor to fill pixels withing a given + /// with the given and blending defined by the given . /// - /// The type of the color. - /// - internal class FillRegionProcessor : ImageProcessor - where TPixel : struct, IPixel + public class FillRegionProcessor : IImageProcessor { - private const float AntialiasFactor = 1f; - private const int DrawPadding = 1; - /// - /// Initializes a new instance of the class. + /// 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) + public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options) { this.Region = region; this.Brush = brush; @@ -38,9 +27,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing } /// - /// Gets the brush. + /// Gets the used for filling the destination image. /// - public IBrush Brush { get; } + public IBrush Brush { get; } /// /// Gets the region that this processor applies to. @@ -48,172 +37,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing public Region Region { get; } /// - /// Gets the options. + /// Gets the defining how to blend the brush pixels over the image pixels. /// - /// - /// 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) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - solidBrush = this.Brush as SolidBrush; - - if (solidBrush == null) - { - return false; - } - - return this.Options.IsOpaqueColorWithoutBlending(solidBrush.Color); + return new FillRegionProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs new file mode 100644 index 000000000..d5778c865 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs @@ -0,0 +1,195 @@ +// 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.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 readonly FillRegionProcessor definition; + + public FillRegionProcessor(FillRegionProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + GraphicsOptions options = this.definition.Options; + IBrush brush = this.definition.Brush; + Region region = this.definition.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 (options.Antialias) + { + offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset. + subpixelCount = options.AntialiasSubpixelDepth; + if (subpixelCount < 4) + { + subpixelCount = 4; + } + } + + using (BrushApplicator applicator = brush.CreateApplicator(source, rect, 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); + TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel() : default; + + 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 (!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(solidBrushColor); + } + + continue; + } + } + + applicator.Apply(scanline, minX, y); + } + } + } + } + } + + private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) + { + solidBrush = this.definition.Brush as SolidBrush; + + if (solidBrush == null) + { + return false; + } + + return this.definition.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 index 266d842bf..40621ce99 100644 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs @@ -2,30 +2,20 @@ // 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. + /// Defines a processor to draw text on an . /// - /// The pixel format. - internal class DrawTextProcessor : ImageProcessor - where TPixel : struct, IPixel + public class DrawTextProcessor : IImageProcessor { - private CachingGlyphRenderer textRenderer; - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The options /// The text we want to render @@ -33,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text /// 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) + public DrawTextProcessor(TextGraphicsOptions options, string text, Font font, IBrush brush, IPen pen, PointF location) { Guard.NotNull(text, nameof(text)); Guard.NotNull(font, nameof(font)); @@ -52,24 +42,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text } /// - /// Gets the brush. + /// Gets the brush used to fill the glyphs. /// - public IBrush Brush { get; } + public IBrush Brush { get; } /// - /// Gets the options + /// Gets the defining blending modes and text-specific drawing settings. /// public TextGraphicsOptions Options { get; } /// - /// Gets the text + /// Gets the text to draw. /// public string Text { get; } /// /// Gets the pen used for outlining the text, if Null then we will not outline /// - public IPen Pen { get; } + public IPen Pen { get; } /// /// Gets the font used to render the text. @@ -81,403 +71,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text /// 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) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - // 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(); - } - } + return new DrawTextProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs new file mode 100644 index 000000000..9fb52d6bc --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs @@ -0,0 +1,446 @@ +// 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; + + private readonly DrawTextProcessor definition; + + public DrawTextProcessor(DrawTextProcessor definition) + { + this.definition = definition; + } + + private TextGraphicsOptions Options => this.definition.Options; + + private Font Font => this.definition.Font; + + private PointF Location => this.definition.Location; + + private string Text => this.definition.Text; + + private IPen Pen => this.definition.Pen; + + private IBrush Brush => this.definition.Brush; + + 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.cs similarity index 78% rename from src/ImageSharp.Drawing/Processing/RadialGradientBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs index 16380fc34..f4d2dd81f 100644 --- a/src/ImageSharp.Drawing/Processing/RadialGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs @@ -11,36 +11,34 @@ 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 + public sealed class RadialGradientBrush : GradientBrush { - private readonly Point center; + private readonly PointF center; private readonly float radius; - /// + /// /// The center of the circular gradient and 0 for the color stops. /// The radius of the circular gradient and 1 for the color stops. /// Defines how the colors in the gradient are repeated. /// the color stops as defined in base class. public RadialGradientBrush( - Point center, + PointF center, float radius, GradientRepetitionMode repetitionMode, - params ColorStop[] colorStops) + params ColorStop[] colorStops) : base(repetitionMode, colorStops) { this.center = center; this.radius = radius; } - /// - public override BrushApplicator CreateApplicator( + /// + public override BrushApplicator CreateApplicator( ImageFrame source, RectangleF region, GraphicsOptions options) => - new RadialGradientBrushApplicator( + new RadialGradientBrushApplicator( source, options, this.center, @@ -49,14 +47,15 @@ namespace SixLabors.ImageSharp.Processing this.RepetitionMode); /// - private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase + private sealed class RadialGradientBrushApplicator : GradientBrushApplicator + where TPixel : struct, IPixel { - private readonly Point center; + private readonly PointF center; private readonly float radius; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The target image /// The options. @@ -67,9 +66,9 @@ namespace SixLabors.ImageSharp.Processing public RadialGradientBrushApplicator( ImageFrame target, GraphicsOptions options, - Point center, + PointF center, float radius, - ColorStop[] colorStops, + ColorStop[] colorStops, GradientRepetitionMode repetitionMode) : base(target, options, colorStops, repetitionMode) { @@ -89,9 +88,9 @@ namespace SixLabors.ImageSharp.Processing /// 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) + protected override float PositionOnGradient(float x, float y) { - float distance = (float)Math.Sqrt(Math.Pow(this.center.X - x, 2) + Math.Pow(this.center.Y - y, 2)); + float distance = MathF.Sqrt(MathF.Pow(this.center.X - x, 2) + MathF.Pow(this.center.Y - y, 2)); return distance / this.radius; } diff --git a/src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs similarity index 87% rename from src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/RecolorBrush.cs index 09a1ff71f..fca95be32 100644 --- a/src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs @@ -15,17 +15,15 @@ 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 + public class RecolorBrush : IBrush { /// - /// Initializes a new instance of the class. + /// 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) + public RecolorBrush(Color sourceColor, Color targetColor, float threshold) { this.SourceColor = sourceColor; this.Threshold = threshold; @@ -43,23 +41,33 @@ namespace SixLabors.ImageSharp.Processing /// /// The color of the source. /// - public TPixel SourceColor { get; } + public Color SourceColor { get; } /// /// Gets the target color. /// - public TPixel TargetColor { get; } + public Color TargetColor { get; } /// - public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options) + where TPixel : struct, IPixel { - return new RecolorBrushApplicator(source, this.SourceColor, this.TargetColor, this.Threshold, options); + return new RecolorBrushApplicator( + source, + this.SourceColor.ToPixel(), + this.TargetColor.ToPixel(), + this.Threshold, + options); } /// /// The recolor brush applicator. /// - private class RecolorBrushApplicator : BrushApplicator + private class RecolorBrushApplicator : BrushApplicator + where TPixel : struct, IPixel { /// /// The source color. @@ -79,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing private readonly TPixel targetColorPixel; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The source image. /// Color of the source. diff --git a/src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/SolidBrush.cs similarity index 86% rename from src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/SolidBrush.cs index 20a6833c4..c62566f6b 100644 --- a/src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/SolidBrush.cs @@ -15,20 +15,18 @@ 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 + public class SolidBrush : IBrush { /// /// The color to paint. /// - private readonly TPixel color; + private readonly Color color; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The color. - public SolidBrush(TPixel color) + public SolidBrush(Color color) { this.color = color; } @@ -39,21 +37,23 @@ namespace SixLabors.ImageSharp.Processing /// /// The color. /// - public TPixel Color => this.color; + public Color Color => this.color; /// - public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + where TPixel : struct, IPixel { - return new SolidBrushApplicator(source, this.color, options); + return new SolidBrushApplicator(source, this.color.ToPixel(), options); } /// /// The solid brush applicator. /// - private class SolidBrushApplicator : BrushApplicator + private class SolidBrushApplicator : BrushApplicator + where TPixel : struct, IPixel { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The source image. /// The color. diff --git a/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs index 3c682a761..7f7332a57 100644 --- a/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs +++ b/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs @@ -1,169 +1,163 @@ -// 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, +// 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 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; set; } + + /// + /// Gets or sets a value indicating the color blending percentage to apply to the drawing operation + /// + public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } + + /// + /// 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; set; } + + /// + /// 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 - }; - } - } + 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 2891598b9..000000000 --- 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/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 63ccea4e6..e0d7a4b18 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -18,11 +18,9 @@ namespace SixLabors.ImageSharp.Advanced /// /// Gets the configuration for the image. /// - /// The Pixel format. - /// The source image + /// The source image. /// Returns the configuration. - public static Configuration GetConfiguration(this Image source) - where TPixel : struct, IPixel + public static Configuration GetConfiguration(this Image source) => GetConfiguration((IConfigurable)source); /// @@ -150,7 +148,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// Gets the assigned to 'source'. /// - /// The source image + /// The source image. /// Returns the configuration. internal static MemoryAllocator GetMemoryAllocator(this IConfigurable source) => GetConfiguration(source).MemoryAllocator; @@ -185,7 +183,7 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// - /// The span returned from Pixel source + /// The span returned from Pixel source. /// private static Span GetSpan(Buffer2D source, int row) where TPixel : struct, IPixel diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index eb6991e6a..82df7304e 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileWuQuantizer() where TPixel : struct, IPixel { - var test = new WuFrameQuantizer(new WuQuantizer(false)); + var test = new WuFrameQuantizer(Configuration.Default.MemoryAllocator, new WuQuantizer(false)); test.QuantizeFrame(new ImageFrame(Configuration.Default, 1, 1)); test.AotGetPalette(); } diff --git a/src/ImageSharp/Advanced/IPixelSource.cs b/src/ImageSharp/Advanced/IPixelSource.cs index 19616d742..a321e877b 100644 --- a/src/ImageSharp/Advanced/IPixelSource.cs +++ b/src/ImageSharp/Advanced/IPixelSource.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Advanced { diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs new file mode 100644 index 000000000..a6e0717f4 --- /dev/null +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.PixelFormats; + +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 000000000..8811516c1 --- /dev/null +++ b/src/ImageSharp/Color/Color.NamedColors.cs @@ -0,0 +1,721 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp +{ + /// + /// Contains static named color values. + /// + public readonly partial struct Color + { + /// + /// 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 = FromRgba(0, 255, 255, 255); + + /// + /// 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 #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 #8FBC8B. + /// + public static readonly Color DarkSeaGreen = FromRgba(143, 188, 139, 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 #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 #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 #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 #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 #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 = FromRgba(255, 0, 255, 255); + + /// + /// 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 #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 #FFFFFF. + /// + public static readonly Color Transparent = FromRgba(255, 255, 255, 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); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Color/Color.WebSafePalette.cs b/src/ImageSharp/Color/Color.WebSafePalette.cs new file mode 100644 index 000000000..8e5fb2a55 --- /dev/null +++ b/src/ImageSharp/Color/Color.WebSafePalette.cs @@ -0,0 +1,166 @@ +// Copyright (c) Six Labors and contributors. +// 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 000000000..768fe065c --- /dev/null +++ b/src/ImageSharp/Color/Color.WernerPalette.cs @@ -0,0 +1,135 @@ +// Copyright (c) Six Labors and contributors. +// 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[] + { + FromHex("#f1e9cd"), + FromHex("#f2e7cf"), + FromHex("#ece6d0"), + FromHex("#f2eacc"), + FromHex("#f3e9ca"), + FromHex("#f2ebcd"), + FromHex("#e6e1c9"), + FromHex("#e2ddc6"), + FromHex("#cbc8b7"), + FromHex("#bfbbb0"), + FromHex("#bebeb3"), + FromHex("#b7b5ac"), + FromHex("#bab191"), + FromHex("#9c9d9a"), + FromHex("#8a8d84"), + FromHex("#5b5c61"), + FromHex("#555152"), + FromHex("#413f44"), + FromHex("#454445"), + FromHex("#423937"), + FromHex("#433635"), + FromHex("#252024"), + FromHex("#241f20"), + FromHex("#281f3f"), + FromHex("#1c1949"), + FromHex("#4f638d"), + FromHex("#383867"), + FromHex("#5c6b8f"), + FromHex("#657abb"), + FromHex("#6f88af"), + FromHex("#7994b5"), + FromHex("#6fb5a8"), + FromHex("#719ba2"), + FromHex("#8aa1a6"), + FromHex("#d0d5d3"), + FromHex("#8590ae"), + FromHex("#3a2f52"), + FromHex("#39334a"), + FromHex("#6c6d94"), + FromHex("#584c77"), + FromHex("#533552"), + FromHex("#463759"), + FromHex("#bfbac0"), + FromHex("#77747f"), + FromHex("#4a475c"), + FromHex("#b8bfaf"), + FromHex("#b2b599"), + FromHex("#979c84"), + FromHex("#5d6161"), + FromHex("#61ac86"), + FromHex("#a4b6a7"), + FromHex("#adba98"), + FromHex("#93b778"), + FromHex("#7d8c55"), + FromHex("#33431e"), + FromHex("#7c8635"), + FromHex("#8e9849"), + FromHex("#c2c190"), + FromHex("#67765b"), + FromHex("#ab924b"), + FromHex("#c8c76f"), + FromHex("#ccc050"), + FromHex("#ebdd99"), + FromHex("#ab9649"), + FromHex("#dbc364"), + FromHex("#e6d058"), + FromHex("#ead665"), + FromHex("#d09b2c"), + FromHex("#a36629"), + FromHex("#a77d35"), + FromHex("#f0d696"), + FromHex("#d7c485"), + FromHex("#f1d28c"), + FromHex("#efcc83"), + FromHex("#f3daa7"), + FromHex("#dfa837"), + FromHex("#ebbc71"), + FromHex("#d17c3f"), + FromHex("#92462f"), + FromHex("#be7249"), + FromHex("#bb603c"), + FromHex("#c76b4a"), + FromHex("#a75536"), + FromHex("#b63e36"), + FromHex("#b5493a"), + FromHex("#cd6d57"), + FromHex("#711518"), + FromHex("#e9c49d"), + FromHex("#eedac3"), + FromHex("#eecfbf"), + FromHex("#ce536b"), + FromHex("#b74a70"), + FromHex("#b7757c"), + FromHex("#612741"), + FromHex("#7a4848"), + FromHex("#3f3033"), + FromHex("#8d746f"), + FromHex("#4d3635"), + FromHex("#6e3b31"), + FromHex("#864735"), + FromHex("#553d3a"), + FromHex("#613936"), + FromHex("#7a4b3a"), + FromHex("#946943"), + FromHex("#c39e6d"), + FromHex("#513e32"), + FromHex("#8b7859"), + FromHex("#9b856b"), + FromHex("#766051"), + FromHex("#453b32") + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs new file mode 100644 index 000000000..2547e6d87 --- /dev/null +++ b/src/ImageSharp/Color/Color.cs @@ -0,0 +1,186 @@ +// Copyright (c) Six Labors and contributors. +// 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; + +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 from the string representing a color in hexadecimal form. + /// + /// + /// 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. + [MethodImpl(InliningOptions.ShortMethod)] + public static Color FromHex(string hex) + { + Rgba32 rgba = Rgba32.FromHex(hex); + + return new Color(rgba); + } + + /// + /// 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) + { + Vector4 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 an + /// implementation defined by . + /// + /// The pixel type to convert to. + /// The pixel value. + [MethodImpl(InliningOptions.ShortMethod)] + public TPixel ToPixel() + where TPixel : struct, IPixel + { + TPixel pixel = default; + pixel.FromRgba64(this.data); + return pixel; + } + + /// + [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(); + } + + /// + /// Bulk convert a span of to a span of a specified pixel type. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ToPixel( + Configuration configuration, + ReadOnlySpan source, + Span destination) + where TPixel : struct, IPixel + { + ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source); + PixelOperations.Instance.FromRgba64(Configuration.Default, rgba64Span, destination); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorSpaces/CieLch.cs index 074bc1516..99d4c09e9 100644 --- a/src/ImageSharp/ColorSpaces/CieLch.cs +++ b/src/ImageSharp/ColorSpaces/CieLch.cs @@ -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/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs index 49c1da9f1..8e14274f6 100644 --- a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.ColorSpaces { /// - /// Represents the coordinates of CIEXY chromaticity space + /// Represents the coordinates of CIEXY chromaticity space. /// public readonly struct CieXyChromaticityCoordinates : IEquatable { diff --git a/src/ImageSharp/ColorSpaces/CieXyz.cs b/src/ImageSharp/ColorSpaces/CieXyz.cs index 30521476e..6c5adcb21 100644 --- a/src/ImageSharp/ColorSpaces/CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/CieXyz.cs @@ -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 04901126c..c2331c379 100644 --- a/src/ImageSharp/ColorSpaces/Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -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 13cca1582..09b324b00 100644 --- a/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs @@ -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 9e2cf8ad8..80b8aee7e 100644 --- a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs @@ -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 a3a912172..773fe8164 100644 --- a/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs @@ -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 e2e802d08..edc0b9763 100644 --- a/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs @@ -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 5ae462913..cc30c3632 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -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/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs index 3c197673d..bfabbb21d 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs @@ -19,7 +19,7 @@ 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 @@ -28,12 +28,11 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion // Conversion (perserving 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 +53,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -66,7 +65,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 +86,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -99,7 +98,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 +119,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -132,7 +131,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 +158,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 +196,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 +217,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -232,7 +229,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 +250,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -264,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 @@ -285,7 +282,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -297,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 @@ -318,7 +315,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -330,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 @@ -351,7 +348,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -363,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 @@ -384,7 +381,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -396,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 @@ -429,7 +426,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 diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs index 0a8607e3b..7f4abfa7b 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs @@ -26,15 +26,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 +53,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -67,7 +65,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 +86,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -100,7 +98,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 +119,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -133,7 +131,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 +164,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 +185,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -198,7 +196,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 +217,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -231,7 +229,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 +250,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -264,7 +262,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 +283,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -297,7 +295,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 +316,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -330,7 +328,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 +349,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -363,7 +361,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 +382,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -396,7 +394,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 +415,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -429,7 +427,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 diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs index 3a779ee72..6ca40af03 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs @@ -32,7 +32,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 +53,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -65,7 +65,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 +86,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 +119,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -133,7 +131,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 +152,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -166,7 +164,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 +185,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -199,7 +197,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 +218,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -253,7 +251,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -265,7 +263,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 +284,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -298,7 +296,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 +317,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -331,7 +329,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 +350,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -364,7 +362,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 +383,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -397,7 +395,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 +416,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -429,7 +427,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 diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs index 90eb8e34d..316b35f37 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs @@ -17,7 +17,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 +28,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 +49,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -60,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 @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -95,7 +95,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 +116,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -127,7 +127,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 +148,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -162,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 @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -194,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 @@ -226,7 +226,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 +247,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -258,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 @@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -290,7 +290,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 +311,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -322,7 +322,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 +343,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -354,7 +354,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 +375,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -386,7 +386,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 +407,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -418,10 +418,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)); diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs index fada6d9c5..21d576fb4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion private LinearRgbToCieXyzConverter linearRgbToCieXyzConverter; /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -38,7 +38,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 +59,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -73,7 +73,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 +94,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -108,7 +108,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 +143,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 +164,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -175,7 +175,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 +196,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 +229,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 +262,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -277,7 +275,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 +296,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 +329,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -343,15 +339,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 +364,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 +434,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 +465,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) { diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs index b79851635..00e20e25b 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs @@ -17,7 +17,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 +29,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 +50,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -62,7 +62,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 +149,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -161,7 +161,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 +182,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -227,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 @@ -248,7 +248,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -260,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 @@ -281,7 +281,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -293,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 @@ -314,7 +314,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -326,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 @@ -347,7 +347,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -359,9 +359,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 +380,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -408,7 +408,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -420,7 +420,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 diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs index a7080b974..76175f1cb 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs @@ -17,7 +17,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 +29,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 +50,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -62,7 +62,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 +83,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -95,7 +95,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 +116,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -128,7 +128,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 +149,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -281,7 +281,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -293,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 @@ -314,7 +314,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -326,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 @@ -347,7 +347,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -359,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 @@ -387,7 +387,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 +408,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -420,7 +420,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 diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs index a2121203c..e64beb3e4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs @@ -263,7 +263,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)); diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs index e5996c238..91e5549ac 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs @@ -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 eef626be2..4be3f0079 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -17,10 +17,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 +38,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 +59,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 +80,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 +101,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 +122,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 +143,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 +164,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 +185,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 +206,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 +227,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 +248,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 +269,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 +290,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -301,7 +301,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -312,7 +312,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -323,7 +323,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -345,7 +345,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -359,7 +359,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -370,7 +370,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -381,7 +381,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -392,7 +392,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -403,7 +403,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -425,7 +425,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.YCbCr.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs index 5780f4f54..68cd34bf2 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs @@ -17,10 +17,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 +38,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 +59,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 +101,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 +122,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 +143,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 +164,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 +185,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 +206,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 +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 @@ -248,7 +248,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 +269,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -281,7 +281,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -293,7 +293,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -329,7 +329,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -353,7 +353,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -365,7 +365,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -389,7 +389,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Converts a into a . /// /// The color to convert. /// The @@ -401,7 +401,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.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs index fe6a57f7a..bcbd64c77 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs @@ -18,7 +18,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; diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs index fcd031e26..65fe79994 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs @@ -40,7 +40,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. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs index f33d1ddcc..7767b7b44 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs @@ -7,7 +7,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// 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/CieXyzToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs index 3812cdbdd..6497e3060 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs @@ -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 29fd32905..0700dab43 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// 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/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs index a93773262..bdf451cd3 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs @@ -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; diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs index 1030ac981..21a96071a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs @@ -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. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs index 1cc055bee..845443093 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// 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 03912a421..4ddbe42e5 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// 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 4ac3ad3cf..ee15ffa50 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs @@ -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/RGBPrimariesChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs index 4c69133e0..e28f3f1a4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Represents the chromaticity coordinates of RGB primaries. - /// One of the specifiers of . + /// One of the specifiers of . /// public readonly struct RgbPrimariesChromaticityCoordinates : IEquatable { diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs index 639d0b293..6caca54cd 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// /// The gamma working space. /// - public class GammaWorkingSpace : RgbWorkingSpaceBase + public sealed class GammaWorkingSpace : RgbWorkingSpace { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs index 16617ea24..a2eb42ad0 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// /// L* working space. /// - public sealed class LWorkingSpace : RgbWorkingSpaceBase + public sealed class LWorkingSpace : RgbWorkingSpace { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs index 9ba1ff881..a794b3dda 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// /// 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. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs index 88623e958..ffa9699bc 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// /// 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. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs similarity index 91% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs index 6051f5286..a97ae22b1 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs @@ -6,16 +6,16 @@ using System; namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// Base class for all implementations of . + /// Base class for all implementations of . /// - public abstract class RgbWorkingSpaceBase + public abstract class RgbWorkingSpace { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The reference white point. /// The chromaticity of the rgb primaries. - protected RgbWorkingSpaceBase(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + protected RgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) { this.WhitePoint = referenceWhite; this.ChromaticityCoordinates = chromaticityCoordinates; @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return true; } - if (obj is RgbWorkingSpaceBase other) + if (obj is RgbWorkingSpace other) { return this.WhitePoint.Equals(other.WhitePoint) && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs index b44db0681..c3d850251 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation /// /// 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 index 85a36331b..a4d96d19e 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs @@ -10,13 +10,13 @@ using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// - /// Basic implementation of the von Kries chromatic adaptation model + /// Implementation of the von Kries chromatic adaptation model. /// /// /// Transformation described here: /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html /// - public class VonKriesChromaticAdaptation : IChromaticAdaptation + public sealed class VonKriesChromaticAdaptation : IChromaticAdaptation { private readonly CieXyzAndLmsConverter converter; diff --git a/src/ImageSharp/ColorSpaces/HunterLab.cs b/src/ImageSharp/ColorSpaces/HunterLab.cs index dcae65e42..402761d8c 100644 --- a/src/ImageSharp/ColorSpaces/HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/HunterLab.cs @@ -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 ed385e02c..d5c1b3eed 100644 --- a/src/ImageSharp/ColorSpaces/Illuminants.cs +++ b/src/ImageSharp/ColorSpaces/Illuminants.cs @@ -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 46b227596..7ef50e9c4 100644 --- a/src/ImageSharp/ColorSpaces/LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs @@ -9,7 +9,7 @@ using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; 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); diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs index cdc9e90ad..bb71deba3 100644 --- a/src/ImageSharp/ColorSpaces/Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Rgb.cs @@ -10,17 +10,17 @@ 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; diff --git a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs index ee3822c15..3f40fa4f5 100644 --- a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs +++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs @@ -8,8 +8,7 @@ using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; 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/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 89877b1b6..8b9dbe1b8 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 and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,14 +9,14 @@ namespace SixLabors.ImageSharp /// The exception that is thrown when the library tries to load /// an image, which has an invalid format. /// - 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/UnknownImageFormatException.cs b/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs new file mode 100644 index 000000000..ca6e1fe6e --- /dev/null +++ b/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp +{ + /// + /// The exception that is thrown when the library tries to load + /// an image which has an unkown 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) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public UnknownImageFormatException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } + } +} diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs index e97a2eaed..e28db7cff 100644 --- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs @@ -45,7 +45,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. @@ -65,7 +65,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. diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index a200ffebc..505ecccdd 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using SixLabors.ImageSharp.Memory; @@ -82,5 +83,25 @@ namespace SixLabors.ImageSharp { stream.Write(buffer.Array, 0, buffer.Length()); } + +#if NET472 || NETSTANDARD1_3 || NETSTANDARD2_0 + // This is a port of the CoreFX implementation and is MIT Licensed: https://github.com/dotnet/coreclr/blob/c4dca1072d15bdda64c754ad1ea474b1580fa554/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L770 + public static void Write(this Stream stream, ReadOnlySpan buffer) + { + // 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/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 2e700c9d6..427b24005 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; @@ -11,83 +12,120 @@ 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) + int minColumn, + int maxColumn) where TPixel : struct, 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); - - vector += matrix[y, x] * currentColor; - } - } + Convolve2DImpl( + in matrixY, + in matrixX, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + ref vector); - ref Vector4 target = ref targetRow[column]; + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; + Vector4Utils.UnPremultiply(ref vector); target = vector; } /// - /// Computes the sum of vectors in 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 : struct, IPixel + { + Vector4 vector = default; + + Convolve2DImpl( + in matrixY, + in matrixX, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + ref vector); + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve2DImpl( + in DenseMatrix matrixY, + in DenseMatrix matrixX, + Buffer2D sourcePixels, + int row, + int column, + int minRow, + int maxRow, + int minColumn, int maxColumn, - int offsetColumn) + ref Vector4 vector) where TPixel : struct, IPixel { Vector4 vectorY = default; @@ -96,16 +134,16 @@ 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); @@ -114,11 +152,133 @@ namespace SixLabors.ImageSharp } } - 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 : struct, IPixel + { + Vector4 vector = default; + + ConvolveImpl( + in matrix, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + ref vector); + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; + Vector4Utils.UnPremultiply(ref vector); target = vector; } + + /// + /// Computes the sum of vectors in the span referenced by weighted by the kernel weight values. + /// Using this method the convolution filter is applied to alpha in addition to the color channels. + /// + /// The pixel format. + /// The dense matrix. + /// The source frame. + /// The target row base reference. + /// The current row. + /// The current column. + /// The minimum working area row. + /// The maximum working area row. + /// The minimum working area column. + /// The maximum working area column. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve4( + in DenseMatrix matrix, + Buffer2D sourcePixels, + ref Vector4 targetRowRef, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn) + where TPixel : struct, IPixel + { + Vector4 vector = default; + + ConvolveImpl( + in matrix, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + ref vector); + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void ConvolveImpl( + in DenseMatrix matrix, + Buffer2D sourcePixels, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn, + ref Vector4 vector) + where TPixel : struct, IPixel + { + int matrixHeight = matrix.Rows; + int matrixWidth = matrix.Columns; + int radiusY = matrixHeight >> 1; + int radiusX = matrixWidth >> 1; + int sourceOffsetColumnBase = column + minColumn; + + for (int y = 0; y < matrixHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(minRow, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixWidth; x++) + { + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + vector += matrix[y, x] * currentColor; + } + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index d8cf69a52..e86da78e3 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -257,6 +257,26 @@ namespace SixLabors.ImageSharp } } + /// + /// Verifies that the 'destination' span is not shorter than 'source'. + /// + /// The source element type + /// The destination element type + /// The source span + /// The destination span + /// The name of the argument for 'destination' + [MethodImpl(InliningOptions.ShortMethod)] + public static void DestinationShouldNotBeTooShort( + Span source, + Span destination, + string destinationParamName) + { + if (destination.Length < source.Length) + { + ThrowArgumentException($"Destination span is too short!", destinationParamName); + } + } + /// /// Verifies, that the `source` span has the length of 'minLength', or longer. /// diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 0c5b05180..7460c9cac 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -5,7 +5,6 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.Primitives; namespace SixLabors.ImageSharp @@ -37,6 +36,17 @@ namespace SixLabors.ImageSharp public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F)); + /// + /// 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)); + /// /// Scales a value from a 16 bit to it's 8 bit equivalent. /// @@ -380,4 +390,4 @@ namespace SixLabors.ImageSharp return GetBoundingRectangle(topLeft, bottomRight); } } -} \ 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 9aeb20931..83216aaa7 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs @@ -1,4 +1,7 @@ -using System; +// 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; diff --git a/src/ImageSharp/Common/Helpers/TestHelpers.cs b/src/ImageSharp/Common/Helpers/TestHelpers.cs index fd16a577a..d330233c4 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 and contributors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Common.Helpers diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index c8b25bf56..75dbb032c 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -2,8 +2,8 @@ // 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 { diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs index 75bb00b6a..a4e0921d0 100644 --- a/src/ImageSharp/Common/Helpers/Vector4Utils.cs +++ b/src/ImageSharp/Common/Helpers/Vector4Utils.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp { @@ -70,5 +71,41 @@ namespace SixLabors.ImageSharp 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 index 0b45719c3..40163bc78 100644 --- a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs +++ b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs @@ -1,4 +1,4 @@ -// Copyright(c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Threading.Tasks; diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs index 782333219..2c2d045a5 100644 --- a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs @@ -1,4 +1,4 @@ -// Copyright(c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.ParallelUtils in ParallelExecutionSettings parallelSettings, Action body) { - DebugGuard.MustBeGreaterThan(rectangle.Width, 0, nameof(rectangle)); + ValidateRectangle(rectangle); int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); @@ -87,6 +87,8 @@ namespace SixLabors.ImageSharp.ParallelUtils Action> body) where T : unmanaged { + ValidateRectangle(rectangle); + int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); @@ -142,5 +144,18 @@ namespace SixLabors.ImageSharp.ParallelUtils [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); + + private static void ValidateRectangle(Rectangle rectangle) + { + Guard.MustBeGreaterThan( + rectangle.Width, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); + + Guard.MustBeGreaterThan( + rectangle.Height, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Tuples/Octet.cs b/src/ImageSharp/Common/Tuples/Octet.cs index 539b74e32..7ece2ed56 100644 --- a/src/ImageSharp/Common/Tuples/Octet.cs +++ b/src/ImageSharp/Common/Tuples/Octet.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// 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 @@ -9,7 +12,7 @@ namespace SixLabors.ImageSharp.Tuples internal static class Octet { /// - /// Value tuple of -s + /// Value tuple of -s. /// [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] public struct OfUInt32 diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs index cae283d62..b3a32deee 100644 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -37,12 +40,11 @@ namespace SixLabors.ImageSharp.Tuples this.B += other.B; } - /// - /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! - /// TODO: Move it somewhere else. + /// . + /// 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() + internal void RoundAndDownscalePreAvx2(float downscaleFactor) { ref Vector a = ref Unsafe.As>(ref this.A); a = a.FastRound(); @@ -50,8 +52,8 @@ namespace SixLabors.ImageSharp.Tuples ref Vector b = ref Unsafe.As>(ref this.B); b = b.FastRound(); - // Downscale by 1/255 - var scale = new Vector4(1 / 255f); + // Downscale by 1/factor + var scale = new Vector4(1 / downscaleFactor); this.A *= scale; this.B *= scale; } @@ -61,14 +63,14 @@ namespace SixLabors.ImageSharp.Tuples /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleAvx2() + internal void RoundAndDownscaleAvx2(float downscaleFactor) { ref Vector self = ref Unsafe.As>(ref this); Vector v = self; v = v.FastRound(); - // Downscale by 1/255 - v *= new Vector(1 / 255f); + // Downscale by 1/factor + v *= new Vector(1 / downscaleFactor); self = v; } diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index c0064d187..4e8284c2c 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -102,6 +102,15 @@ namespace SixLabors.ImageSharp /// internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); + /// + /// Gets or sets the working buffer size hint for image processors. + /// The default value is 1MB. + /// + /// + /// Currently only used by Resize. + /// + internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; + /// /// Gets or sets the image operations provider factory. /// @@ -118,9 +127,9 @@ namespace SixLabors.ImageSharp } /// - /// Creates a shallow copy of the + /// Creates a shallow copy of the . /// - /// A new configuration instance + /// A new configuration instance. public Configuration Clone() { return new Configuration @@ -130,18 +139,19 @@ namespace SixLabors.ImageSharp MemoryAllocator = this.MemoryAllocator, ImageOperationsProvider = this.ImageOperationsProvider, ReadOrigin = this.ReadOrigin, - FileSystem = this.FileSystem + FileSystem = this.FileSystem, + WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, }; } /// /// Creates the default instance with the following s preregistered: - /// - /// - /// - /// + /// + /// + /// + /// . /// - /// The default configuration of + /// The default configuration of . internal static Configuration CreateDefaultInstance() { return new Configuration( diff --git a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs new file mode 100644 index 000000000..e8afb422a --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// 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 618999c87..6e1145beb 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -4,10 +4,20 @@ 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 ef063f010..27a0e121b 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 and contributors. // 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,28 +21,25 @@ 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. - /// Not supported at the moment. /// RLE8 = 1, /// /// 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, /// /// Each image row has a multiple of four elements. If the /// row has less elements, zeros will be added at the right side. - /// Not supported at the moment. /// BitFields = 3, @@ -56,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. + /// + RLE24 = 100, } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs index 99799b619..5cbed4af2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -19,5 +19,41 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The list of file extensions that equate to a bmp. /// public static readonly IEnumerable FileExtensions = new[] { "bm", "bmp", "dip" }; + + /// + /// Valid magic bytes markers identifying a Bitmap file. + /// + internal static class TypeMarkers + { + /// + /// Single-image BMP file that may have been created under Windows or OS/2. + /// + public const int Bitmap = 0x4D42; + + /// + /// OS/2 Bitmap Array. + /// + public const int BitmapArray = 0x4142; + + /// + /// OS/2 Color Icon. + /// + public const int ColorIcon = 0x4943; + + /// + /// OS/2 Color Pointer. + /// + public const int ColorPointer = 0x5043; + + /// + /// OS/2 Icon. + /// + public const int Icon = 0x4349; + + /// + /// OS/2 Pointer. + /// + public const int Pointer = 0x5450; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 3d079cf61..a404ab418 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -14,15 +14,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// JPG /// PNG - /// RLE4 - /// RLE8 - /// BitFields + /// 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 @@ -32,6 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp return new BmpDecoderCore(configuration, this).Decode(stream); } + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + /// public IImageInfo Identify(Configuration configuration, Stream stream) { diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 68528edcd..906fc53fe 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1,20 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.IO; +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Performs the bmp decoding operation. + /// Performs the bitmap decoding operation. /// /// /// A useful decoding source example can be found at @@ -22,37 +24,37 @@ namespace SixLabors.ImageSharp.Formats.Bmp internal sealed class BmpDecoderCore { /// - /// The mask for the red part of the color for 16 bit rgb bitmaps. + /// The default mask for the red part of the color for 16 bit rgb bitmaps. /// - private const int Rgb16RMask = 0x7C00; + private const int DefaultRgb16RMask = 0x7C00; /// - /// The mask for the green part of the color for 16 bit rgb bitmaps. + /// The default mask for the green part of the color for 16 bit rgb bitmaps. /// - private const int Rgb16GMask = 0x3E0; + private const int DefaultRgb16GMask = 0x3E0; /// - /// The mask for the blue part of the color for 16 bit rgb bitmaps. + /// The default mask for the blue part of the color for 16 bit rgb bitmaps. /// - private const int Rgb16BMask = 0x1F; + 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; @@ -62,34 +64,55 @@ namespace SixLabors.ImageSharp.Formats.Bmp private Stream stream; /// - /// The metadata + /// The metadata. /// - private ImageMetaData metaData; + private ImageMetadata metadata; + + /// + /// The bitmap specific metadata. + /// + 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; + /// + /// The global configuration. + /// 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. /// /// The configuration. - /// The options + /// The options. public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) { this.configuration = configuration; this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; } /// @@ -110,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { 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(); @@ -119,7 +142,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp case BmpCompression.RGB: if (this.infoHeader.BitsPerPixel == 32) { - this.ReadRgb32(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3) + { + this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else + { + this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } } else if (this.infoHeader.BitsPerPixel == 24) { @@ -142,12 +172,28 @@ namespace SixLabors.ImageSharp.Formats.Bmp } break; + + case BmpCompression.RLE24: + this.ReadRle24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + + break; + case BmpCompression.RLE8: - this.ReadRle8(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); + case BmpCompression.RLE4: + this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); break; + + case BmpCompression.BitFields: + case BmpCompression.BI_ALPHABITFIELDS: + this.ReadBitFields(pixels, inverted); + + break; + default: - throw new NotSupportedException("Does not support this kind of bitmap files."); + BmpThrowHelper.ThrowNotSupportedException("Does not support this kind of bitmap files."); + + break; } return image; @@ -165,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public IImageInfo Identify(Stream stream) { 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); } /// @@ -184,7 +230,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The image width. /// The pixel component count. /// - /// The . + /// The padding. /// private static int CalculatePadding(int width, int componentCount) { @@ -199,30 +245,69 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Performs final shifting from a 5bit value to an 8bit one. + /// 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 masked and shifted value - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); + /// 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 + { + if (this.infoHeader.BitsPerPixel == 16) + { + this.ReadRgb16( + pixels, + this.infoHeader.Width, + this.infoHeader.Height, + inverted, + this.infoHeader.RedMask, + this.infoHeader.GreenMask, + this.infoHeader.BlueMask); + } + else + { + this.ReadRgb32BitFields( + pixels, + this.infoHeader.Width, + this.infoHeader.Height, + inverted, + this.infoHeader.RedMask, + this.infoHeader.GreenMask, + this.infoHeader.BlueMask, + this.infoHeader.AlphaMask); + } + } /// - /// Looks up color values and builds the image from de-compressed RLE8 data. - /// Compressed RLE8 stream is uncompressed by + /// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data. + /// Compressed RLE8 stream is uncompressed by + /// Compressed RLE4 stream is uncompressed by /// /// The pixel format. + /// The compression type. Either RLE4 or RLE8. /// The to assign the palette to. /// The containing the colors. /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRle8(Buffer2D pixels, byte[] colors, int width, int height, bool inverted) + private void ReadRle(BmpCompression compression, Buffer2D pixels, byte[] colors, int width, int height, bool inverted) where TPixel : struct, IPixel { TPixel color = default; using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) { - this.UncompressRle8(width, buffer.GetSpan()); + Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; + if (compression == BmpCompression.RLE8) + { + this.UncompressRle8(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + } + else + { + this.UncompressRle4(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + } for (int y = 0; y < height; y++) { @@ -230,26 +315,133 @@ namespace SixLabors.ImageSharp.Formats.Bmp Span bufferRow = buffer.GetRowSpan(y); 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 (undefinedPixels[x, y]) + { + 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 : struct, IPixel + { + TPixel color = default; + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean)) + using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) + { + Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSpan(), 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. + for (int x = 0; x < width; x++) + { + int idx = (y * width * 3) + (x * 3); + if (undefinedPixels[x, y]) + { + 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. + for (int x = 0; x < width; x++) + { + int idx = (y * width * 3) + (x * 3); + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + pixelRow[x] = color; + } } } } } /// - /// Produce uncompressed bitmap data from RLE8 stream + /// Produce uncompressed bitmap data from a RLE4 stream. /// /// - /// RLE8 is a 2-byte run-length encoding - ///
If first byte is 0, the second byte may have special meaning - ///
Otherwise, first byte is the length of the run and second byte is the color for the run + /// RLE4 is a 2-byte run-length encoding. + ///
If first byte is 0, the second byte may have special meaning. + ///
Otherwise, the first byte is the length of the run and second byte contains two color indexes. ///
/// The width of the bitmap. /// Buffer for uncompressed data. - private void UncompressRle8(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]; @@ -262,7 +454,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - throw new Exception("Failed to read 2 bytes from stream"); + BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); } if (cmd[0] == RleCommand) @@ -270,27 +462,137 @@ 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 += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); + + break; + + case RleDelta: + int dx = this.stream.ReadByte(); + int dy = this.stream.ReadByte(); + count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); + + break; + + default: + // If the second byte > 2, we are in 'absolute mode'. + // The second byte contains the number of color indexes that follow. + int max = cmd[1]; + int bytesToRead = (max + 1) / 2; + + byte[] run = new byte[bytesToRead]; + + this.stream.Read(run, 0, run.Length); + + int idx = 0; + for (int i = 0; i < max; i++) { - count += w - extra; + byte twoPixels = run[idx]; + if (i % 2 == 0) + { + byte leftPixel = (byte)((twoPixels >> 4) & 0xF); + buffer[count++] = leftPixel; + } + else + { + byte rightPixel = (byte)(twoPixels & 0xF); + buffer[count++] = rightPixel; + idx++; + } } + // Absolute mode data is aligned to two-byte word-boundary. + int padding = bytesToRead & 1; + + this.stream.Skip(padding); + + break; + } + } + else + { + int max = cmd[0]; + + // The second byte contains two color indexes, one in its high-order 4 bits and one in its low-order 4 bits. + byte twoPixels = cmd[1]; + byte rightPixel = (byte)(twoPixels & 0xF); + byte leftPixel = (byte)((twoPixels >> 4) & 0xF); + + for (int idx = 0; idx < max; idx++) + { + if (idx % 2 == 0) + { + buffer[count] = leftPixel; + } + else + { + buffer[count] = rightPixel; + } + + count++; + } + } + } + } + + /// + /// Produce uncompressed bitmap data from a RLE8 stream. + /// + /// + /// RLE8 is a 2-byte run-length encoding. + ///
If first byte is 0, the second byte may have special meaning. + ///
Otherwise, the first byte is the length of the run and second byte is the color for the run. + ///
+ /// The width of the bitmap. + /// Buffer for uncompressed data. + /// Keeps track of skipped and therefore undefined pixels. + /// Keeps track of rows, which have undefined pixels. + private void UncompressRle8(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."); + } + + if (cmd[0] == RleCommand) + { + switch (cmd[1]) + { + case RleEndOfBitmap: + int skipEoB = buffer.Length - count; + RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); + + return; + + case RleEndOfLine: + count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); + break; case RleDelta: int dx = 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]; @@ -301,7 +603,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); @@ -312,16 +614,193 @@ 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) + { +#if NETCOREAPP2_1 + Span cmd = stackalloc byte[2]; +#else + byte[] cmd = new byte[2]; +#endif + int uncompressedPixels = 0; + + while (uncompressedPixels < buffer.Length) + { + if (this.stream.Read(cmd, 0, cmd.Length) != 2) + { + BmpThrowHelper.ThrowImageFormatException("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]; + + byte[] 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. /// @@ -337,7 +816,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void ReadRgbPalette(Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted) where TPixel : struct, IPixel { - // Pixels per byte (bits per pixel) + // Pixels per byte (bits per pixel). int ppb = 8 / bitsPerPixel; int arrayWidth = (width + ppb - 1) / ppb; @@ -345,7 +824,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) { @@ -382,20 +861,32 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Reads the 16 bit color palette from the stream + /// Reads the 16 bit color palette from the stream. /// /// The pixel format. /// The to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb16(Buffer2D pixels, int width, int height, bool inverted) + /// The bitmask for the red channel. + /// The bitmask for the green channel. + /// The bitmask for the blue channel. + private void ReadRgb16(Buffer2D pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask) where TPixel : struct, IPixel { int padding = CalculatePadding(width, 2); int stride = (width * 2) + padding; TPixel color = default; + int rightShiftRedMask = CalculateRightShift((uint)redMask); + int rightShiftGreenMask = CalculateRightShift((uint)greenMask); + int rightShiftBlueMask = CalculateRightShift((uint)blueMask); + + // Each color channel contains either 5 or 6 Bits values. + int redMaskBits = CountBits((uint)redMask); + int greenMaskBits = CountBits((uint)greenMask); + int blueMaskBits = CountBits((uint)blueMask); + using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) { for (int y = 0; y < height; y++) @@ -409,10 +900,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp { short temp = BitConverter.ToInt16(buffer.Array, offset); - var rgb = new Rgb24( - GetBytesFrom5BitValue((temp & Rgb16RMask) >> 10), - GetBytesFrom5BitValue((temp & Rgb16GMask) >> 5), - GetBytesFrom5BitValue(temp & Rgb16BMask)); + // Rescale values, so the values range from 0 to 255. + int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask); + int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask); + int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask); + var rgb = new Rgb24((byte)r, (byte)g, (byte)b); color.FromRgb24(rgb); pixelRow[x] = color; @@ -423,7 +915,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Reads the 24 bit color palette from the stream + /// Performs final shifting from a 5bit value to an 8bit one. + /// + /// The masked and shifted value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); + + /// + /// Performs final shifting from a 6bit value to an 8bit one. + /// + /// The masked and shifted value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4)); + + /// + /// Reads the 24 bit color palette from the stream. /// /// The pixel format. /// The to assign the palette to. @@ -452,14 +960,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Reads the 32 bit color palette from the stream + /// Reads the 32 bit color palette from the stream. /// /// The pixel format. /// The to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb32(Buffer2D pixels, int width, int height, bool inverted) + private void ReadRgb32Fast(Buffer2D pixels, int width, int height, bool inverted) where TPixel : struct, IPixel { int padding = CalculatePadding(width, 4); @@ -480,6 +988,224 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } + /// + /// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel. + /// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb32Slow(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : struct, IPixel + { + int padding = CalculatePadding(width, 4); + + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) + using (IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width)) + { + Span bgraRowSpan = bgraRow.GetSpan(); + long currentPosition = this.stream.Position; + bool hasAlpha = false; + + // Loop though the rows checking each pixel. We start by assuming it's + // an BGR0 image. If we hit a non-zero alpha value, then we know it's + // actually a BGRA image, and change tactics accordingly. + for (int y = 0; y < height; y++) + { + this.stream.Read(row); + + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + row.GetSpan(), + bgraRowSpan, + width); + + // Check each pixel in the row to see if it has an alpha value. + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + if (bgra.A > 0) + { + hasAlpha = true; + break; + } + } + + if (hasAlpha) + { + break; + } + } + + // Reset our stream for a second pass. + this.stream.Position = currentPosition; + + // Process the pixels in bulk taking the raw alpha component value. + if (hasAlpha) + { + for (int y = 0; y < height; y++) + { + this.stream.Read(row); + + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + row.GetSpan(), + pixelSpan, + width); + } + + return; + } + + // Slow path. We need to set each alpha component value to fully opaque. + for (int y = 0; y < height; y++) + { + this.stream.Read(row); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + row.GetSpan(), + bgraRowSpan, + width); + + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + bgra.A = byte.MaxValue; + ref TPixel pixel = ref pixelSpan[x]; + pixel.FromBgra32(bgra); + } + } + } + } + + /// + /// Decode an 32 Bit Bitmap containing a bitmask for each color channel. + /// + /// The pixel format. + /// The output pixel buffer containing the decoded image. + /// The width of the image. + /// The height of the image. + /// Whether the bitmap is inverted. + /// The bitmask for the red channel. + /// The bitmask for the green channel. + /// The bitmask for the blue channel. + /// The bitmask for the alpha channel. + private void ReadRgb32BitFields(Buffer2D pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask) + where TPixel : struct, IPixel + { + TPixel color = default; + int padding = CalculatePadding(width, 4); + int stride = (width * 4) + padding; + + int rightShiftRedMask = CalculateRightShift((uint)redMask); + int rightShiftGreenMask = CalculateRightShift((uint)greenMask); + int rightShiftBlueMask = CalculateRightShift((uint)blueMask); + int rightShiftAlphaMask = CalculateRightShift((uint)alphaMask); + + int bitsRedMask = CountBits((uint)redMask); + int bitsGreenMask = CountBits((uint)greenMask); + int bitsBlueMask = CountBits((uint)blueMask); + int bitsAlphaMask = CountBits((uint)alphaMask); + float invMaxValueRed = 1.0f / (0xFFFFFFFF >> (32 - bitsRedMask)); + float invMaxValueGreen = 1.0f / (0xFFFFFFFF >> (32 - bitsGreenMask)); + float invMaxValueBlue = 1.0f / (0xFFFFFFFF >> (32 - bitsBlueMask)); + uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask); + float invMaxValueAlpha = 1.0f / maxValueAlpha; + + bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8; + + using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) + { + for (int y = 0; y < height; y++) + { + this.stream.Read(buffer.Array, 0, stride); + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.GetRowSpan(newY); + + int offset = 0; + for (int x = 0; x < width; x++) + { + uint temp = BitConverter.ToUInt32(buffer.Array, offset); + + if (unusualBitMask) + { + uint r = (uint)(temp & redMask) >> rightShiftRedMask; + uint g = (uint)(temp & greenMask) >> rightShiftGreenMask; + uint b = (uint)(temp & blueMask) >> rightShiftBlueMask; + float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f; + var vector4 = new Vector4( + r * invMaxValueRed, + g * invMaxValueGreen, + b * invMaxValueBlue, + alpha); + color.FromVector4(vector4); + } + else + { + byte r = (byte)((temp & redMask) >> rightShiftRedMask); + byte g = (byte)((temp & greenMask) >> rightShiftGreenMask); + byte b = (byte)((temp & blueMask) >> rightShiftBlueMask); + byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255; + color.FromRgba32(new Rgba32(r, g, b, a)); + } + + pixelRow[x] = color; + offset += 4; + } + } + } + } + + /// + /// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right). + /// + /// The color bit mask. + /// Number of bits to shift right. + private static int CalculateRightShift(uint n) + { + int count = 0; + while (n > 0) + { + if ((1 & n) == 0) + { + count++; + } + else + { + break; + } + + n >>= 1; + } + + return count; + } + + /// + /// Counts none zero bits. + /// + /// A color mask. + /// The none zero bits. + private static int CountBits(uint n) + { + int count = 0; + while (n != 0) + { + count++; + n &= n - 1; + } + + return count; + } + /// /// Reads the from the stream. /// @@ -490,46 +1216,91 @@ namespace SixLabors.ImageSharp.Formats.Bmp #else byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize]; #endif - this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); // read the header size - int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); - if (headerSize < BmpInfoHeader.CoreSize) - { - throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); - } + // Read the header size. + this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); - int skipAmount = 0; - if (headerSize > BmpInfoHeader.MaxHeaderSize) + int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); + if (headerSize < BmpInfoHeader.CoreSize || headerSize > BmpInfoHeader.MaxHeaderSize) { - skipAmount = headerSize - BmpInfoHeader.MaxHeaderSize; - headerSize = BmpInfoHeader.MaxHeaderSize; + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize is '{headerSize}'."); } - // 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; if (headerSize == BmpInfoHeader.CoreSize) { // 12 bytes + infoHeaderType = BmpInfoHeaderType.WinVersion2; this.infoHeader = BmpInfoHeader.ParseCore(buffer); } else if (headerSize == BmpInfoHeader.Os22ShortSize) { // 16 bytes + infoHeaderType = BmpInfoHeaderType.Os2Version2Short; this.infoHeader = BmpInfoHeader.ParseOs22Short(buffer); } - else if (headerSize >= BmpInfoHeader.Size) + else if (headerSize == BmpInfoHeader.SizeV3) + { + // == 40 bytes + infoHeaderType = BmpInfoHeaderType.WinVersion3; + this.infoHeader = BmpInfoHeader.ParseV3(buffer); + + // If the info header is BMP version 3 and the compression type is BITFIELDS, + // color masks for each color channel follow the info header. + if (this.infoHeader.Compression == BmpCompression.BitFields) + { + byte[] bitfieldsBuffer = new byte[12]; + this.stream.Read(bitfieldsBuffer, 0, 12); + Span data = bitfieldsBuffer.AsSpan(); + this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)); + this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); + this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); + } + else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS) + { + byte[] bitfieldsBuffer = new byte[16]; + this.stream.Read(bitfieldsBuffer, 0, 16); + Span data = bitfieldsBuffer.AsSpan(); + this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)); + this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); + this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); + this.infoHeader.AlphaMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(12, 4)); + } + } + else if (headerSize == BmpInfoHeader.AdobeV3Size) { - // >= 40 bytes - this.infoHeader = BmpInfoHeader.Parse(buffer); + // == 52 bytes + infoHeaderType = BmpInfoHeaderType.AdobeVersion3; + this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: false); + } + else if (headerSize == BmpInfoHeader.AdobeV3WithAlphaSize) + { + // == 56 bytes + infoHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha; + this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: true); + } + else if (headerSize == BmpInfoHeader.Os2v2Size) + { + // == 64 bytes + infoHeaderType = BmpInfoHeaderType.Os2Version2; + this.infoHeader = BmpInfoHeader.ParseOs2Version2(buffer); + } + else if (headerSize >= BmpInfoHeader.SizeV4) + { + // >= 108 bytes + infoHeaderType = headerSize == BmpInfoHeader.SizeV4 ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion5; + this.infoHeader = BmpInfoHeader.ParseV4(buffer); } else { - throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize '{headerSize}'."); } // Resolution is stored in PPM. - var meta = new ImageMetaData + var meta = new ImageMetadata { ResolutionUnits = PixelResolutionUnit.PixelsPerMeter }; @@ -541,24 +1312,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; - BmpMetaData bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance); + this.bmpMetadata = this.metadata.GetFormatMetadata(BmpFormat.Instance); + 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)) { - bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; + this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; } - - // skip the remaining header because we can't read those parts - this.stream.Skip(skipAmount); } /// @@ -573,7 +1344,31 @@ namespace SixLabors.ImageSharp.Formats.Bmp #endif this.stream.Read(buffer, 0, BmpFileHeader.Size); - this.fileHeader = BmpFileHeader.Parse(buffer); + short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(buffer); + switch (fileTypeMarker) + { + case BmpConstants.TypeMarkers.Bitmap: + this.fileMarkerType = BmpFileMarkerType.Bitmap; + this.fileHeader = BmpFileHeader.Parse(buffer); + break; + case BmpConstants.TypeMarkers.BitmapArray: + this.fileMarkerType = BmpFileMarkerType.BitmapArray; + + // Because we only decode the first bitmap in the array, the array header will be ignored. + // The bitmap file header of the first image follows the array header. + 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; + } } /// @@ -600,44 +1395,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) { - throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); + BmpThrowHelper.ThrowImageFormatException( + $"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.ThrowImageFormatException("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 b1a66acce..612675c33 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -4,13 +4,13 @@ using System.IO; 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,6 +18,20 @@ 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 diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index a67c581eb..4e6ec4502 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -2,13 +2,16 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; +using System.Runtime.InteropServices; 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.ImageSharp.Processing.Processors.Quantization; using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Bmp @@ -23,21 +26,69 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private int padding; + /// + /// The mask for the alpha channel of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32AlphaMask = 0xFF << 24; + + /// + /// The mask for the red part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32RedMask = 0xFF << 16; + + /// + /// The mask for the green part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32GreenMask = 0xFF << 8; + + /// + /// The mask for the blue part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32BlueMask = 0xFF; + + /// + /// The color palette for an 8 bit image will have 256 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize8Bit = 1024; + + /// + /// 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 ?? new OctreeQuantizer(dither: true, maxColors: 256); } /// @@ -53,9 +104,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp 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.GetFormatMetadata(BmpFormat.Instance); + this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel; short bpp = (short)this.bitsPerPixel; int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); @@ -65,35 +116,36 @@ 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 = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3; var infoHeader = new BmpInfoHeader( - headerSize: BmpInfoHeader.Size, + headerSize: infoHeaderSize, height: image.Height, width: image.Width, bitsPerPixel: bpp, @@ -104,24 +156,42 @@ namespace SixLabors.ImageSharp.Formats.Bmp xPelsPerMeter: hResolution, yPelsPerMeter: vResolution); + 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: 19778, // BM - fileSize: 54 + infoHeader.ImageSize, + type: BmpConstants.TypeMarkers.Bitmap, + fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize, reserved: 0, - offset: 54); + offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); #if NETCOREAPP2_1 - Span buffer = stackalloc byte[40]; + Span buffer = stackalloc byte[infoHeaderSize]; #else - byte[] buffer = new byte[40]; + byte[] buffer = new byte[infoHeaderSize]; #endif fileHeader.WriteTo(buffer); stream.Write(buffer, 0, BmpFileHeader.Size); - infoHeader.WriteTo(buffer); + if (this.writeV4Header) + { + infoHeader.WriteV4Header(buffer); + } + else + { + infoHeader.WriteV3Header(buffer); + } - stream.Write(buffer, 0, 40); + stream.Write(buffer, 0, infoHeaderSize); this.WriteImage(stream, image.Frames.RootFrame); @@ -149,6 +219,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; } } @@ -201,5 +279,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 : struct, 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 : struct, IPixel + { + bool isGray8 = typeof(TPixel) == typeof(Gray8); + using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) + { + Span colorPalette = colorPaletteBuffer.GetSpan(); + if (isGray8) + { + 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 : struct, IPixel + { + using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) + { + 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.GetRowSpan(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 : struct, 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 e39a2af0e..661275fc9 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 and contributors. // 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; @@ -69,4 +69,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp 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 000000000..4abcaa3a0 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// 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 a5eaab8eb..056fbe840 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -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 6a740d47d..3d7510bc2 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 and contributors. // 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,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp private bool IsSupportedFileFormat(ReadOnlySpan header) { - // TODO: This should be in constants + short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); return header.Length >= this.HeaderSize && - header[0] == 0x42 && // B - header[1] == 0x4D; // M + (fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs index 5177bc325..4d7f78100 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 and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; @@ -16,11 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct BmpInfoHeader { - /// - /// Defines the size of the BITMAPINFOHEADER data structure in the bitmap file. - /// - public const int Size = 40; - /// /// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file. /// @@ -31,10 +26,40 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public const int Os22ShortSize = 16; + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file. + /// + public const int SizeV3 = 40; + + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. + /// + public const int AdobeV3Size = 52; + + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks (including the alpha channel) are part of the info header instead of following it. + /// + public const int AdobeV3WithAlphaSize = 56; + + /// + /// Size of a IBM OS/2 2.x bitmap header. + /// + public const int Os2v2Size = 64; + + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file. + /// + public const int SizeV4 = 108; + + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 5) data structure in the bitmap file. + /// + public const int SizeV5 = 124; + /// /// Defines the size of the biggest supported header data structure in the bitmap file. /// - public const int MaxHeaderSize = Size; + public const int MaxHeaderSize = SizeV5; /// /// Defines the size of the field. @@ -52,7 +77,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp int xPelsPerMeter = 0, int yPelsPerMeter = 0, int clrUsed = 0, - int clrImportant = 0) + int clrImportant = 0, + int redMask = 0, + int greenMask = 0, + int blueMask = 0, + int alphaMask = 0, + int csType = 0, + int redX = 0, + int redY = 0, + int redZ = 0, + int greenX = 0, + int greenY = 0, + int greenZ = 0, + int blueX = 0, + int blueY = 0, + int blueZ = 0, + int gammeRed = 0, + int gammeGreen = 0, + int gammeBlue = 0) { this.HeaderSize = headerSize; this.Width = width; @@ -65,10 +107,27 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.YPelsPerMeter = yPelsPerMeter; this.ClrUsed = clrUsed; this.ClrImportant = clrImportant; + this.RedMask = redMask; + this.GreenMask = greenMask; + this.BlueMask = blueMask; + this.AlphaMask = alphaMask; + this.CsType = csType; + this.RedX = redX; + this.RedY = redY; + this.RedZ = redZ; + this.GreenX = greenX; + this.GreenY = greenY; + this.GreenZ = greenZ; + this.BlueX = blueX; + this.BlueY = blueY; + this.BlueZ = blueZ; + this.GammaRed = gammeRed; + this.GammaGreen = gammeGreen; + this.GammaBlue = gammeBlue; } /// - /// Gets or sets the size of this header + /// Gets or sets the size of this header. /// public int HeaderSize { get; set; } @@ -130,26 +189,95 @@ namespace SixLabors.ImageSharp.Formats.Bmp public int ClrImportant { get; set; } /// - /// Parses the full BITMAPINFOHEADER header (40 bytes). + /// Gets or sets red color mask. This is used with the BITFIELDS decoding. /// - /// The data to parse. - /// Parsed header - /// - public static BmpInfoHeader Parse(ReadOnlySpan data) - { - if (data.Length != Size) - { - throw new ArgumentException(nameof(data), $"Must be {Size} bytes. Was {data.Length} bytes."); - } + public int RedMask { get; set; } - return MemoryMarshal.Cast(data)[0]; - } + /// + /// Gets or sets green color mask. This is used with the BITFIELDS decoding. + /// + public int GreenMask { get; set; } + + /// + /// Gets or sets blue color mask. This is used with the BITFIELDS decoding. + /// + public int BlueMask { get; set; } + + /// + /// Gets or sets alpha color mask. This is not used yet. + /// + public int AlphaMask { get; set; } + + /// + /// Gets or sets the Color space type. Not used yet. + /// + public int CsType { get; set; } + + /// + /// Gets or sets the X coordinate of red endpoint. Not used yet. + /// + public int RedX { get; set; } + + /// + /// Gets or sets the Y coordinate of red endpoint. Not used yet. + /// + public int RedY { get; set; } + + /// + /// Gets or sets the Z coordinate of red endpoint. Not used yet. + /// + public int RedZ { get; set; } + + /// + /// Gets or sets the X coordinate of green endpoint. Not used yet. + /// + public int GreenX { get; set; } /// - /// Parses the BITMAPCOREHEADER consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes). + /// Gets or sets the Y coordinate of green endpoint. Not used yet. + /// + public int GreenY { get; set; } + + /// + /// Gets or sets the Z coordinate of green endpoint. Not used yet. + /// + public int GreenZ { get; set; } + + /// + /// Gets or sets the X coordinate of blue endpoint. Not used yet. + /// + public int BlueX { get; set; } + + /// + /// Gets or sets the Y coordinate of blue endpoint. Not used yet. + /// + public int BlueY { get; set; } + + /// + /// Gets or sets the Z coordinate of blue endpoint. Not used yet. + /// + public int BlueZ { get; set; } + + /// + /// Gets or sets the Gamma red coordinate scale value. Not used yet. + /// + public int GammaRed { get; set; } + + /// + /// Gets or sets the Gamma green coordinate scale value. Not used yet. + /// + public int GammaGreen { get; set; } + + /// + /// Gets or sets the Gamma blue coordinate scale value. Not used yet. + /// + public int GammaBlue { get; set; } + + /// + /// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes). /// /// The data to parse. - /// Parsed header + /// The parsed header. /// public static BmpInfoHeader ParseCore(ReadOnlySpan data) { @@ -163,10 +291,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height - /// are 4 bytes instead of 2. + /// 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) { @@ -178,7 +306,148 @@ namespace SixLabors.ImageSharp.Formats.Bmp bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); } - public unsafe void WriteTo(Span buffer) + /// + /// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes). + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseV3(ReadOnlySpan data) + { + return new BmpInfoHeader( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)), + compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), + imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), + xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), + yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), + clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), + clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4))); + } + + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. + /// 52 bytes without the alpha mask, 56 bytes with the alpha mask. + /// + /// The data to parse. + /// Indicates, if the alpha bitmask is present. + /// The parsed header. + /// + public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) + { + return new BmpInfoHeader( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)), + compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), + imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), + xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), + yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), + clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), + clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)), + redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)), + greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)), + blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)), + alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0); + } + + /// + /// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are + /// very similar to the Bitmap v3 header. The other 24 bytes are ignored, but they do not hold any + /// useful information for decoding the image. + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan data) + { + var infoHeader = new BmpInfoHeader( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); + + int compression = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)); + + // The compression value in OS/2 bitmap has a different meaning than in windows bitmaps. + // Map the OS/2 value to the windows values. + switch (compression) + { + case 0: + infoHeader.Compression = BmpCompression.RGB; + break; + case 1: + infoHeader.Compression = BmpCompression.RLE8; + break; + case 2: + infoHeader.Compression = BmpCompression.RLE4; + break; + case 4: + infoHeader.Compression = BmpCompression.RLE24; + break; + default: + // Compression type 3 (1DHuffman) is not supported. + BmpThrowHelper.ThrowImageFormatException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24."); + break; + } + + infoHeader.ImageSize = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)); + infoHeader.XPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)); + infoHeader.YPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)); + infoHeader.ClrUsed = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)); + infoHeader.ClrImportant = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)); + + // The following 24 bytes of the header are omitted. + return infoHeader; + } + + /// + /// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes). + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseV4(ReadOnlySpan data) + { + if (data.Length < SizeV4) + { + throw new ArgumentException(nameof(data), $"Must be {SizeV4} bytes. Was {data.Length} bytes."); + } + + return MemoryMarshal.Cast(data)[0]; + } + + /// + /// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes). + /// + /// The buffer to write to. + public void WriteV3Header(Span buffer) + { + buffer.Clear(); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(0, 4), SizeV3); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height); + BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes); + BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant); + } + + /// + /// Writes a complete Bitmap V4 header to a buffer. + /// + /// The buffer to write to. + public unsafe void WriteV4Header(Span buffer) { ref BmpInfoHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); @@ -197,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 new file mode 100644 index 000000000..a92a19d9b --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Bmp +{ + /// + /// Enum value for the different bitmap info header types. The enum value is the number of bytes for the specific bitmap header. + /// + public enum BmpInfoHeaderType + { + /// + /// Bitmap Core or BMP Version 2 header (Microsoft Windows 2.x). + /// + WinVersion2 = 12, + + /// + /// Short variant of the OS/2 Version 2 bitmap header. + /// + Os2Version2Short = 16, + + /// + /// BMP Version 3 header (Microsoft Windows 3.x or Microsoft Windows NT). + /// + WinVersion3 = 40, + + /// + /// Adobe variant of the BMP Version 3 header. + /// + AdobeVersion3 = 52, + + /// + /// Adobe variant of the BMP Version 3 header with an alpha mask. + /// + AdobeVersion3WithAlpha = 56, + + /// + /// BMP Version 2.x header (IBM OS/2 2.x). + /// + Os2Version2 = 64, + + /// + /// BMP Version 4 header (Microsoft Windows 95). + /// + WinVersion4 = 108, + + /// + /// BMP Version 5 header (Windows NT 5.0, 98 or later). + /// + WinVersion5 = 124, + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs index 8b33e30fa..83d4eefe4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs +++ b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs @@ -6,20 +6,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Provides Bmp specific metadata information for the image. /// - public class BmpMetaData : IDeepCloneable + public class BmpMetadata : IDeepCloneable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public BmpMetaData() + public BmpMetadata() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private BmpMetaData(BmpMetaData other) => this.BitsPerPixel = other.BitsPerPixel; + 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. @@ -27,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; /// - public IDeepCloneable DeepClone() => new BmpMetaData(this); + public IDeepCloneable DeepClone() => new BmpMetadata(this); // TODO: Colors used once we support encoding palette bmps. } diff --git a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs new file mode 100644 index 000000000..443471404 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Bmp +{ + internal static class BmpThrowHelper + { + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowImageFormatException(string errorMessage) + { + throw new ImageFormatException(errorMessage); + } + + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException(string errorMessage) + { + throw new NotSupportedException(errorMessage); + } + } +} diff --git a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs index 219d37ca6..f456f2ba3 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs @@ -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 f62504d08..59ad929df 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. // 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 index aa1c353db..0c37907c2 100644 --- a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs @@ -2,38 +2,35 @@ // 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. + /// 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); + public static void SaveAsBmp(this Image source, Stream stream) => 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)); + public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) => + source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/README.md b/src/ImageSharp/Formats/Bmp/README.md index d07283843..f41830733 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 000000000..493fe366a --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// 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/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 42c76d640..1addcd0ab 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -3,7 +3,7 @@ using System.IO; using System.Text; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Gif @@ -44,5 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Gif var decoder = new GifDecoderCore(configuration, this); return decoder.Identify(stream); } + + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index bfa91416a..e16ecb42e 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; using System.Text; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -63,12 +63,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. @@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp.Formats.Gif new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height, - this.metaData); + this.metadata); } /// @@ -276,7 +276,7 @@ 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; } @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { 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)); + this.metadata.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); } } } @@ -416,9 +416,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,7 +431,7 @@ 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; @@ -549,11 +549,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.GetFormatMetadata(GifFormat.Instance); if (this.graphicsControlExtension.DelayTime > 0) { gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; @@ -586,7 +586,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 +614,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.GetFormatMetadata(GifFormat.Instance); + 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); diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index a922f3011..36e27866e 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -10,7 +10,7 @@ using System.Text; 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; @@ -58,9 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Gif private int bitDepth; /// - /// Gif specific meta data. + /// Gif specific metadata. /// - private GifMetaData gifMetaData; + private GifMetadata gifMetadata; /// /// Initializes a new instance of the class. @@ -89,14 +89,17 @@ namespace SixLabors.ImageSharp.Formats.Gif 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; + this.gifMetadata = metadata.GetFormatMetadata(GifFormat.Instance); + this.colorTableMode = this.colorTableMode ?? this.gifMetadata.ColorTableMode; + bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; // Quantize the image returning a palette. - QuantizedFrame quantized = - this.quantizer.CreateFrameQuantizer(image.GetConfiguration()).QuantizeFrame(image.Frames.RootFrame); + IQuantizedFrame quantized = null; + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration())) + { + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + } // Get the number of bits. this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); @@ -106,7 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Gif // 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 +117,12 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Write the comments. - this.WriteComments(metaData, stream); + this.WriteComments(metadata, stream); // Write application extension to allow additional frames. if (image.Frames.Count > 1) { - this.WriteApplicationExtension(stream, this.gifMetaData.RepeatCount); + this.WriteApplicationExtension(stream, this.gifMetadata.RepeatCount); } if (useGlobalTable) @@ -133,23 +136,20 @@ namespace SixLabors.ImageSharp.Formats.Gif // Clean up. quantized?.Dispose(); - quantized = null; // TODO: Write extension etc stream.WriteByte(GifConstants.EndIntroducer); } - private void EncodeGlobal(Image image, QuantizedFrame quantized, int transparencyIndex, Stream stream) + private void EncodeGlobal(Image image, IQuantizedFrame quantized, int transparencyIndex, Stream stream) where TPixel : struct, IPixel { - var palleteQuantizer = new PaletteQuantizer(quantized.Palette, this.quantizer.Diffuser); - 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.GetFormatMetadata(GifFormat.Instance); + this.WriteGraphicalControlExtension(frameMetadata, transparencyIndex, stream); this.WriteImageDescriptor(frame, false, stream); if (i == 0) @@ -158,42 +158,49 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (QuantizedFrame paletteQuantized = palleteQuantizer.CreateFrameQuantizer(image.GetConfiguration()).QuantizeFrame(frame)) + using (IFrameQuantizer palleteFrameQuantizer = + new PaletteFrameQuantizer(this.quantizer.Diffuser, quantized.Palette)) { - this.WriteImageData(paletteQuantized, stream); + using (IQuantizedFrame paletteQuantized = palleteFrameQuantizer.QuantizeFrame(frame)) + { + this.WriteImageData(paletteQuantized, stream); + } } } } } - private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream) + private void EncodeLocal(Image image, IQuantizedFrame quantized, Stream stream) where TPixel : struct, IPixel { ImageFrame previousFrame = null; - GifFrameMetaData previousMeta = null; + GifFrameMetadata previousMeta = null; foreach (ImageFrame frame in image.Frames) { - ImageFrameMetaData metaData = frame.MetaData; - GifFrameMetaData frameMetaData = metaData.GetFormatMetaData(GifFormat.Instance); + ImageFrameMetadata metadata = frame.Metadata; + GifFrameMetadata frameMetadata = metadata.GetFormatMetadata(GifFormat.Instance); 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); + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration(), frameMetadata.ColorTableLength)) + { + quantized = frameQuantizer.QuantizeFrame(frame); + } } else { - quantized = this.quantizer.CreateFrameQuantizer(image.GetConfiguration()) - .QuantizeFrame(frame); + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration())) + { + quantized = frameQuantizer.QuantizeFrame(frame); + } } } this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); - this.WriteGraphicalControlExtension(frameMetaData, this.GetTransparentIndex(quantized), stream); + this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream); this.WriteImageDescriptor(frame, true, stream); this.WriteColorTable(quantized, stream); this.WriteImageData(quantized, stream); @@ -201,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Gif quantized?.Dispose(); quantized = null; // So next frame can regenerate it previousFrame = frame; - previousMeta = frameMetaData; + previousMeta = frameMetadata; } } @@ -215,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The . /// - private int GetTransparentIndex(QuantizedFrame quantized) + private int GetTransparentIndex(IQuantizedFrame quantized) where TPixel : struct, IPixel { // Transparent pixels are much more likely to be found at the end of a palette @@ -226,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { Span rgbaSpan = rgbaBuffer.GetSpan(); ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette, rgbaSpan); + PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan); for (int i = quantized.Palette.Length - 1; i >= 0; i--) { @@ -250,14 +257,14 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// 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 +284,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,7 +333,7 @@ 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(ImageMetadata metadata, Stream stream) { if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) || string.IsNullOrEmpty(property.Value)) @@ -350,18 +357,18 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// 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); @@ -418,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The to encode. /// The stream to write to. - private void WriteColorTable(QuantizedFrame image, Stream stream) + private void WriteColorTable(IQuantizedFrame image, Stream stream) where TPixel : struct, IPixel { // The maximum number of colors for the bit depth @@ -429,7 +436,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { PixelOperations.Instance.ToRgb24Bytes( this.configuration, - image.Palette.AsSpan(), + image.Palette.Span, colorTable.GetSpan(), pixelCount); stream.Write(colorTable.Array, 0, colorTableLength); @@ -440,9 +447,9 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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) + private void WriteImageData(IQuantizedFrame image, Stream stream) where TPixel : struct, IPixel { using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth)) diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index 07d4a5454..abe87819c 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -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 index 0042c6a10..613825ad6 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs @@ -6,20 +6,20 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Provides Gif specific metadata information for the image frame. /// - public class GifFrameMetaData : IDeepCloneable + public class GifFrameMetadata : IDeepCloneable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public GifFrameMetaData() + public GifFrameMetadata() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private GifFrameMetaData(GifFrameMetaData other) + private GifFrameMetadata(GifFrameMetadata other) { this.ColorTableLength = other.ColorTableLength; this.FrameDelay = other.FrameDelay; @@ -49,6 +49,6 @@ namespace SixLabors.ImageSharp.Formats.Gif public GifDisposalMethod DisposalMethod { get; set; } /// - public IDeepCloneable DeepClone() => new GifFrameMetaData(this); + public IDeepCloneable DeepClone() => new GifFrameMetadata(this); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifMetaData.cs b/src/ImageSharp/Formats/Gif/GifMetaData.cs index bb7fb5051..0b6566fbf 100644 --- a/src/ImageSharp/Formats/Gif/GifMetaData.cs +++ b/src/ImageSharp/Formats/Gif/GifMetaData.cs @@ -6,20 +6,20 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Provides Gif specific metadata information for the image. /// - public class GifMetaData : IDeepCloneable + public class GifMetadata : IDeepCloneable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public GifMetaData() + public GifMetadata() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private GifMetaData(GifMetaData other) + private GifMetadata(GifMetadata other) { this.RepeatCount = other.RepeatCount; this.ColorTableMode = other.ColorTableMode; @@ -45,6 +45,6 @@ namespace SixLabors.ImageSharp.Formats.Gif public int GlobalColorTableLength { get; set; } /// - public IDeepCloneable DeepClone() => new GifMetaData(this); + public IDeepCloneable DeepClone() => new GifMetadata(this); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs index 42c202a3d..871b511a0 100644 --- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Text; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Gif { @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Gif 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/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs index 8ddd4247e..c7ac001ff 100644 --- a/src/ImageSharp/Formats/Gif/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Gif/ImageExtensions.cs @@ -2,38 +2,35 @@ // 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. + /// 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); + public static void SaveAsGif(this Image source, Stream stream) => 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)); + public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) => + source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index 2d32fd23a..b4c80a3da 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The span of indexed pixels. /// The stream to write to. - public void Encode(Span indexedPixels, Stream stream) + public void Encode(ReadOnlySpan indexedPixels, Stream stream) { // Write "initial code size" byte stream.WriteByte((byte)this.initialCodeSize); @@ -251,7 +251,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The span of indexed pixels. /// The initial bits. /// The stream to write to. - private void Compress(Span indexedPixels, int intialBits, Stream stream) + private void Compress(ReadOnlySpan indexedPixels, int intialBits, Stream stream) { int fcode; int c; @@ -375,7 +375,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int NextPixel(Span indexedPixels) + private int NextPixel(ReadOnlySpan indexedPixels) { return indexedPixels[this.position++] & 0xFF; } diff --git a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs index 2fdc233b0..c8bd28674 100644 --- a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; namespace SixLabors.ImageSharp.Formats.Gif { diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index ffc40314d..8dafdac79 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -12,13 +12,22 @@ 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 decoded image of a given pixel type. Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel; + + /// + /// Decodes the image from the specified stream to an . + /// The decoder is free to choose the pixel type. + /// + /// The configuration for the image. + /// The containing image data. + /// The decoded image of a pixel type chosen by the decoder. + Image Decode(Configuration configuration, Stream stream); } } diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs index 3cd289df7..bd0d6357c 100644 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -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/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index 09ed6408d..a9e9903a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,10 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal partial struct Block8x8F { - private static readonly Vector4 CMin4 = new Vector4(0F); - private static readonly Vector4 CMax4 = new Vector4(255F); - private static readonly Vector4 COff4 = new Vector4(128F); - /// /// Transpose the block into the destination block. /// @@ -94,10 +91,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Level shift by +128, clip to [0, 255] + /// Level shift by +maximum/2, clip to [0, maximum] /// - public void NormalizeColorsInplace() + public void NormalizeColorsInplace(float maximum) { + Vector4 CMin4 = new Vector4(0F); + Vector4 CMax4 = new Vector4(maximum); + Vector4 COff4 = new Vector4(MathF.Ceiling(maximum / 2)); + this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4); this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4); this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4); @@ -120,10 +121,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2() + public void NormalizeColorsAndRoundInplaceAvx2(float maximum) { - Vector off = new Vector(128f); - Vector max = new Vector(255F); + Vector off = new Vector(MathF.Ceiling(maximum / 2)); + Vector max = new Vector(maximum); ref Vector row0 = ref Unsafe.As>(ref this.V0L); row0 = NormalizeAndRound(row0, off, max); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt index f93ee6522..d6c42a802 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -11,6 +11,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -22,10 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal partial struct Block8x8F { - private static readonly Vector4 CMin4 = new Vector4(0F); - private static readonly Vector4 CMax4 = new Vector4(255F); - private static readonly Vector4 COff4 = new Vector4(128F); - /// /// Transpose the block into the destination block. /// @@ -59,10 +56,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Level shift by +128, clip to [0, 255] + /// Level shift by +maximum/2, clip to [0, maximum] /// - public void NormalizeColorsInplace() + public void NormalizeColorsInplace(float maximum) { + Vector4 CMin4 = new Vector4(0F); + Vector4 CMax4 = new Vector4(maximum); + Vector4 COff4 = new Vector4(MathF.Ceiling(maximum / 2)); + <# PushIndent(" "); @@ -83,10 +84,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2() + public void NormalizeColorsAndRoundInplaceAvx2(float maximum) { - Vector off = new Vector(128f); - Vector max = new Vector(255F); + Vector off = new Vector(MathF.Ceiling(maximum / 2)); + Vector max = new Vector(maximum); <# for (int i = 0; i < 8; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 81393342d..c85bd725c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -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,7 +197,7 @@ 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)] @@ -210,7 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Convert salars to byte-s and copy to dest + /// Convert salars to byte-s and copy to dest, /// /// Pointer to block /// Destination @@ -226,10 +226,10 @@ 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) { @@ -275,7 +275,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// 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) @@ -467,17 +467,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Level shift by +128, clip to [0..255], and round all the values in the block. + /// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block. /// - public void NormalizeColorsAndRoundInplace() + public void NormalizeColorsAndRoundInplace(float maximum) { if (SimdUtils.IsAvx2CompatibleArchitecture) { - this.NormalizeColorsAndRoundInplaceAvx2(); + this.NormalizeColorsAndRoundInplaceAvx2(maximum); } else { - this.NormalizeColorsInplace(); + this.NormalizeColorsInplace(maximum); this.RoundInplace(); } } @@ -538,6 +538,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() { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs index 43de39ac7..e34af9825 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs @@ -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 7a14d072e..a57535b71 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs @@ -8,10 +8,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromCmyk : JpegColorConverter + internal sealed class FromCmyk : JpegColorConverter { - public FromCmyk() - : base(JpegColorSpace.Cmyk) + public FromCmyk(int precision) + : base(JpegColorSpace.Cmyk, precision) { } @@ -25,14 +25,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); for (int i = 0; i < result.Length; i++) { float c = cVals[i]; float m = mVals[i]; float y = yVals[i]; - float k = kVals[i] / 255F; + float k = kVals[i] / this.MaximumValue; v.X = c * k; v.Y = m * k; 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 7424145c3..15bb2cf4b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs @@ -10,16 +10,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromGrayscale : JpegColorConverter + internal sealed class FromGrayscale : JpegColorConverter { - public FromGrayscale() - : base(JpegColorSpace.Grayscale) + public FromGrayscale(int precision) + : base(JpegColorSpace.Grayscale, precision) { } public override void ConvertToRgba(in ComponentValues values, Span result) { - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); ref float sBase = ref MemoryMarshal.GetReference(values.Component0); ref Vector4 dBase = ref MemoryMarshal.GetReference(result); 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 7cd97c414..2f68e312d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs @@ -8,10 +8,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromRgb : JpegColorConverter + internal sealed class FromRgb : JpegColorConverter { - public FromRgb() - : base(JpegColorSpace.RGB) + public FromRgb(int precision) + : base(JpegColorSpace.RGB, precision) { } @@ -24,7 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 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 cb71889bc..a646cd6cf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs @@ -8,19 +8,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromYCbCrBasic : JpegColorConverter + internal sealed class FromYCbCrBasic : JpegColorConverter { - public FromYCbCrBasic() - : base(JpegColorSpace.YCbCr) + public FromYCbCrBasic(int precision) + : base(JpegColorSpace.YCbCr, precision) { } public override void ConvertToRgba(in ComponentValues values, Span result) { - ConvertCore(values, result); + ConvertCore(values, result, this.MaximumValue, this.HalfValue); } - internal static void ConvertCore(in ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) { // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! ReadOnlySpan yVals = values.Component0; @@ -29,13 +29,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4(1 / maxValue, 1 / maxValue, 1 / maxValue, 1F); for (int i = 0; i < result.Length; i++) { float y = yVals[i]; - float cb = cbVals[i] - 128F; - float cr = crVals[i] - 128F; + float cb = cbVals[i] - halfValue; + float cr = crVals[i] - halfValue; v.X = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); v.Y = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); 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 23aa1acbe..1706b4c1b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs @@ -12,10 +12,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromYCbCrSimd : JpegColorConverter + internal sealed class FromYCbCrSimd : JpegColorConverter { - public FromYCbCrSimd() - : base(JpegColorSpace.YCbCr) + public FromYCbCrSimd(int precision) + : base(JpegColorSpace.YCbCr, precision) { } @@ -25,16 +25,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters int simdCount = result.Length - remainder; if (simdCount > 0) { - ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount)); + ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue); } - FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder)); + FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue); } /// /// SIMD convert using buffers of sizes divisible by 8. /// - internal static void ConvertCore(in ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) { DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisible by 8!"); @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector4Octet resultBase = ref Unsafe.As(ref MemoryMarshal.GetReference(result)); - var chromaOffset = new Vector4(-128f); + var chromaOffset = new Vector4(-halfValue); // Walking 8 elements at one step: int n = result.Length / 8; @@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // y = yVals[i]; Vector4Pair y = Unsafe.Add(ref yBase, i); - // cb = cbVals[i] - 128F; + // cb = cbVals[i] - halfValue); Vector4Pair cb = Unsafe.Add(ref cbBase, i); cb.AddInplace(chromaOffset); - // cr = crVals[i] - 128F; + // cr = crVals[i] - halfValue; Vector4Pair cr = Unsafe.Add(ref crBase, i); cr.AddInplace(chromaOffset); @@ -90,15 +90,15 @@ 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(); - g.RoundAndDownscalePreAvx2(); - b.RoundAndDownscalePreAvx2(); + r.RoundAndDownscalePreAvx2(maxValue); + g.RoundAndDownscalePreAvx2(maxValue); + b.RoundAndDownscalePreAvx2(maxValue); } else if (SimdUtils.IsAvx2CompatibleArchitecture) { - r.RoundAndDownscaleAvx2(); - g.RoundAndDownscaleAvx2(); - b.RoundAndDownscaleAvx2(); + r.RoundAndDownscaleAvx2(maxValue); + g.RoundAndDownscaleAvx2(maxValue); + b.RoundAndDownscaleAvx2(maxValue); } else { 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 f0a70a6f3..093ea2f9a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs @@ -13,10 +13,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromYCbCrSimdAvx2 : JpegColorConverter + internal sealed class FromYCbCrSimdAvx2 : JpegColorConverter { - public FromYCbCrSimdAvx2() - : base(JpegColorSpace.YCbCr) + public FromYCbCrSimdAvx2(int precision) + : base(JpegColorSpace.YCbCr, precision) { } @@ -28,16 +28,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters int simdCount = result.Length - remainder; if (simdCount > 0) { - ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount)); + ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue); } - FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder)); + FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue); } /// /// SIMD convert using buffers of sizes divisible by 8. /// - internal static void ConvertCore(in ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) { // This implementation is actually AVX specific. // An AVX register is capable of storing 8 float-s. @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector4Octet resultBase = ref Unsafe.As(ref MemoryMarshal.GetReference(result)); - var chromaOffset = new Vector(-128f); + var chromaOffset = new Vector(-halfValue); // Walking 8 elements at one step: int n = result.Length / 8; @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector ggRefAsVector = ref Unsafe.As>(ref gg); ref Vector bbRefAsVector = ref Unsafe.As>(ref bb); - var scale = new Vector(1 / 255f); + var scale = new Vector(1 / maxValue); for (int i = 0; i < n; i++) { 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 6f940f62f..cd8a3bb06 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs @@ -8,10 +8,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal class FromYccK : JpegColorConverter + internal sealed class FromYccK : JpegColorConverter { - public FromYccK() - : base(JpegColorSpace.Ycck) + public FromYccK(int precision) + : base(JpegColorSpace.Ycck, precision) { } @@ -25,18 +25,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); for (int i = 0; i < result.Length; i++) { float y = yVals[i]; - float cb = cbVals[i] - 128F; - float cr = crVals[i] - 128F; - float k = kVals[i] / 255F; + float cb = cbVals[i] - this.HalfValue; + float cr = crVals[i] - this.HalfValue; + float k = kVals[i] / this.MaximumValue; - v.X = (255F - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - v.Y = (255F - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; - v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.X = (this.MaximumValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (this.MaximumValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; + v.Z = (this.MaximumValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; v.W = 1F; v *= scale; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index a44ebf89d..61e359869 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tuples; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { @@ -22,15 +20,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ///
private static readonly JpegColorConverter[] Converters = { - GetYCbCrConverter(), new FromYccK(), new FromCmyk(), new FromGrayscale(), new FromRgb() + // 8-bit converters + GetYCbCrConverter(8), + new FromYccK(8), + new FromCmyk(8), + new FromGrayscale(8), + new FromRgb(8), + + // 12-bit converters + GetYCbCrConverter(12), + new FromYccK(12), + new FromCmyk(12), + new FromGrayscale(12), + new FromRgb(12), }; /// /// Initializes a new instance of the class. /// - protected JpegColorConverter(JpegColorSpace colorSpace) + protected JpegColorConverter(JpegColorSpace colorSpace, int precision) { this.ColorSpace = colorSpace; + this.Precision = precision; + this.MaximumValue = MathF.Pow(2, precision) - 1; + this.HalfValue = MathF.Ceiling(this.MaximumValue / 2); } /// @@ -38,12 +51,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// public JpegColorSpace ColorSpace { get; } + /// + /// Gets the Precision of this converter in bits. + /// + public int Precision { get; } + + /// + /// Gets the maximum value of a sample + /// + private float MaximumValue { get; } + + /// + /// Gets the half of the maximum value of a sample + /// + private float HalfValue { get; } + /// /// Returns the corresponding to the given /// - public static JpegColorConverter GetConverter(JpegColorSpace colorSpace) + public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, float precision) { - JpegColorConverter converter = Converters.FirstOrDefault(c => c.ColorSpace == colorSpace); + JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace + && c.Precision == precision); if (converter is null) { @@ -63,8 +92,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// Returns the for the YCbCr colorspace that matches the current CPU architecture. /// - private static JpegColorConverter GetYCbCrConverter() => - FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2() : new FromYCbCrSimd(); + private static JpegColorConverter GetYCbCrConverter(int precision) => + FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2(precision) : new FromYCbCrSimd(precision); /// /// A stack-only struct to reference the input buffers using -s. 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 06b46746a..000000000 --- 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 000000000..13c89c82c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs @@ -0,0 +1,227 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.IO; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Used to buffer and track the bits read from the Huffman entropy encoded data. + /// + internal struct HuffmanScanBuffer + { + private readonly DoubleBufferedStreamReader stream; + + // The entropy encoded code buffer. + private ulong data; + + // The number of valid bits left to read in the buffer. + private int remainingBits; + + // Whether there is no more good data to pull from the stream for the current mcu. + private bool badData; + + public HuffmanScanBuffer(DoubleBufferedStreamReader 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 < 16) + { + 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 += 48; + this.data = (this.data << 48) | this.GetBytes(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe int DecodeHuffman(ref HuffmanTable h) + { + this.CheckBits(); + int v = this.PeekBits(JpegConstants.Huffman.LookupBits); + int symbol = h.LookaheadValue[v]; + int size = h.LookaheadSize[v]; + + if (size == JpegConstants.Huffman.SlowBits) + { + ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remainingBits); + while (x > h.MaxCode[size]) + { + size++; + } + + v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size)); + symbol = h.Values[(h.ValOffset[size] + v) & 0xFF]; + } + + this.remainingBits -= size; + + return symbol; + } + + [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 < 6; 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 000000000..c50812e25 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -0,0 +1,714 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Decodes the Huffman encoded spectral scan. + /// Originally ported from + /// with additional fixes for both performance and common encoding errors. + /// + internal class HuffmanScanDecoder + { + private readonly JpegFrame frame; + private readonly HuffmanTable[] dcHuffmanTables; + private readonly HuffmanTable[] acHuffmanTables; + private readonly DoubleBufferedStreamReader stream; + private readonly JpegComponent[] components; + + // The restart interval. + private readonly int restartInterval; + + // The number of interleaved components. + private readonly int componentsLength; + + // The spectral selection start. + private readonly int spectralStart; + + // The spectral selection end. + private readonly int spectralEnd; + + // The successive approximation high bit end. + private readonly int successiveHigh; + + // The successive approximation low bit end. + private readonly int successiveLow; + + // How many mcu's are left to do. + private int todo; + + // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. + private int eobrun; + + // The unzig data. + private ZigZag dctZigZag; + + private HuffmanScanBuffer scanBuffer; + + /// + /// Initializes a new instance of the class. + /// + /// The input stream. + /// The image frame. + /// The DC Huffman tables. + /// The AC Huffman tables. + /// The length of the components. Different to the array length. + /// The reset interval. + /// The spectral selection start. + /// The spectral selection end. + /// The successive approximation bit high end. + /// The successive approximation bit low end. + public HuffmanScanDecoder( + DoubleBufferedStreamReader stream, + JpegFrame frame, + HuffmanTable[] dcHuffmanTables, + HuffmanTable[] acHuffmanTables, + int componentsLength, + int restartInterval, + int spectralStart, + int spectralEnd, + int successiveHigh, + int successiveLow) + { + this.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; + } + + /// + /// Decodes the entropy coded data. + /// + public void ParseEntropyCodedData() + { + 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 unsafe 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++) + { + 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 unsafe 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(); + + int mcu = 0; + for (int j = 0; j < h; j++) + { + 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); + + // Every data block is an MCU, so countdown the restart interval + mcu++; + + 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 unsafe 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(); + + int mcu = 0; + for (int j = 0; j < h; j++) + { + 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); + + // Every data block is an MCU, so countdown the restart interval + mcu++; + this.HandleRestart(); + } + } + } + else + { + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + acHuffmanTable.Configure(); + + int mcu = 0; + for (int j = 0; j < h; j++) + { + 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); + + // Every data block is an MCU, so countdown the restart interval + mcu++; + this.HandleRestart(); + } + } + } + } + + private void DecodeBlockBaseline( + JpegComponent component, + ref Block8x8 block, + ref HuffmanTable dcTable, + ref HuffmanTable acTable) + { + ref short blockDataRef = ref Unsafe.As(ref block); + ref 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 9e134746b..4685ba289 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -1,124 +1,162 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.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) { - const int Length = 257; - using (IMemoryOwner huffcode = memoryAllocator.Allocate(Length)) + 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 l = 1; l <= 16; l++) + { + int i = this.Sizes[l]; + while (i-- != 0) { - byte length = Unsafe.Add(ref codeLengthsRef, i); - for (short j = 0; j < length; j++) - { - Unsafe.Add(ref sizesRef, x++) = i; - } + huffSize[p++] = (char)l; } + } - 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) - { - 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 l = 1; l <= 16; l++) + { + if (this.Sizes[l] != 0) + { + int offset = p - (int)huffCode[p]; + this.ValOffset[l] = offset; + p += this.Sizes[l]; + this.MaxCode[l] = huffCode[p - 1]; // Maximum code of length l + this.MaxCode[l] <<= 64 - l; // Left justify + this.MaxCode[l] |= (1ul << (64 - l)) - 1; } + else + { + this.MaxCode[l] = 0; + } + } - 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 fastRef = ref this.Lookahead[0]; - Unsafe.InitBlockUnaligned(ref fastRef, 0xFF, 1 << FastBits); // 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++) + { + 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] << (JpegConstants.Huffman.LookupBits - length)); + for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) { - int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - size); - int m = 1 << (FastBits - size); - for (int l = 0; l < m; l++) - { - Unsafe.Add(ref fastRef, c + l) = (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 dc066aa0a..000000000 --- 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/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index 1454bb5b1..ace8d7215 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -29,10 +29,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// JpegColorSpace ColorSpace { get; } + /// + /// Gets the number of bits used for precision. + /// + int Precision { get; } + /// /// Gets the components. /// - IEnumerable Components { get; } + IJpegComponent[] Components { get; } /// /// Gets the quantization tables, in zigzag order. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index c51a2f4da..7497c8a40 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -2,12 +2,12 @@ // 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.ThrowImageFormatException($"X-Density {xDensity} must be greater than 0."); + } + + if (yDensity <= 0) + { + JpegThrowHelper.ThrowImageFormatException($"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 da4b2847b..c5efb812e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.Primitives; @@ -19,17 +20,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; @@ -38,6 +39,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ///
private Size subSamplingDivisors; + /// + /// Defines the maximum value derived from the bitdepth. + /// + private readonly int maximumValue; + /// /// Initializes a new instance of the struct. /// @@ -48,6 +54,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)MathF.Pow(2, decoder.Precision) - 1; this.SourceBlock = default; this.WorkspaceBlock1 = default; @@ -58,14 +65,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Processes 'sourceBlock' producing Jpeg color channel values from spectral values: /// - Dequantize /// - Applying IDCT - /// - Level shift by +128, clip to [0, 255] - /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in + /// - Level shift by +maximumValue/2, clip to [0, maximumValue] + /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in . ///
/// The source block. /// The destination buffer area. + /// The maximum value derived from the bitdepth. public void ProcessBlockColorsInto( ref Block8x8 sourceBlock, - in BufferArea destArea) + in BufferArea destArea, + float maximumValue) { ref Block8x8F b = ref this.SourceBlock; b.LoadFrom(ref sourceBlock); @@ -78,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // To conform better to libjpeg we actually NEED TO loose precision here. // This is because they store blocks as Int16 between all the operations. // To be "more accurate", we need to emulate this by rounding! - this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(); + this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(maximumValue); this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs index 2861a2c2e..aa33744ad 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs @@ -4,7 +4,7 @@ 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 ef03582d6..535330394 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -2,8 +2,6 @@ // 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; @@ -12,9 +10,9 @@ 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 +21,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.memoryAllocator = memoryAllocator; this.Frame = frame; this.Id = id; + + // Valid sampling factors are 1..2 + if (horizontalFactor == 0 + || verticalFactor == 0 + || horizontalFactor > 2 + || verticalFactor > 2) + { + JpegThrowHelper.ThrowBadSampling(); + } + this.HorizontalSamplingFactor = horizontalFactor; this.VerticalSamplingFactor = verticalFactor; this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); + + if (quantizationTableIndex > 3) + { + JpegThrowHelper.ThrowBadQuantizationTable(); + } + this.QuantizationTableIndex = quantizationTableIndex; this.Index = index; } /// - /// 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 +83,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 +123,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); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 94ec600dd..e1a9380a0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -78,6 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public void CopyBlocksToColorBuffer() { var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); + float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1; for (int y = 0; y < this.BlockRowsPerStep; y++) { @@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.blockAreaSize.Width, this.blockAreaSize.Height); - blockPp.ProcessBlockColorsInto(ref block, destArea); + blockPp.ProcessBlockColorsInto(ref block, destArea, maximumValue); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 36a3dc2d2..f426eb1b1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -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; } /// - /// Gets or sets the number of samples per scanline + /// Gets or sets the number of samples per scanline. /// public short SamplesPerLine { get; set; } /// - /// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4 + /// 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() { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 7ce86b4c9..f3f2952b1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.Linq; using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -22,7 +21,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 { @@ -57,14 +56,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.configuration = configuration; this.RawJpeg = rawJpeg; - IJpegComponent c0 = rawJpeg.Components.First(); + IJpegComponent c0 = rawJpeg.Components[0]; this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryAllocator, this, c)).ToArray(); + + this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length]; + for (int i = 0; i < rawJpeg.Components.Length; i++) + { + this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]); + } + this.rgbaBuffer = memoryAllocator.Allocate(rawJpeg.ImageSizeInPixels.Width); - this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace); + this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision); } /// @@ -152,7 +157,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); - Buffer2D[] buffers = this.ComponentProcessors.Select(cp => cp.ColorBuffer).ToArray(); + var buffers = new Buffer2D[this.ComponentProcessors.Length]; + for (int i = 0; i < this.ComponentProcessors.Length; i++) + { + buffers[i] = this.ComponentProcessors[i].ColorBuffer; + } for (int yy = this.PixelRowCounter; yy < maxY; yy++) { @@ -164,8 +173,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 3e7108b15..54633a5d7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -12,31 +12,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder internal static class ProfileResolver { /// - /// Describes the EXIF specific markers + /// Describes the JFIF specific markers. /// public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); /// - /// Describes the EXIF specific markers + /// Describes the ICC specific markers. /// public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); /// - /// Describes the ICC specific markers + /// Describes the EXIF specific markers. /// public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); /// - /// Describes Adobe specific markers + /// Describes Adobe specific markers . /// public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); /// - /// Returns a value indicating whether the passed bytes are a match to the profile identifier + /// 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 diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs index 4e11568c8..a7d2a0fde 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs @@ -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 48abca2a4..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs +++ /dev/null @@ -1,959 +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/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index d775425c5..301079b6a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index a3701f2c1..059e2052b 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 and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -16,10 +16,22 @@ 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, @@ -27,22 +39,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). /// private static readonly byte[] Unzig = + new byte[Size] { - 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 }; /// @@ -68,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { ZigZag result = default; byte* unzigPtr = result.Data; - Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); + Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, Size); return result; } @@ -79,7 +87,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 +95,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components return result; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs index cb7fc1944..7fec050b4 100644 --- a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs @@ -2,38 +2,35 @@ // 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. + /// 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); + 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 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)); + public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) => + source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index 49e3b4170..a39480e12 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -26,7 +26,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 +219,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// Contains Adobe specific constants + /// Contains Adobe specific constants. /// internal static class Adobe { @@ -238,5 +238,32 @@ 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; + + /// + /// 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 57b70dd26..a1bf04852 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -38,5 +38,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return decoder.Identify(stream); } } + + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index faa7c3bc9..bf7c8f9c8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1,11 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// 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.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers; @@ -13,9 +11,9 @@ 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.PixelFormats; using SixLabors.ImageSharp.Primitives; using SixLabors.Memory; @@ -33,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The only supported precision /// - public const int SupportedPrecision = 8; + private readonly int[] supportedPrecisions = { 8, 12 }; /// /// The global configuration @@ -53,17 +51,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ -137,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Gets the color depth, in number of bits per pixel. /// - public int BitsPerPixel => this.ComponentCount * SupportedPrecision; + public int BitsPerPixel => this.ComponentCount * this.Frame.Precision; /// /// Gets the input stream. @@ -150,9 +143,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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; } @@ -160,13 +153,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public JpegColorSpace ColorSpace { get; private set; } + /// + public int Precision { get; private set; } + /// /// Gets the components. /// public JpegComponent[] Components => this.Frame.Components; /// - IEnumerable IRawJpegData.Components => this.Components; + IJpegComponent[] IRawJpegData.Components => this.Components; /// public Block8x8F[] QuantizationTables { get; private set; } @@ -220,7 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.ParseStream(stream); this.InitExifProfile(); this.InitIccProfile(); - this.InitDerivedMetaDataProperties(); + this.InitDerivedMetadataProperties(); return this.PostProcessIntoImage(); } @@ -233,9 +229,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.ParseStream(stream, true); this.InitExifProfile(); this.InitIccProfile(); - this.InitDerivedMetaDataProperties(); + 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,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Whether to decode metadata only. public void ParseStream(Stream stream, bool metadataOnly = false) { - this.MetaData = new ImageMetaData(); + this.Metadata = new ImageMetadata(); this.InputStream = new DoubleBufferedStreamReader(this.configuration.MemoryAllocator, stream); // Check for the Start Of Image marker. @@ -253,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { - throw new ImageFormatException("Missing SOI marker."); + JpegThrowHelper.ThrowImageFormatException("Missing SOI marker."); } this.InputStream.Read(this.markerBuffer, 0, 2); @@ -264,9 +260,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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. @@ -376,14 +372,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { 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; } /// @@ -399,15 +393,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) @@ -417,7 +410,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg : JpegColorSpace.Cmyk; } - throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}"); + JpegThrowHelper.ThrowImageFormatException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}"); + return default; } /// @@ -427,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { if (this.isExif) { - this.MetaData.ExifProfile = new ExifProfile(this.exifData); + this.Metadata.ExifProfile = new ExifProfile(this.exifData); } } @@ -441,21 +435,21 @@ 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. + /// 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) { @@ -464,16 +458,16 @@ 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) { - if (!this.MetaData.ExifProfile.TryGetValue(tag, out ExifValue exifValue)) + if (!this.Metadata.ExifProfile.TryGetValue(tag, out ExifValue exifValue)) { return 0; } @@ -644,6 +638,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg bool done = false; remaining--; int quantizationTableSpec = this.InputStream.ReadByte(); + int tableIndex = quantizationTableSpec & 15; + + // Max index. 4 Tables max. + if (tableIndex > 3) + { + JpegThrowHelper.ThrowBadQuantizationTable(); + } switch (quantizationTableSpec >> 4) { @@ -659,7 +660,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InputStream.Read(this.temp, 0, 64); remaining -= 64; - ref Block8x8F table = ref this.QuantizationTables[quantizationTableSpec & 15]; + ref Block8x8F table = ref this.QuantizationTables[tableIndex]; for (int j = 0; j < 64; j++) { table[j] = this.temp[j]; @@ -679,7 +680,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InputStream.Read(this.temp, 0, 128); remaining -= 128; - ref Block8x8F table = ref this.QuantizationTables[quantizationTableSpec & 15]; + 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]; @@ -688,7 +689,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg break; default: - throw new ImageFormatException("Bad Tq index value"); + JpegThrowHelper.ThrowBadQuantizationTable(); + break; } if (done) @@ -699,10 +701,10 @@ 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); } /// @@ -715,17 +717,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { if (this.Frame != null) { - throw new ImageFormatException("Multiple SOF markers. Only single frame jpegs supported."); + JpegThrowHelper.ThrowImageFormatException("Multiple SOF markers. Only single frame jpegs supported."); } - this.InputStream.Read(this.temp, 0, remaining); + // Read initial marker definitions. + const int length = 6; + this.InputStream.Read(this.temp, 0, length); - // We only support 8-bit precision. - if (this.temp[0] != SupportedPrecision) + // We only support 8-bit and 12-bit precision. + if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1) { - throw new ImageFormatException("Only 8-Bit precision supported."); + JpegThrowHelper.ThrowImageFormatException("Only 8-Bit and 12-Bit precision supported."); } + this.Precision = this.temp[0]; + this.Frame = new JpegFrame { Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, @@ -736,22 +742,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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); + } + + this.InputStream.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]; @@ -773,7 +792,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Frame.Components[i] = component; this.Frame.ComponentIds[i] = component.Id; - index += 3; + index += componentBytes; } this.Frame.MaxHorizontalFactor = maxH; @@ -791,12 +810,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. private void ProcessDefineHuffmanTablesMarker(int remaining) { + 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(); + int tableType = huffmanTableSpec >> 4; + int tableIndex = huffmanTableSpec & 15; + + // Types 0..1 DC..AC + if (tableType > 1) + { + JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table type."); + } + + // Max tables of each type + if (tableIndex > 3) + { + JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table index."); + } + this.InputStream.Read(huffmanData.Array, 0, 16); using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean)) @@ -809,26 +845,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1); } + length -= 17; + + if (codeLengthSum > 256 || codeLengthSum > length) + { + JpegThrowHelper.ThrowImageFormatException("Huffman table has excessive length."); + } + using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) { this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); i += 17 + codeLengthSum; - int tableType = huffmanTableSpec >> 4; - int tableIndex = huffmanTableSpec & 15; - 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); - } } } } @@ -844,7 +878,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { if (remaining != 2) { - throw new ImageFormatException($"DRI has wrong length: {remaining}"); + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); } this.resetInterval = this.ReadUint16(); @@ -855,6 +889,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void ProcessStartOfScanMarker() { + if (this.Frame is null) + { + JpegThrowHelper.ThrowImageFormatException("No readable SOFn (Start Of Frame) marker found."); + } + int selectorsCount = this.InputStream.ReadByte(); for (int i = 0; i < selectorsCount; i++) { @@ -873,7 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (componentIndex < 0) { - throw new ImageFormatException("Unknown component selector"); + JpegThrowHelper.ThrowImageFormatException($"Unknown component selector {componentIndex}."); } ref JpegComponent component = ref this.Frame.Components[componentIndex]; @@ -889,12 +928,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; - var sd = new ScanDecoder( + var sd = new HuffmanScanDecoder( this.InputStream, this.Frame, this.dcHuffmanTables, this.acHuffmanTables, - this.fastACTables, selectorsCount, this.resetInterval, spectralStart, @@ -913,8 +951,8 @@ 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 @@ -935,11 +973,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private Image PostProcessIntoImage() where TPixel : struct, IPixel { + if (this.ImageWidth == 0 || this.ImageHeight == 0) + { + JpegThrowHelper.ThrowInvalidImageDimensions(this.ImageWidth, this.ImageHeight); + } + var image = Image.CreateUninitialized( this.configuration, this.ImageWidth, this.ImageHeight, - this.MetaData); + this.Metadata); using (var postProcessor = new JpegImagePostProcessor(this.configuration, this)) { @@ -949,4 +992,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return image; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 0dcbd8fef..0b9eeb609 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -4,15 +4,14 @@ using System; using System.Buffers.Binary; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.MetaData; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -27,85 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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,6 +87,103 @@ 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. /// @@ -179,17 +196,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - 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.GetFormatMetadata(JpegFormat.Instance).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 +225,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); + this.WriteProfiles(metadata); // Write the quantization tables. this.WriteDefineQuantizationTables(); @@ -259,9 +276,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 +377,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)); @@ -427,8 +447,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; @@ -547,7 +567,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private void WriteDefineHuffmanTables(int componentCount) { // Table identifiers. - Span headers = stackalloc byte[] { 0x00, 0x10, 0x01, 0x11 }; + Span headers = stackalloc byte[] + { + 0x00, + 0x10, + 0x01, + 0x11 + }; int markerlen = 2; HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs; @@ -626,8 +652,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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(); @@ -636,31 +662,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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; @@ -764,17 +789,17 @@ 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); } /// @@ -786,16 +811,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private void WriteStartOfFrame(int width, int height, int componentCount) { // "default" to 4:2:0 - Span subsamples = stackalloc byte[] { 0x22, 0x11, 0x11 }; - Span chroma = stackalloc byte[] { 0x00, 0x01, 0x01 }; + Span subsamples = stackalloc byte[] + { + 0x22, + 0x11, + 0x11 + }; + + Span chroma = stackalloc byte[] + { + 0x00, + 0x01, + 0x01 + }; switch (this.subsample) { case JpegSubsample.Ratio444: - subsamples = stackalloc byte[] { 0x11, 0x11, 0x11 }; + subsamples = stackalloc byte[] + { + 0x11, + 0x11, + 0x11 + }; break; case JpegSubsample.Ratio420: - subsamples = stackalloc byte[] { 0x22, 0x11, 0x11 }; + subsamples = stackalloc byte[] + { + 0x22, + 0x11, + 0x11 + }; break; } @@ -844,7 +890,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { // 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) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs index 6b23ceac7..f56072a4b 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -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/JpegMetaData.cs b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs index fcad29e5d..9f0deae02 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs @@ -6,20 +6,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Provides Jpeg specific metadata information for the image. /// - public class JpegMetaData : IDeepCloneable + public class JpegMetadata : IDeepCloneable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public JpegMetaData() + public JpegMetadata() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private JpegMetaData(JpegMetaData other) => this.Quality = other.Quality; + private JpegMetadata(JpegMetadata other) => this.Quality = other.Quality; /// /// Gets or sets the encoded quality. @@ -27,6 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public int Quality { get; set; } = 75; /// - public IDeepCloneable DeepClone() => new JpegMetaData(this); + public IDeepCloneable DeepClone() => new JpegMetadata(this); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index c7f366660..7e8384dcf 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -8,19 +8,25 @@ 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); - } + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowBadHuffmanCode() - { - throw new ImageFormatException("Bad Huffman code."); - } + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadMarker(string marker, int length) => throw new ImageFormatException($"Marker {marker} has bad length {length}."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadQuantizationTable() => throw new ImageFormatException("Bad Quantization Table index."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadSampling() => throw new ImageFormatException("Bad sampling factor."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new ImageFormatException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageDimensions(int width, int height) => throw new ImageFormatException($"Invalid image dimensions: {width}x{height}."); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs index ed21b91bf..66d04f39f 100644 --- a/src/ImageSharp/Formats/PixelTypeInfo.cs +++ b/src/ImageSharp/Formats/PixelTypeInfo.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.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 : struct, IPixel => + new PixelTypeInfo(Unsafe.SizeOf() * 8); } } diff --git a/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs index 6ab0dd657..8b3c3e9aa 100644 --- a/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs +++ b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs @@ -1,7 +1,10 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Buffers.Binary; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Png.Chunks { @@ -57,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/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index c6a297e33..5d9dc6a89 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -57,7 +57,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Up(x) = Raw(x) - Prior(x) resultBaseRef = 2; - for (int x = 0; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) { + for (int x = 0; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) + { byte scan = Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); ++x; diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs index c73ec6f57..af56830f4 100644 --- a/src/ImageSharp/Formats/Png/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Png/ImageExtensions.cs @@ -2,39 +2,35 @@ // 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. + /// 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); + 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 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)); + public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) => + source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 62a7b74ab..e1f978e1a 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -26,7 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// public static readonly IEnumerable FileExtensions = new[] { "png" }; - public static readonly byte[] HeaderBytes = { + public static readonly byte[] HeaderBytes = + { 0x89, // Set the high bit. 0x50, // P 0x4E, // N diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 39dfb1d0b..040da9473 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -59,5 +59,8 @@ namespace SixLabors.ImageSharp.Formats.Png var decoder = new PngDecoderCore(configuration, this); return decoder.Identify(stream); } + + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 9ffac5e62..5e9d1440a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -12,8 +12,8 @@ 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.Profiles.Exif; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -157,8 +157,8 @@ namespace SixLabors.ImageSharp.Formats.Png public Image Decode(Stream stream) where TPixel : struct, IPixel { - var metaData = new ImageMetaData(); - PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); + var metadata = new ImageMetadata(); + PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); this.currentStream = stream; this.currentStream.Skip(8); Image image = null; @@ -171,24 +171,24 @@ 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(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetadata); } break; @@ -201,17 +201,17 @@ namespace SixLabors.ImageSharp.Formats.Png byte[] 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(metadata, chunk.Data.Array.AsSpan(0, chunk.Length)); break; case PngChunkType.Exif: if (!this.ignoreMetadata) { byte[] exifData = new byte[chunk.Length]; Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); - metaData.ExifProfile = new ExifProfile(exifData); + metadata.ExifProfile = new ExifProfile(exifData); } break; @@ -246,8 +246,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing image data. public IImageInfo Identify(Stream stream) { - var metaData = new ImageMetaData(); - PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); + var metadata = new ImageMetadata(); + PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); this.currentStream = stream; this.currentStream.Skip(8); try @@ -259,19 +259,19 @@ 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(metadata, chunk.Data.Array.AsSpan(0, chunk.Length)); break; case PngChunkType.End: this.isEndChunkReached = true; @@ -295,7 +295,7 @@ namespace SixLabors.ImageSharp.Formats.Png throw new ImageFormatException("PNG Image does not contain a header chunk"); } - 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 +350,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 +367,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,7 +380,7 @@ 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) + private void InitializeImage(ImageMetadata metadata, out Image image) where TPixel : struct, IPixel { image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); @@ -471,17 +471,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The containing data. /// The pixel data. - /// The png meta data - private void ReadScanlines(Stream dataStream, ImageFrame image, PngMetaData pngMetaData) + /// The png metadata + private void ReadScanlines(Stream dataStream, ImageFrame image, PngMetadata pngMetadata) where TPixel : struct, IPixel { if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) { - this.DecodeInterlacedPixelData(dataStream, image, pngMetaData); + this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); } else { - this.DecodePixelData(dataStream, image, pngMetaData); + this.DecodePixelData(dataStream, image, pngMetadata); } } @@ -491,8 +491,8 @@ 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) + /// The png metadata + private void DecodePixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) where TPixel : struct, IPixel { while (this.currentRow < this.header.Height) @@ -532,7 +532,7 @@ namespace SixLabors.ImageSharp.Formats.Png throw new ImageFormatException("Unknown filter type."); } - this.ProcessDefilteredScanline(scanlineSpan, image, pngMetaData); + this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); this.SwapBuffers(); this.currentRow++; @@ -546,8 +546,8 @@ 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) + /// The png metadata. + private void DecodeInterlacedPixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) where TPixel : struct, IPixel { while (true) @@ -604,7 +604,7 @@ namespace SixLabors.ImageSharp.Formats.Png } 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[this.pass], Adam7.ColumnIncrement[this.pass]); this.SwapBuffers(); @@ -632,8 +632,8 @@ 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) + /// The png metadata. + private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) where TPixel : struct, IPixel { Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); @@ -653,9 +653,9 @@ namespace SixLabors.ImageSharp.Formats.Png this.header, scanlineSpan, rowSpan, - pngMetaData.HasTrans, - pngMetaData.TransparentGray16.GetValueOrDefault(), - pngMetaData.TransparentGray8.GetValueOrDefault()); + pngMetadata.HasTrans, + pngMetadata.TransparentGray16.GetValueOrDefault(), + pngMetadata.TransparentGray8.GetValueOrDefault()); break; @@ -687,9 +687,9 @@ namespace SixLabors.ImageSharp.Formats.Png rowSpan, this.bytesPerPixel, this.bytesPerSample, - pngMetaData.HasTrans, - pngMetaData.TransparentRgb48.GetValueOrDefault(), - pngMetaData.TransparentRgb24.GetValueOrDefault()); + pngMetadata.HasTrans, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); break; @@ -714,10 +714,10 @@ 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) + private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1) where TPixel : struct, IPixel { // Trim the first marker byte from the buffer @@ -737,9 +737,9 @@ namespace SixLabors.ImageSharp.Formats.Png rowSpan, pixelOffset, increment, - pngMetaData.HasTrans, - pngMetaData.TransparentGray16.GetValueOrDefault(), - pngMetaData.TransparentGray8.GetValueOrDefault()); + pngMetadata.HasTrans, + pngMetadata.TransparentGray16.GetValueOrDefault(), + pngMetadata.TransparentGray8.GetValueOrDefault()); break; @@ -776,9 +776,9 @@ namespace SixLabors.ImageSharp.Formats.Png increment, this.bytesPerPixel, this.bytesPerSample, - pngMetaData.HasTrans, - pngMetaData.TransparentRgb48.GetValueOrDefault(), - pngMetaData.TransparentRgb24.GetValueOrDefault()); + pngMetadata.HasTrans, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); break; @@ -799,11 +799,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 +815,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.HasTrans = 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.HasTrans = true; } } else if (this.pngColorType == PngColorType.Grayscale) @@ -833,14 +833,14 @@ namespace SixLabors.ImageSharp.Formats.Png { if (this.header.BitDepth == 16) { - pngMetaData.TransparentGray16 = new Gray16(BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2))); + pngMetadata.TransparentGray16 = new Gray16(BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2))); } else { - pngMetaData.TransparentGray8 = new Gray8(ReadByteLittleEndian(alpha, 0)); + pngMetadata.TransparentGray8 = new Gray8(ReadByteLittleEndian(alpha, 0)); } - pngMetaData.HasTrans = true; + pngMetadata.HasTrans = true; } } } @@ -848,16 +848,16 @@ 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; this.pngColorType = this.header.ColorType; } @@ -867,7 +867,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(ImageMetadata metadata, ReadOnlySpan data) { if (this.ignoreMetadata) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 216f3129f..def57c3b0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -6,7 +6,6 @@ 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 SixLabors.ImageSharp.Advanced; @@ -14,7 +13,7 @@ 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; @@ -185,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.Png 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) + this.pngFilterMethod = options.FilterMethod ?? (options.ColorType == PngColorType.Palette ? PngFilterMethod.None : PngFilterMethod.Paeth); this.compressionLevel = options.CompressionLevel; @@ -211,13 +210,13 @@ namespace SixLabors.ImageSharp.Formats.Png 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; + 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); + this.pngColorType = this.pngColorType ?? pngMetadata.ColorType; + this.pngBitDepth = this.pngBitDepth ?? pngMetadata.BitDepth; + this.use16Bit = this.pngBitDepth == PngBitDepth.Bit16; // Ensure we are not allowing impossible combinations. if (!ColorTypes.ContainsKey(this.pngColorType.Value)) @@ -227,11 +226,11 @@ namespace SixLabors.ImageSharp.Formats.Png stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); - QuantizedFrame quantized = null; + IQuantizedFrame quantized = null; if (this.pngColorType == PngColorType.Palette) { byte bits = (byte)this.pngBitDepth; - if (!ColorTypes[this.pngColorType.Value].Contains(bits)) + if (Array.IndexOf(ColorTypes[this.pngColorType.Value], bits) == -1) { throw new NotSupportedException("Bit depth is not supported or not valid."); } @@ -243,8 +242,11 @@ namespace SixLabors.ImageSharp.Formats.Png } // Create quantized frame returning the palette and set the bit depth. - quantized = this.quantizer.CreateFrameQuantizer(image.GetConfiguration()) - .QuantizeFrame(image.Frames.RootFrame); + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration())) + { + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + } + byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); bits = Math.Max(bits, quantizedBits); @@ -265,7 +267,7 @@ namespace SixLabors.ImageSharp.Formats.Png else { this.bitDepth = (byte)this.pngBitDepth; - if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth)) + if (Array.IndexOf(ColorTypes[this.pngColorType.Value], this.bitDepth) == -1) { throw new NotSupportedException("Bit depth is not supported or not valid."); } @@ -290,14 +292,14 @@ namespace SixLabors.ImageSharp.Formats.Png this.WritePaletteChunk(stream, quantized); } - if (pngMetaData.HasTrans) + if (pngMetadata.HasTrans) { - this.WriteTransparencyChunk(stream, pngMetaData); + this.WriteTransparencyChunk(stream, pngMetadata); } - this.WritePhysicalChunk(stream, metaData); + this.WritePhysicalChunk(stream, metadata); this.WriteGammaChunk(stream); - this.WriteExifChunk(stream, metaData); + this.WriteExifChunk(stream, metadata); this.WriteDataChunks(image.Frames.RootFrame, quantized, stream); this.WriteEndChunk(stream); stream.Flush(); @@ -329,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rawScanlineSpan = this.rawScanline.GetSpan(); ref byte rawScanlineSpanRef = ref MemoryMarshal.GetReference(rawScanlineSpan); - if (this.pngColorType.Equals(PngColorType.Grayscale)) + if (this.pngColorType == PngColorType.Grayscale) { if (this.use16Bit) { @@ -508,7 +510,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The quantized pixels. Can be null. /// The row. /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) + private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IQuantizedFrame quantized, int row) where TPixel : struct, IPixel { switch (this.pngColorType) @@ -659,11 +661,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The containing image data. /// The quantized frame. - private void WritePaletteChunk(Stream stream, QuantizedFrame quantized) + private void WritePaletteChunk(Stream stream, IQuantizedFrame quantized) where TPixel : struct, IPixel { // Grab the palette and write it to the stream. - TPixel[] palette = quantized.Palette; + ReadOnlySpan palette = quantized.Palette.Span; int paletteLength = Math.Min(palette.Length, 256); int colorTableLength = paletteLength * 3; bool anyAlpha = false; @@ -673,7 +675,7 @@ namespace SixLabors.ImageSharp.Formats.Png { ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - Span quantizedSpan = quantized.GetPixelSpan(); + ReadOnlySpan quantizedSpan = quantized.GetPixelSpan(); Rgba32 rgba = default; @@ -714,8 +716,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// Writes the physical dimension information to the stream. /// /// 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) { PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer); @@ -723,11 +725,11 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - /// 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) { @@ -757,42 +759,42 @@ namespace SixLabors.ImageSharp.Formats.Png /// Writes the transparency chunk to the stream /// /// 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) { 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.TransparentGray16.HasValue && this.use16Bit) { - BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetaData.TransparentGray16.Value.PackedValue); + BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentGray16.Value.PackedValue); this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); } - else if (pngMetaData.TransparentGray8.HasValue) + else if (pngMetadata.TransparentGray8.HasValue) { alpha.Clear(); - alpha[1] = pngMetaData.TransparentGray8.Value.PackedValue; + alpha[1] = pngMetadata.TransparentGray8.Value.PackedValue; this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); } } @@ -805,7 +807,7 @@ 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) + private void WriteDataChunks(ImageFrame pixels, IQuantizedFrame quantized, Stream stream) where TPixel : struct, IPixel { this.bytesPerScanline = this.CalculateScanlineLength(this.width); diff --git a/src/ImageSharp/Formats/PngFilterMethod.cs b/src/ImageSharp/Formats/Png/PngFilterMethod.cs similarity index 97% rename from src/ImageSharp/Formats/PngFilterMethod.cs rename to src/ImageSharp/Formats/Png/PngFilterMethod.cs index 73c405625..e16c1234f 100644 --- a/src/ImageSharp/Formats/PngFilterMethod.cs +++ b/src/ImageSharp/Formats/Png/PngFilterMethod.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats.Png { /// /// Provides enumeration of available PNG filter methods. diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index 210e2a837..408e37802 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -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/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs index d5ab3d255..dd951763f 100644 --- a/src/ImageSharp/Formats/Png/PngMetaData.cs +++ b/src/ImageSharp/Formats/Png/PngMetaData.cs @@ -8,20 +8,20 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Provides Png specific metadata information for the image. /// - public class PngMetaData : IDeepCloneable + public class PngMetadata : IDeepCloneable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public PngMetaData() + public PngMetadata() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private PngMetaData(PngMetaData other) + private PngMetadata(PngMetadata other) { this.BitDepth = other.BitDepth; this.ColorType = other.ColorType; @@ -75,6 +75,6 @@ namespace SixLabors.ImageSharp.Formats.Png public bool HasTrans { get; set; } /// - public IDeepCloneable DeepClone() => new PngMetaData(this); + public IDeepCloneable DeepClone() => new PngMetadata(this); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs index 4242b2d55..c23694951 100644 --- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -145,9 +145,9 @@ namespace SixLabors.ImageSharp.Formats.Png { 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); + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); rgba32.R = luminance; rgba32.G = luminance; rgba32.B = luminance; @@ -240,9 +240,9 @@ namespace SixLabors.ImageSharp.Formats.Png else { Rgba32 rgba32 = default; + int offset = 0; 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; @@ -252,6 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Png pixel.FromRgba32(rgba32); Unsafe.Add(ref rowSpanRef, x) = pixel; + offset += bytesPerPixel; } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index 583175b56..405eeafeb 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -126,8 +126,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib int bytesToRead = Math.Min(count, this.currentDataRemaining); this.currentDataRemaining -= bytesToRead; int bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + long length = this.innerStream.Length; - // keep reading data until we've reached the end of the stream or filled the buffer + // Keep reading data until we've reached the end of the stream or filled the buffer while (this.currentDataRemaining == 0 && bytesRead < count) { this.currentDataRemaining = this.getData(); @@ -138,6 +139,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } offset += bytesRead; + + if (offset >= length || offset >= count) + { + return bytesRead; + } + bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining); this.currentDataRemaining -= bytesToRead; bytesRead += this.innerStream.Read(buffer, offset, bytesToRead); @@ -242,4 +249,4 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.compressedStream = new DeflateStream(this, CompressionMode.Decompress, true); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index 2d20c1773..4d5bf6d51 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -1,147 +1,146 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp -{ - /// - /// Options for influencing the drawing functions. - /// - public struct GraphicsOptions - { - /// - /// Represents the default . - /// - public static readonly GraphicsOptions Default = new GraphicsOptions(true); - - private float? blendPercentage; - - private int? antialiasSubpixelDepth; - - private bool? antialias; - +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// Options for influencing the drawing functions. + /// + public struct GraphicsOptions + { + /// + /// 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; - - /// - /// Initializes a new instance of the struct. - /// - /// If set to true [enable antialiasing]. - public GraphicsOptions(bool enableAntialiasing) - { + private PixelAlphaCompositionMode alphaCompositionMode; + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + public GraphicsOptions(bool enableAntialiasing) + { this.colorBlendingMode = PixelColorBlendingMode.Normal; - this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; - this.blendPercentage = 1; - this.antialiasSubpixelDepth = 16; - this.antialias = enableAntialiasing; + this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; + this.blendPercentage = 1; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; } - /// - /// Initializes a new instance of the struct. - /// + /// + /// 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) + /// 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; + this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; + this.blendPercentage = opacity; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; } - /// - /// Initializes a new instance of the struct. - /// + /// + /// 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) + /// 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; + this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; + this.blendPercentage = opacity; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; } - /// - /// Initializes a new instance of the struct. - /// + /// + /// 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) + /// 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; - } - - /// - /// 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 mode to apply to the drawing operation - /// - public PixelColorBlendingMode ColorBlendingMode - { - get => this.colorBlendingMode; - set => this.colorBlendingMode = value; + this.alphaCompositionMode = composition; + this.blendPercentage = opacity; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; } - /// - /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation - /// - public PixelAlphaCompositionMode AlphaCompositionMode - { - get => this.alphaCompositionMode; - set => this.alphaCompositionMode = value; + /// + /// 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 mode to apply to the drawing operation + /// + public PixelColorBlendingMode ColorBlendingMode + { + get => this.colorBlendingMode; + set => this.colorBlendingMode = value; + } + + /// + /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation + /// + public PixelAlphaCompositionMode AlphaCompositionMode + { + get => this.alphaCompositionMode; + set => this.alphaCompositionMode = value; } /// /// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings. /// - /// The pixel format /// the color /// true if the color can be considered opaque /// @@ -149,8 +148,7 @@ namespace SixLabors.ImageSharp /// 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 + internal bool IsOpaqueColorWithoutBlending(Color color) { if (this.ColorBlendingMode != PixelColorBlendingMode.Normal) { @@ -174,6 +172,6 @@ namespace SixLabors.ImageSharp } return true; - } - } + } + } } \ No newline at end of file diff --git a/src/ImageSharp/IImageInfo.cs b/src/ImageSharp/IImageInfo.cs index 6e64aa679..b270c2c4d 100644 --- a/src/ImageSharp/IImageInfo.cs +++ b/src/ImageSharp/IImageInfo.cs @@ -2,13 +2,13 @@ // 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/IImageVisitor.cs b/src/ImageSharp/IImageVisitor.cs new file mode 100644 index 000000000..971c4d37c --- /dev/null +++ b/src/ImageSharp/IImageVisitor.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// A visitor to implement double-dispatch pattern in order to apply pixel-specific operations + /// on non-generic instances. The operation is dispatched by . + /// + internal interface IImageVisitor + { + /// + /// Provides a pixel-specific implementation for a given operation. + /// + /// The image. + /// The pixel type. + void Visit(Image image) + where TPixel : struct, IPixel; + } +} \ No newline at end of file diff --git a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs index 94a2f2cbf..07f892806 100644 --- a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs +++ b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using System.Runtime.CompilerServices; @@ -13,24 +14,28 @@ 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 + internal sealed unsafe class DoubleBufferedStreamReader : IDisposable { /// - /// The length, in bytes, of the buffering chunk + /// The length, in bytes, of the buffering chunk. /// - public const int ChunkLength = 4096; + public const int ChunkLength = 8192; - private const int ChunkLengthMinusOne = ChunkLength - 1; + private const int MaxChunkIndex = ChunkLength - 1; private readonly Stream stream; private readonly IManagedByteBuffer managedBuffer; + private MemoryHandle handle; + + private readonly byte* pinnedChunk; + private readonly byte[] bufferChunk; private readonly int length; - private int bytesRead; + private int chunkIndex; private int position; @@ -42,18 +47,22 @@ namespace SixLabors.ImageSharp.IO public DoubleBufferedStreamReader(MemoryAllocator memoryAllocator, Stream stream) { this.stream = stream; + this.Position = (int)stream.Position; this.length = (int)stream.Length; - this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength, AllocationOptions.Clean); + this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength); this.bufferChunk = this.managedBuffer.Array; + this.handle = this.managedBuffer.Memory.Pin(); + this.pinnedChunk = (byte*)this.handle.Pointer; + this.chunkIndex = ChunkLength; } /// - /// Gets the length, in bytes, of the stream + /// Gets the length, in bytes, of the stream. /// public long Length => this.length; /// - /// Gets or sets the current position within the stream + /// Gets or sets the current position within the stream. /// public long Position { @@ -61,10 +70,20 @@ namespace SixLabors.ImageSharp.IO set { - // Reset everything. It's easier than tracking. - this.position = (int)value; - this.stream.Seek(this.position, SeekOrigin.Begin); - this.bytesRead = ChunkLength; + // Only reset chunkIndex if we are out of bounds of our working chunk + // otherwise we should simply move the value by the diff. + int v = (int)value; + if (this.IsInChunk(v, out int index)) + { + this.chunkIndex = index; + this.position = v; + } + else + { + this.position = v; + this.stream.Seek(value, SeekOrigin.Begin); + this.chunkIndex = ChunkLength; + } } } @@ -73,7 +92,7 @@ namespace SixLabors.ImageSharp.IO /// 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)] + [MethodImpl(InliningOptions.ShortMethod)] public int ReadByte() { if (this.position >= this.length) @@ -81,24 +100,21 @@ namespace SixLabors.ImageSharp.IO return -1; } - if (this.position == 0 || this.bytesRead > ChunkLengthMinusOne) + if (this.chunkIndex > MaxChunkIndex) { - return this.ReadByteSlow(); + this.FillChunk(); } this.position++; - return this.bufferChunk[this.bytesRead++]; + return this.pinnedChunk[this.chunkIndex++]; } /// /// 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; - } + /// The number of bytes to skip. + [MethodImpl(InliningOptions.ShortMethod)] + public void Skip(int count) => this.Position += count; /// /// Reads a sequence of bytes from the current stream and advances the position within the stream @@ -119,36 +135,46 @@ namespace SixLabors.ImageSharp.IO /// 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)] + [MethodImpl(InliningOptions.ShortMethod)] public int Read(byte[] buffer, int offset, int count) { - if (buffer.Length > ChunkLength) + if (count > ChunkLength) { return this.ReadToBufferSlow(buffer, offset, count); } - if (this.position == 0 || count + this.bytesRead > ChunkLength) + if (count + this.chunkIndex > ChunkLength) { return this.ReadToChunkSlow(buffer, offset, count); } - int n = this.GetCount(count); + int n = this.GetCopyCount(count); this.CopyBytes(buffer, offset, n); this.position += n; - this.bytesRead += n; - + this.chunkIndex += n; return n; } /// public void Dispose() { + this.handle.Dispose(); this.managedBuffer?.Dispose(); } - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadByteSlow() + [MethodImpl(InliningOptions.ShortMethod)] + private int GetPositionDifference(int p) => p - this.position; + + [MethodImpl(InliningOptions.ShortMethod)] + private bool IsInChunk(int p, out int index) + { + index = this.GetPositionDifference(p) + this.chunkIndex; + return index > -1 && index < ChunkLength; + } + + [MethodImpl(InliningOptions.ColdPath)] + private void FillChunk() { if (this.position != this.stream.Position) { @@ -156,34 +182,25 @@ namespace SixLabors.ImageSharp.IO } this.stream.Read(this.bufferChunk, 0, ChunkLength); - this.bytesRead = 0; - - this.position++; - return this.bufferChunk[this.bytesRead++]; + this.chunkIndex = 0; } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(InliningOptions.ColdPath)] 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.FillChunk(); - this.stream.Read(this.bufferChunk, 0, ChunkLength); - this.bytesRead = 0; - - int n = this.GetCount(count); + int n = this.GetCopyCount(count); this.CopyBytes(buffer, offset, n); this.position += n; - this.bytesRead += n; + this.chunkIndex += n; return n; } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(InliningOptions.ColdPath)] private int ReadToBufferSlow(byte[] buffer, int offset, int count) { // Read to target but don't copy to our chunk. @@ -194,11 +211,12 @@ namespace SixLabors.ImageSharp.IO int n = this.stream.Read(buffer, offset, count); this.Position += n; + return n; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetCount(int count) + [MethodImpl(InliningOptions.ShortMethod)] + private int GetCopyCount(int count) { int n = this.length - this.position; if (n > count) @@ -214,23 +232,23 @@ namespace SixLabors.ImageSharp.IO return n; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private void CopyBytes(byte[] buffer, int offset, int count) { if (count < 9) { int byteCount = count; - int read = this.bytesRead; - byte[] chunk = this.bufferChunk; + int read = this.chunkIndex; + byte* pinned = this.pinnedChunk; while (--byteCount > -1) { - buffer[offset + byteCount] = chunk[read + byteCount]; + buffer[offset + byteCount] = pinned[read + byteCount]; } } else { - Buffer.BlockCopy(this.bufferChunk, this.bytesRead, buffer, offset, count); + Buffer.BlockCopy(this.bufferChunk, this.chunkIndex, buffer, offset, count); } } } diff --git a/src/ImageSharp/IO/Endianness.cs b/src/ImageSharp/IO/Endianness.cs deleted file mode 100644 index 59b2ae77c..000000000 --- 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/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs index dc5901ff9..11f3d7972 100644 --- a/src/ImageSharp/IO/LocalFileSystem.cs +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -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 ffdab25e2..8d0df599e 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -14,7 +14,7 @@ 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,16 +22,16 @@ 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) + ImageMetadata metadata) where TPixel : struct, IPixel { Buffer2D uninitializedMemoryBuffer = @@ -103,6 +103,18 @@ namespace SixLabors.ImageSharp return (img, format); } + private static (Image img, 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); + } + /// /// Reads the raw image information from the specified stream. /// diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 34927e6e2..25dc5a1e0 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +11,7 @@ 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. @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp /// The mime type or null if none found. public static IImageFormat DetectFormat(Configuration config, byte[] data) { - using (Stream stream = new MemoryStream(data)) + using (var stream = new MemoryStream(data)) { return DetectFormat(config, stream); } @@ -45,48 +44,6 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(byte[] data) => Load(Configuration.Default, data); - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte array containing encoded image data. - /// The mime type of the decoded image. - /// A new . - public static Image Load(byte[] data, out IImageFormat format) => Load(Configuration.Default, data, out format); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The config for the decoder. - /// The byte array containing encoded image data. - /// A new . - public static Image Load(Configuration config, byte[] data) => Load(config, data); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The config for the decoder. - /// The byte array containing image data. - /// The mime type of the decoded image. - /// A new . - public static Image Load(Configuration config, byte[] data, out IImageFormat format) => Load(config, data, out format); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte array containing encoded image data. - /// The decoder. - /// A new . - public static Image Load(byte[] data, IImageDecoder decoder) => Load(data, decoder); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The config for the decoder. - /// The byte array containing image data. - /// The decoder. - /// A new . - public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) => Load(config, data, decoder); - /// /// Load a new instance of from the given encoded byte array. /// @@ -118,9 +75,9 @@ namespace SixLabors.ImageSharp public static Image Load(Configuration config, byte[] data) where TPixel : struct, IPixel { - using (var memoryStream = new MemoryStream(data)) + using (var stream = new MemoryStream(data)) { - return Load(config, memoryStream); + return Load(config, stream); } } @@ -135,9 +92,9 @@ namespace SixLabors.ImageSharp public static Image Load(Configuration config, byte[] data, out IImageFormat format) where TPixel : struct, IPixel { - using (var memoryStream = new MemoryStream(data)) + using (var stream = new MemoryStream(data)) { - return Load(config, memoryStream, out format); + return Load(config, stream, out format); } } @@ -151,9 +108,9 @@ namespace SixLabors.ImageSharp public static Image Load(byte[] data, IImageDecoder decoder) where TPixel : struct, IPixel { - using (var memoryStream = new MemoryStream(data)) + using (var stream = new MemoryStream(data)) { - return Load(memoryStream, decoder); + return Load(stream, decoder); } } @@ -212,29 +169,36 @@ 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. + /// A new . + public static Image Load(ReadOnlySpan data) + where TPixel : struct, IPixel + => Load(Configuration.Default, data); /// - /// Load a new instance of from the given encoded byte span. + /// Load a new instance of from the given encoded byte array. /// - /// 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. + /// A new . + public static Image Load(ReadOnlySpan data, out IImageFormat format) + where TPixel : struct, IPixel + => Load(Configuration.Default, data, out format); /// - /// Load a new instance of from the given encoded byte span. + /// Load a new instance of from the given encoded byte array. /// /// The byte span containing encoded image data. + /// The decoder. /// The pixel format. /// A new . - public static Image Load(ReadOnlySpan data) + public static Image Load(ReadOnlySpan data, IImageDecoder decoder) where TPixel : struct, IPixel - => Load(Configuration.Default, data); + => Load(Configuration.Default, data, decoder); /// /// Load a new instance of from the given encoded byte span. @@ -300,5 +264,135 @@ namespace SixLabors.ImageSharp } } } + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing image data. + /// The detected format. + /// A new . + public static Image Load(byte[] data, out IImageFormat format) => + Load(Configuration.Default, data, out format); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing encoded image data. + /// The decoder. + /// A new . + 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 config for the decoder. + /// The byte array containing encoded image data. + /// A new . + public static Image Load(Configuration config, byte[] data) => Load(config, data, out _); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// The decoder. + /// A new . + public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) + { + using (var stream = new MemoryStream(data)) + { + return Load(config, stream, decoder); + } + } + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// The mime type of the decoded image. + /// A new . + public static Image Load(Configuration config, byte[] data, out IImageFormat format) + { + using (var stream = new MemoryStream(data)) + { + return Load(config, stream, out format); + } + } + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing image data. + /// A new . + public static Image Load(ReadOnlySpan data) => Load(Configuration.Default, data); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing image data. + /// The decoder. + /// A new . + 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. + /// A new . + 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. + /// A new . + public static unsafe Image Load(Configuration config, ReadOnlySpan data) => Load(config, data, out _); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The Configuration. + /// The byte span containing image data. + /// The decoder. + /// A new . + public static unsafe Image Load( + Configuration config, + ReadOnlySpan data, + IImageDecoder decoder) + { + fixed (byte* ptr = &data.GetPinnableReference()) + { + using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) + { + return Load(config, stream, decoder); + } + } + } + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The configuration options. + /// The byte span containing image data. + /// The of the decoded image.> + /// A new . + public static unsafe Image Load( + Configuration config, + ReadOnlySpan data, + out IImageFormat format) + { + fixed (byte* ptr = &data.GetPinnableReference()) + { + using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) + { + return Load(config, stream, out format); + } + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index b13cef482..08ed381c5 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -11,7 +11,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. @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path) => Load(path); + public static Image Load(string path) => Load(Configuration.Default, path); /// /// Create a new instance of the class from the given file. @@ -57,7 +57,7 @@ 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. @@ -68,19 +68,7 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(Configuration config, string path) => Load(config, path); - - /// - /// Create a new instance of the class from the given file. - /// - /// The config for the decoder. - /// The file path to the image. - /// The mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// - /// A new . - public static Image Load(Configuration config, string path, out IImageFormat format) => Load(config, path, out format); + public static Image Load(Configuration config, string path) => Load(config, path, out _); /// /// Create a new instance of the class from the given file. @@ -92,7 +80,13 @@ namespace SixLabors.ImageSharp /// 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); + public static Image Load(Configuration config, string path, IImageDecoder decoder) + { + using (Stream stream = config.FileSystem.OpenRead(path)) + { + return Load(config, stream, decoder); + } + } /// /// Create a new instance of the class from the given file. @@ -103,7 +97,7 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path, IImageDecoder decoder) => Load(path, decoder); + public static Image Load(string path, IImageDecoder decoder) => Load(Configuration.Default, path, decoder); /// /// Create a new instance of the class from the given file. @@ -175,6 +169,25 @@ namespace SixLabors.ImageSharp } } + /// + /// 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. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// A new . + public static Image Load(Configuration config, string path, out IImageFormat format) + { + using (Stream stream = config.FileSystem.OpenRead(path)) + { + return Load(config, stream, out format); + } + } + /// /// Create a new instance of the class from the given file. /// diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 3236e0007..c4336c9ac 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,7 +13,7 @@ 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. @@ -56,56 +56,66 @@ namespace SixLabors.ImageSharp => WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default)); /// - /// 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); + /// Image cannot be loaded. + /// A new .> + public static Image Load(Stream stream, out IImageFormat format) => Load(Configuration.Default, stream, out format); /// - /// 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. /// Thrown if the stream is not readable. - /// A new .> - public static Image Load(Stream stream) => Load(stream); + /// Image cannot be loaded. + /// A new .> + public static Image Load(Stream stream) => Load(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 decoder. /// Thrown if the stream is not readable. - /// A new .> - public static Image Load(Stream stream, IImageDecoder decoder) => Load(stream, decoder); + /// Image cannot be loaded. + /// A new .> + public static Image Load(Stream stream, IImageDecoder decoder) => Load(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 config for the decoder. /// The stream containing image information. + /// The decoder. /// Thrown if the stream is not readable. - /// A new .> - public static Image Load(Configuration config, Stream stream) => Load(config, stream); + /// Image cannot be loaded. + /// A new .> + public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) => + WithSeekableStream(config, stream, s => decoder.Decode(config, s)); /// - /// 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 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); + /// Image cannot be loaded. + /// A new .> + public static Image Load(Configuration config, Stream stream) => Load(config, stream, out _); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. /// Thrown if the stream is not readable. + /// Image cannot be loaded. /// The pixel format. /// A new .> public static Image Load(Stream stream) @@ -118,6 +128,7 @@ namespace SixLabors.ImageSharp /// The stream containing image information. /// the mime type of the decoded image. /// Thrown if the stream is not readable. + /// Image cannot be loaded. /// The pixel format. /// A new .> public static Image Load(Stream stream, out IImageFormat format) @@ -130,6 +141,7 @@ namespace SixLabors.ImageSharp /// The stream containing image information. /// The decoder. /// Thrown if the stream is not readable. + /// Image cannot be loaded. /// The pixel format. /// A new .> public static Image Load(Stream stream, IImageDecoder decoder) @@ -143,6 +155,7 @@ namespace SixLabors.ImageSharp /// The stream containing image information. /// The decoder. /// Thrown if the stream is not readable. + /// Image cannot be loaded. /// The pixel format. /// A new .> public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) @@ -155,6 +168,7 @@ namespace SixLabors.ImageSharp /// The configuration options. /// The stream containing image information. /// Thrown if the stream is not readable. + /// Image cannot be loaded. /// The pixel format. /// A new .> public static Image Load(Configuration config, Stream stream) @@ -168,6 +182,7 @@ namespace SixLabors.ImageSharp /// The stream containing image information. /// the mime type of the decoded image. /// Thrown if the stream is not readable. + /// Image cannot be loaded. /// The pixel format. /// A new .> public static Image Load(Configuration config, Stream stream, out IImageFormat format) @@ -191,7 +206,40 @@ namespace SixLabors.ImageSharp sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); } - throw new NotSupportedException(sb.ToString()); + throw new UnknownImageFormatException(sb.ToString()); + } + + /// + /// 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 mime type of the decoded image. + /// Thrown if the stream is not readable. + /// Image cannot be loaded. + /// A new . + public static Image Load(Configuration config, Stream stream, out IImageFormat format) + { + config = config ?? Configuration.Default; + (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); + + format = data.format; + + if (data.img != null) + { + return data.img; + } + + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); + + foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) + { + sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + } + + throw new UnknownImageFormatException(sb.ToString()); } private static T WithSeekableStream(Configuration config, Stream stream, Func action) @@ -221,4 +269,4 @@ namespace SixLabors.ImageSharp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index 282f98086..eb7ce261f 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -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. diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index c2935bed9..095991b07 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -5,38 +5,37 @@ 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 . /// An instance public static Image WrapMemory( Configuration config, Memory pixelMemory, int width, int height, - ImageMetaData metaData) + ImageMetadata metadata) where TPixel : struct, IPixel { var memorySource = new MemorySource(pixelMemory); - return new Image(config, memorySource, width, height, metaData); + return new Image(config, memorySource, width, height, metadata); } /// @@ -44,11 +43,11 @@ 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. + /// An instance. public static Image WrapMemory( Configuration config, Memory pixelMemory, @@ -56,7 +55,7 @@ namespace SixLabors.ImageSharp int height) where TPixel : struct, IPixel { - return WrapMemory(config, pixelMemory, width, height, new ImageMetaData()); + return WrapMemory(config, pixelMemory, width, height, new ImageMetadata()); } /// @@ -64,11 +63,11 @@ namespace SixLabors.ImageSharp /// 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, @@ -86,22 +85,22 @@ 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 /// An instance public static Image WrapMemory( Configuration config, IMemoryOwner pixelMemoryOwner, int width, int height, - ImageMetaData metaData) + ImageMetadata metadata) where TPixel : struct, IPixel { var memorySource = new MemorySource(pixelMemoryOwner, false); - return new Image(config, memorySource, width, height, metaData); + return new Image(config, memorySource, width, height, metadata); } /// @@ -111,11 +110,11 @@ 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. /// An instance public static Image WrapMemory( Configuration config, @@ -124,7 +123,7 @@ namespace SixLabors.ImageSharp int height) where TPixel : struct, IPixel { - return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetaData()); + return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetadata()); } /// @@ -135,10 +134,10 @@ 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, diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs new file mode 100644 index 000000000..86fef715d --- /dev/null +++ b/src/ImageSharp/Image.cs @@ -0,0 +1,150 @@ +// 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.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +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, IConfigurable + { + private Size size; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// 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 . + /// + protected Configuration Configuration { get; } + + /// + /// 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; + + /// + /// Gets the pixel buffer. + /// + Configuration IConfigurable.Configuration => this.Configuration; + + /// + public abstract void Dispose(); + + /// + /// 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)); + + EncodeVisitor visitor = new EncodeVisitor(encoder, stream); + this.AcceptVisitor(visitor); + } + + /// + /// 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); + + /// + /// 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 : struct, IPixel; + + /// + /// Accept a . + /// Implemented by invoking + /// with the pixel type of the image. + /// + internal abstract void AcceptVisitor(IImageVisitor visitor); + + /// + /// Update the size of the image after mutation. + /// + /// The . + protected void UpdateSize(Size size) => this.size = size; + + private class EncodeVisitor : IImageVisitor + { + 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 : struct, IPixel + { + this.encoder.Encode(image, this.stream); + } + } + } +} diff --git a/src/ImageSharp/ImageExtensions.Internal.cs b/src/ImageSharp/ImageExtensions.Internal.cs index dfdbbd89b..5b5e56665 100644 --- a/src/ImageSharp/ImageExtensions.Internal.cs +++ b/src/ImageSharp/ImageExtensions.Internal.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp { diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index cbf93275c..ec4c364d8 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -12,19 +12,17 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// - /// Extension methods over Image{TPixel} + /// Extension methods over Image{TPixel}. /// 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 + public static void Save(this Image source, string filePath) { Guard.NotNullOrWhiteSpace(filePath, nameof(filePath)); @@ -33,7 +31,7 @@ namespace SixLabors.ImageSharp 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:"); + sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:"); foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) { sb.AppendLine($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}"); @@ -47,7 +45,7 @@ namespace SixLabors.ImageSharp if (encoder is null) { var sb = new StringBuilder(); - sb.AppendLine($"Can't find encoder for file extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); + sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { sb.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}"); @@ -62,13 +60,11 @@ namespace SixLabors.ImageSharp /// /// Writes the image to the given stream using the currently loaded image format. /// - /// The pixel format. /// The source image. /// The file path to save the image to. /// The encoder to save the image with. /// Thrown if the encoder is null. - public static void Save(this Image source, string filePath, IImageEncoder encoder) - where TPixel : struct, IPixel + public static void Save(this Image source, string filePath, IImageEncoder encoder) { Guard.NotNull(encoder, nameof(encoder)); using (Stream fs = source.GetConfiguration().FileSystem.Create(filePath)) @@ -80,13 +76,11 @@ namespace SixLabors.ImageSharp /// /// 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 + public static void Save(this Image source, Stream stream, IImageFormat format) { Guard.NotNull(format, nameof(format)); IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); @@ -94,7 +88,7 @@ namespace SixLabors.ImageSharp 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) { diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 33dbe31df..9e90aeaf5 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -9,9 +9,9 @@ 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,7 +22,7 @@ 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) + internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) where TPixel : struct, IPixel => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); @@ -35,7 +35,7 @@ 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) + internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) where TPixel : struct, IPixel { int count = width * height; @@ -48,4 +48,4 @@ namespace SixLabors.ImageSharp 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 000000000..f3fe1ed8d --- /dev/null +++ b/src/ImageSharp/ImageFrame.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +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 : IDisposable + { + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The width. + /// The 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; + this.MemoryAllocator = configuration.MemoryAllocator; + this.Width = width; + this.Height = height; + this.Metadata = metadata; + } + + /// + /// Gets the to use for buffer allocations. + /// + public MemoryAllocator MemoryAllocator { get; } + + /// + /// Gets the instance associated with this . + /// + internal Configuration Configuration { get; } + + /// + /// 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; } + + /// + /// 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 abstract void Dispose(); + + internal abstract void CopyPixelsTo(Span destination) + where TDestinationPixel : struct, 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 bbe05ce43..c5bd02c79 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -4,149 +4,69 @@ 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.NonGenericGetEnumerator(); - 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 000000000..ffd7225bd --- /dev/null +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -0,0 +1,358 @@ +// Copyright (c) Six Labors and contributors. +// 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 : struct, 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, 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 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) + { + ImageFrame result = new ImageFrame( + this.parent.GetConfiguration(), + source.Size(), + source.Metadata.DeepClone()); + source.CopyPixelsTo(result.PixelBuffer.Span); + return result; + } + } +} diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index f69ae3757..982f8d8f2 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -3,10 +3,11 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -15,10 +16,12 @@ 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 + public sealed class ImageFrame : ImageFrame, IPixelSource, IDisposable where TPixel : struct, IPixel { private bool isDisposed; @@ -30,7 +33,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 +42,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 +54,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.MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); } /// @@ -65,7 +72,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,17 +83,14 @@ 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); } @@ -98,7 +102,7 @@ namespace SixLabors.ImageSharp /// 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()) + : this(configuration, width, height, memorySource, new ImageFrameMetadata()) { } @@ -109,18 +113,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, MemorySource 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 +129,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(); } - /// - /// 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,21 +146,6 @@ 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. /// @@ -188,18 +161,6 @@ namespace SixLabors.ImageSharp set => this.PixelBuffer[x, y] = value; } - /// - /// Gets the size of the frame. - /// - /// The - public Size Size() => new Size(this.Width, this.Height); - - /// - /// Gets the bounds of the frame. - /// - /// The - public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); - /// /// Gets a reference to the pixel at the specified position. /// @@ -232,12 +193,13 @@ 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() + public override void Dispose() { if (this.isDisposed) { @@ -251,6 +213,17 @@ namespace SixLabors.ImageSharp this.isDisposed = true; } + internal override void CopyPixelsTo(Span destination) + { + if (typeof(TPixel) == typeof(TDestinationPixel)) + { + Span dest1 = MemoryMarshal.Cast(destination); + this.PixelBuffer.Span.CopyTo(dest1); + } + + PixelOperations.Instance.To(this.Configuration, this.PixelBuffer.Span, destination); + } + /// public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})"; @@ -289,7 +262,7 @@ namespace SixLabors.ImageSharp 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()); ParallelHelper.IterateRows( this.Bounds(), @@ -325,8 +298,5 @@ namespace SixLabors.ImageSharp span.Fill(value); } } - - /// - void IDisposable.Dispose() => this.Dispose(); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageInfo.cs b/src/ImageSharp/ImageInfo.cs index 6f894cb59..12dcf1ed7 100644 --- a/src/ImageSharp/ImageInfo.cs +++ b/src/ImageSharp/ImageInfo.cs @@ -2,7 +2,7 @@ // 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/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 1cdee81a2..3b0211434 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -1,127 +1,34 @@ - + + + + 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 + + netcoreapp2.1;netstandard1.3;netstandard2.0;net472 + true true SixLabors.ImageSharp SixLabors.ImageSharp Image Resize Crop Gif Jpg Jpeg Bitmap Png Core - https://raw.githubusercontent.com/SixLabors/Branding/master/icons/imagesharp/sixlabors.imagesharp.128.png - https://github.com/SixLabors/ImageSharp - http://www.apache.org/licenses/LICENSE-2.0 - git - https://github.com/SixLabors/ImageSharp - full - portable - True - IOperation - 7.3 + SixLabors.ImageSharp + netcoreapp2.1;netstandard1.3;netstandard2.0;net472 $(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS - - - - - - - - - - - All - - - - - - - - - - - - ..\..\ImageSharp.ruleset - SixLabors.ImageSharp - - - true - - - TextTemplatingFileGenerator - Block8x8F.Generated.cs - - - TextTemplatingFileGenerator - GenericBlock8x8.Generated.cs - - - TextTemplatingFileGenerator - Block8x8F.Generated.cs - - - TextTemplatingFileGenerator - PixelOperations{TPixel}.Generated.cs - - - TextTemplatingFileGenerator - Argb32.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Bgr24.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Bgra32.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Gray8.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Gray16.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Rgb24.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Rgba32.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Rgb48.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Rgba64.PixelOperations.Generated.cs - - - PorterDuffFunctions.Generated.cs - TextTemplatingFileGenerator - - - DefaultPixelBlenders.Generated.cs - TextTemplatingFileGenerator - + + True @@ -199,7 +106,89 @@ PorterDuffFunctions.Generated.tt + - + - \ No newline at end of file + + + + 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 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings index a7337240a..018ca75cd 100644 --- a/src/ImageSharp/ImageSharp.csproj.DotSettings +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -1,4 +1,5 @@  + True True True True @@ -6,5 +7,7 @@ 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 178194b21..5d0364dd8 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -3,26 +3,24 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; 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 + public sealed class Image : Image where TPixel : struct, IPixel { - private readonly Configuration configuration; - /// /// Initializes a new instance of the class /// with the height and the width of the image. @@ -31,7 +29,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 +42,7 @@ 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()) { } @@ -67,28 +65,29 @@ 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 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, + MemorySource memorySource, + 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); } @@ -101,11 +100,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 +118,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. /// - public ImageFrameCollection Frames { get; } + public new ImageFrameCollection Frames { get; } /// /// Gets the root frame. @@ -161,29 +146,14 @@ namespace SixLabors.ImageSharp public TPixel this[int x, int y] { get => this.PixelSource.PixelBuffer[x, y]; - set => this.PixelSource.PixelBuffer[x, y] = value; } - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream or encoder is null. - public void Save(Stream stream, IImageEncoder encoder) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); - - encoder.Encode(this, stream); - } - /// /// Clones the current image /// /// Returns a new image with all the same metadata as the original. - public Image Clone() => this.Clone(this.configuration); + public Image Clone() => this.Clone(this.Configuration); /// /// Clones the current image with the given configuration. @@ -192,33 +162,32 @@ 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); + IEnumerable> clonedFrames = + this.Frames.Select, ImageFrame>(x => x.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 - public Image CloneAs() - where TPixel2 : struct, IPixel => this.CloneAs(this.configuration); - /// /// Returns a copy of the image in the given pixel format. /// /// The pixel format. /// The configuration providing initialization code which allows extending the library. - /// The - public Image CloneAs(Configuration configuration) - where TPixel2 : struct, IPixel + /// The . + public override Image CloneAs(Configuration configuration) { - IEnumerable> clonedFrames = this.Frames.Select(x => x.CloneAs(configuration)); - return new Image(configuration, this.MetaData.DeepClone(), clonedFrames); + IEnumerable> clonedFrames = + this.Frames.Select, ImageFrame>(x => x.CloneAs(configuration)); + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); } /// - public void Dispose() => this.Frames.Dispose(); + public override void Dispose() => this.Frames.Dispose(); + + /// + internal override void AcceptVisitor(IImageVisitor visitor) + { + visitor.Visit(this); + } /// public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; @@ -235,6 +204,29 @@ 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; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 17ab6e252..61fcb99db 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -2,7 +2,10 @@ // 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.Primitives; @@ -14,55 +17,135 @@ namespace SixLabors.ImageSharp.Memory internal static class Buffer2DExtensions { /// - /// Gets a to the backing buffer of . + /// Copy columns of inplace, + /// from positions starting at to positions at . /// - internal static Span GetSpan(this Buffer2D buffer) + public static unsafe void CopyColumns( + this Buffer2D buffer, + int sourceIndex, + int destIndex, + int columnCount) where T : struct { - return buffer.MemorySource.GetSpan(); + DebugGuard.NotNull(buffer, nameof(buffer)); + DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex)); + DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex)); + CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount); + + int elementSize = Unsafe.SizeOf(); + int width = buffer.Width * elementSize; + int sOffset = sourceIndex * elementSize; + int dOffset = destIndex * elementSize; + long count = columnCount * elementSize; + + Span span = MemoryMarshal.AsBytes(buffer.Memory.Span); + + fixed (byte* ptr = span) + { + byte* basePtr = (byte*)ptr; + for (int y = 0; y < buffer.Height; y++) + { + byte* sPtr = basePtr + sOffset; + byte* dPtr = basePtr + dOffset; + + Buffer.MemoryCopy(sPtr, dPtr, count, count); + + basePtr += width; + } + } } /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// Returns a representing the full area of the buffer. + /// + /// The element type + /// The + /// The + public 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' + /// + /// The element type + /// The + /// The rectangle subarea + /// The + public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle) + where T : struct => + new BufferArea(buffer, rectangle); + + public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height) + where T : struct => + new BufferArea(buffer, new Rectangle(x, y, width, height)); + + /// + /// Return a to the whole area of 'buffer' + /// + /// The element type + /// The + /// The + public static BufferArea GetArea(this Buffer2D buffer) + where T : struct => + new BufferArea(buffer); + + public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY) + where T : struct => + new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY)); + + /// + /// Gets a span for all the pixels in defined by + /// + public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) + where T : struct + { + return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. /// /// 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) + public static Memory GetRowMemory(this Buffer2D buffer, int y) where T : struct { - return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x); + return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width); } /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// Gets a to the row 'y' beginning from the pixel at 'x'. /// /// The buffer + /// The x coordinate (position in the row) /// The y (row) coordinate /// The element type /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetRowSpan(this Buffer2D buffer, int y) + public static Span GetRowSpan(this Buffer2D buffer, int x, int y) where T : struct { - return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width); + return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x); } /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// 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) + public static Span GetRowSpan(this Buffer2D buffer, int y) where T : struct { - return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width); + return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width); } /// @@ -78,49 +161,28 @@ namespace SixLabors.ImageSharp.Memory } /// - /// Returns a representing the full area of the buffer. + /// Gets a to the backing buffer of . /// - /// The element type - /// The - /// The - public static Rectangle FullRectangle(this Buffer2D buffer) + internal static Span GetSpan(this Buffer2D buffer) where T : struct { - return new Rectangle(0, 0, buffer.Width, buffer.Height); + return buffer.MemorySource.GetSpan(); } - /// - /// Return a to the subarea represented by 'rectangle' - /// - /// The element type - /// The - /// The rectangle subarea - /// The - public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle) - where T : struct => new BufferArea(buffer, rectangle); - - public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height) - where T : struct => new BufferArea(buffer, new Rectangle(x, y, width, height)); - - 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)); - - /// - /// 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); - - /// - /// Gets a span for all the pixels in defined by - /// - public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) + [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/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs index 0750e0368..815918754 100644 --- a/src/ImageSharp/Memory/RowInterval.cs +++ b/src/ImageSharp/Memory/RowInterval.cs @@ -1,6 +1,8 @@ -// Copyright(c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + using SixLabors.Primitives; namespace SixLabors.ImageSharp.Memory @@ -8,7 +10,7 @@ namespace SixLabors.ImageSharp.Memory /// /// Represents an interval of rows in a and/or /// - internal readonly struct RowInterval + internal readonly struct RowInterval : IEquatable { /// /// Initializes a new instance of the struct. @@ -22,24 +24,47 @@ namespace SixLabors.ImageSharp.Memory } /// - /// Gets the INCLUSIVE minimum + /// Gets the INCLUSIVE minimum. /// public int Min { get; } /// - /// Gets the EXCLUSIVE maximum + /// Gets the EXCLUSIVE maximum. /// public int Max { get; } /// - /// Gets the difference ( - ) + /// Gets the difference ( - ). /// public int Height => this.Max - this.Min; + public static bool operator ==(RowInterval left, RowInterval right) + { + return left.Equals(right); + } + + public static bool operator !=(RowInterval left, RowInterval right) + { + return !left.Equals(right); + } + /// - public override string ToString() + public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]"; + + public RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max); + + public RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length); + + 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); } } \ No newline at end of file diff --git a/src/ImageSharp/MetaData/FrameDecodingMode.cs b/src/ImageSharp/MetaData/FrameDecodingMode.cs index 2863fbf8f..835e43354 100644 --- a/src/ImageSharp/MetaData/FrameDecodingMode.cs +++ b/src/ImageSharp/MetaData/FrameDecodingMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData +namespace SixLabors.ImageSharp.Metadata { /// /// Enumerated frame process modes to apply to multi-frame images. diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs index f1f884be6..3858a7d0a 100644 --- a/src/ImageSharp/MetaData/ImageFrameMetaData.cs +++ b/src/ImageSharp/MetaData/ImageFrameMetaData.cs @@ -4,62 +4,62 @@ using System.Collections.Generic; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.MetaData +namespace SixLabors.ImageSharp.Metadata { /// /// Encapsulates the metadata of an image frame. /// - public sealed class ImageFrameMetaData : IDeepCloneable + public sealed class ImageFrameMetadata : IDeepCloneable { - private readonly Dictionary formatMetaData = new Dictionary(); + private readonly Dictionary formatMetadata = new Dictionary(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - internal ImageFrameMetaData() + internal ImageFrameMetadata() { } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// by making a copy from other metadata. /// /// - /// The other to create this instance from. + /// The other to create this instance from. /// - internal ImageFrameMetaData(ImageFrameMetaData other) + internal ImageFrameMetadata(ImageFrameMetadata other) { DebugGuard.NotNull(other, nameof(other)); - foreach (KeyValuePair meta in other.formatMetaData) + foreach (KeyValuePair meta in other.formatMetadata) { - this.formatMetaData.Add(meta.Key, meta.Value.DeepClone()); + this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); } } /// - public ImageFrameMetaData DeepClone() => new ImageFrameMetaData(this); + 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 type of format metadata. + /// The type of format frame metadata. /// The key of the value to get. /// - /// The . + /// The . /// - public TFormatFrameMetaData GetFormatMetaData(IImageFormat key) - where TFormatMetaData : class - where TFormatFrameMetaData : class, IDeepCloneable + public TFormatFrameMetadata GetFormatMetadata(IImageFormat key) + where TFormatMetadata : class + where TFormatFrameMetadata : class, IDeepCloneable { - if (this.formatMetaData.TryGetValue(key, out IDeepCloneable meta)) + if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) { - return (TFormatFrameMetaData)meta; + return (TFormatFrameMetadata)meta; } - TFormatFrameMetaData newMeta = key.CreateDefaultFormatFrameMetaData(); - this.formatMetaData[key] = newMeta; + TFormatFrameMetadata newMeta = key.CreateDefaultFormatFrameMetadata(); + this.formatMetadata[key] = newMeta; return newMeta; } } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 73549d98a..b9efca4fe 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -3,15 +3,15 @@ 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.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.MetaData +namespace SixLabors.ImageSharp.Metadata { /// /// Encapsulates the metadata of an image. /// - public sealed class ImageMetaData : IDeepCloneable + public sealed class ImageMetadata : IDeepCloneable { /// /// The default horizontal resolution value (dots per inch) in x direction. @@ -31,14 +31,14 @@ namespace SixLabors.ImageSharp.MetaData /// public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; - private readonly Dictionary formatMetaData = new Dictionary(); + private readonly Dictionary formatMetadata = new Dictionary(); private double horizontalResolution; private double verticalResolution; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - internal ImageMetaData() + internal ImageMetadata() { this.horizontalResolution = DefaultHorizontalResolution; this.verticalResolution = DefaultVerticalResolution; @@ -46,21 +46,21 @@ namespace SixLabors.ImageSharp.MetaData } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// by making a copy from other metadata. /// /// - /// The other to create this instance from. + /// The other to create this instance from. /// - private ImageMetaData(ImageMetaData other) + private ImageMetadata(ImageMetadata other) { this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; this.ResolutionUnits = other.ResolutionUnits; - foreach (KeyValuePair meta in other.formatMetaData) + foreach (KeyValuePair meta in other.formatMetadata) { - this.formatMetaData.Add(meta.Key, meta.Value.DeepClone()); + this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); } foreach (ImageProperty property in other.Properties) @@ -135,26 +135,26 @@ namespace SixLabors.ImageSharp.MetaData /// /// Gets the metadata value associated with the specified key. /// - /// The type of metadata. + /// The type of metadata. /// The key of the value to get. /// - /// The . + /// The . /// - public TFormatMetaData GetFormatMetaData(IImageFormat key) - where TFormatMetaData : class, IDeepCloneable + public TFormatMetadata GetFormatMetadata(IImageFormat key) + where TFormatMetadata : class, IDeepCloneable { - if (this.formatMetaData.TryGetValue(key, out IDeepCloneable meta)) + if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) { - return (TFormatMetaData)meta; + return (TFormatMetadata)meta; } - TFormatMetaData newMeta = key.CreateDefaultFormatMetaData(); - this.formatMetaData[key] = newMeta; + TFormatMetadata newMeta = key.CreateDefaultFormatMetadata(); + this.formatMetadata[key] = newMeta; return newMeta; } /// - public ImageMetaData DeepClone() => new ImageMetaData(this); + public ImageMetadata DeepClone() => new ImageMetadata(this); /// /// Looks up a property with the provided name. @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.MetaData } /// - /// Synchronizes the profiles with the current meta data. + /// Synchronizes the profiles with the current metadata. /// internal void SyncProfiles() => this.ExifProfile?.Sync(this); } diff --git a/src/ImageSharp/MetaData/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs index 24a3686de..905e42dab 100644 --- a/src/ImageSharp/MetaData/ImageProperty.cs +++ b/src/ImageSharp/MetaData/ImageProperty.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData +namespace SixLabors.ImageSharp.Metadata { /// /// Stores meta information about a image, like the name of the author, @@ -107,10 +107,7 @@ namespace SixLabors.ImageSharp.MetaData /// /// A containing a fully qualified type name. /// - public override string ToString() - { - return $"ImageProperty [ Name={this.Name}, Value={this.Value} ]"; - } + public override string ToString() => $"ImageProperty [ Name={this.Name}, Value={this.Value} ]"; /// /// Indicates whether the current object is equal to another object of the same type. diff --git a/src/ImageSharp/MetaData/PixelResolutionUnit.cs b/src/ImageSharp/MetaData/PixelResolutionUnit.cs index ce848004f..661e7a308 100644 --- a/src/ImageSharp/MetaData/PixelResolutionUnit.cs +++ b/src/ImageSharp/MetaData/PixelResolutionUnit.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData +namespace SixLabors.ImageSharp.Metadata { /// /// Provides enumeration of available pixel density units. diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs index 555cadafe..c7112c47a 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs @@ -1,18 +1,20 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal static class ExifConstants { - public static readonly byte[] LittleEndianByteOrderMarker = { + public static readonly byte[] LittleEndianByteOrderMarker = + { (byte)'I', (byte)'I', 0x2A, 0x00, }; - public static readonly byte[] BigEndianByteOrderMarker = { + public static readonly byte[] BigEndianByteOrderMarker = + { (byte)'M', (byte)'M', 0x00, diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs index 5bd38b195..83e7f7fe8 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// /// Specifies exif data types. diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs index b1b42ad43..d22dc730f 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// /// Specifies which parts will be written when the profile is added to an image. diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 37ceaf10f..3d90cb3a9 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -7,7 +7,7 @@ using System.IO; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// /// Represents an EXIF profile providing access to the collection of values. @@ -249,13 +249,13 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif public ExifProfile DeepClone() => new ExifProfile(this); /// - /// Synchronizes the profiles with the specified meta data. + /// Synchronizes the profiles with the specified metadata. /// - /// The meta data. - internal void Sync(ImageMetaData metaData) + /// The metadata. + internal void Sync(ImageMetadata metadata) { - this.SyncResolution(ExifTag.XResolution, metaData.HorizontalResolution); - this.SyncResolution(ExifTag.YResolution, metaData.VerticalResolution); + this.SyncResolution(ExifTag.XResolution, metadata.HorizontalResolution); + this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); } private void SyncResolution(ExifTag tag, double resolution) diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs index 8ec9eea27..e40e6c26c 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs @@ -8,20 +8,19 @@ 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 +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// - /// Reads and parses EXIF data from a byte array + /// 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 bool isBigEndian; private uint exifOffset; private uint gpsOffset; @@ -38,12 +37,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); /// - /// Gets the thumbnail length in the byte stream + /// Gets the thumbnail length in the byte stream. /// public uint ThumbnailLength { get; private set; } /// - /// Gets the thumbnail offset position in the byte stream + /// Gets the thumbnail offset position in the byte stream. /// public uint ThumbnailOffset { get; private set; } @@ -74,10 +73,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif var values = new List(); // II == 0x4949 - if (this.ReadUInt16() == 0x4949) - { - this.endianness = Endianness.LittleEndian; - } + this.isBigEndian = !(this.ReadUInt16() == 0x4949); if (this.ReadUInt16() != 0x002A) { @@ -382,7 +378,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif [MethodImpl(InliningOptions.ShortMethod)] private TEnum ToEnum(int value, TEnum defaultValue) - where TEnum : struct + where TEnum : struct, Enum { if (EnumHelper.IsDefined(value)) { @@ -458,7 +454,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return default; } - long intValue = this.endianness == Endianness.BigEndian + long intValue = this.isBigEndian ? BinaryPrimitives.ReadInt64BigEndian(buffer) : BinaryPrimitives.ReadInt64LittleEndian(buffer); @@ -473,7 +469,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return default; } - return this.endianness == Endianness.BigEndian + return this.isBigEndian ? BinaryPrimitives.ReadUInt32BigEndian(buffer) : BinaryPrimitives.ReadUInt32LittleEndian(buffer); } @@ -485,7 +481,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return default; } - return this.endianness == Endianness.BigEndian + return this.isBigEndian ? BinaryPrimitives.ReadUInt16BigEndian(buffer) : BinaryPrimitives.ReadUInt16LittleEndian(buffer); } @@ -497,7 +493,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return default; } - int intValue = this.endianness == Endianness.BigEndian + int intValue = this.isBigEndian ? BinaryPrimitives.ReadInt32BigEndian(buffer) : BinaryPrimitives.ReadInt32LittleEndian(buffer); @@ -526,7 +522,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return default; } - return this.endianness == Endianness.BigEndian + return this.isBigEndian ? BinaryPrimitives.ReadInt32BigEndian(buffer) : BinaryPrimitives.ReadInt32LittleEndian(buffer); } @@ -551,13 +547,13 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return default; } - return this.endianness == Endianness.BigEndian + return this.isBigEndian ? BinaryPrimitives.ReadInt16BigEndian(buffer) : BinaryPrimitives.ReadInt16LittleEndian(buffer); } - private class EnumHelper - where TEnum : struct + private sealed class EnumHelper + where TEnum : struct, Enum { private static readonly int[] Values = Enum.GetValues(typeof(TEnum)).Cast() .Select(e => Convert.ToInt32(e)).OrderBy(e => e).ToArray(); diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs index 625f95b63..ddd4591fa 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// /// All exif tags from the Exif standard 2.2 diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs index 286fdbe57..0188c662e 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs @@ -4,7 +4,7 @@ using System; using System.Reflection; -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// /// Class that provides a description for an ExifTag value. diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs index e497fc7fa..0ed3a43b7 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using static SixLabors.ImageSharp.MetaData.Profiles.Exif.ExifTag; +using static SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag; -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal static class ExifTags { @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif LensMake, LensModel, LensSerialNumber - }; + }; /// /// The collection of GPS tags diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs index 409c55253..05a9f35c9 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs @@ -6,7 +6,7 @@ using System.Globalization; using System.Text; using SixLabors.ImageSharp.Primitives; -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// /// Represent the value of the EXIF profile. diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs index 9079526d5..93fe7fbbe 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Text; using SixLabors.ImageSharp.Primitives; -namespace SixLabors.ImageSharp.MetaData.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// /// Contains methods for writing EXIF metadata. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs index 516887bcd..7bf1f8421 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A segment of a curve diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs index d168c5c28..d013f6ef1 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A formula based curve segment diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs index 7076e5144..cbb433012 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A one dimensional ICC curve. @@ -52,7 +51,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } return this.BreakPoints.AsSpan().SequenceEqual(other.BreakPoints) - && this.Segments.SequenceEqual(other.Segments); + && 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 index efc2ca537..04a9faf7d 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A parametric curve diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs index de08485ac..b9a50acd4 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs @@ -2,10 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A response curve @@ -65,10 +64,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override bool Equals(object obj) - { - return obj is IccResponseCurve other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccResponseCurve other && this.Equals(other); /// public override int GetHashCode() @@ -88,7 +84,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc for (int i = 0; i < this.ResponseArrays.Length; i++) { - if (!this.ResponseArrays[i].SequenceEqual(other.ResponseArrays[i])) + if (!this.ResponseArrays[i].AsSpan().SequenceEqual(other.ResponseArrays[i])) { return false; } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs index d9badf5a8..4872bd2b0 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A sampled curve segment diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs index b27083dc4..111911080 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs @@ -3,7 +3,7 @@ using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to read ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs index f9d56b0ff..5262e3ee9 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to read ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs index 495d73a78..3157b9ef3 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to read ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs index 2b39b1d6c..425305828 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to read ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs index 7dc8cf98a..aacbbcc1d 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to read ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs index bb85a5ca3..2cbf798ec 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs @@ -6,7 +6,7 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Text; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to read ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs index c572b7f21..3f330ef72 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -5,7 +5,7 @@ using System; using System.Globalization; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to read ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs index 91a28fd74..7d694bec6 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs @@ -4,7 +4,7 @@ using System; using System.Text; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to read ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs index d5f5d5a9b..c48ba8ab7 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs @@ -3,7 +3,7 @@ using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to write ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs index 38a3dd457..016bd8009 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to write ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs index 79b913219..b0bd377cb 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -4,7 +4,7 @@ using System.Numerics; using SixLabors.ImageSharp.Primitives; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to write ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs index 48c6734ae..824a9bef6 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to write ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs index 1a3c2c0ac..e681f84b8 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to write ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs index 404285b50..6c49eb0c4 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs @@ -4,7 +4,7 @@ using System; using System.Text; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to write ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs index 51ea2d4e4..f3c632a86 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs @@ -3,7 +3,7 @@ using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to write ICC data types @@ -163,10 +163,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// The entry to write /// The number of bytes written - public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value) - { - return this.WriteArray(value.Data); - } + public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value) => this.WriteArray(value.Data); /// /// Writes a @@ -269,10 +266,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// The entry to write /// The number of bytes written - public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value) - { - return this.WriteDateTime(value.Value); - } + public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value) => this.WriteDateTime(value.Value); /// /// Writes a @@ -563,6 +557,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc long tpos = this.dataStream.Position; this.dataStream.Position += cultureCount * 12; + // TODO: Investigate cost of Linq GroupBy IGrouping[] texts = value.Texts.GroupBy(t => t.Text).ToArray(); uint[] offset = new uint[texts.Length]; @@ -625,7 +620,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc long tpos = this.dataStream.Position; this.dataStream.Position += value.Data.Length * 8; - IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length]; + var posTable = new IccPositionNumber[value.Data.Length]; for (int i = 0; i < value.Data.Length; i++) { uint offset = (uint)(this.dataStream.Position - start); @@ -673,10 +668,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// The entry to write /// The number of bytes written - public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value) - { - return this.WriteParametricCurve(value.Curve); - } + public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value) => this.WriteParametricCurve(value.Curve); /// /// Writes a @@ -793,20 +785,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// The entry to write /// The number of bytes written - public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value) - { - return this.WriteAsciiString(value.SignatureData, 4, false); - } + 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) - { - return this.WriteAsciiString(value.Text); - } + public int WriteTextTagDataEntry(IccTextTagDataEntry value) => this.WriteAsciiString(value.Text); /// /// Writes a @@ -829,40 +815,28 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// The entry to write /// The number of bytes written - public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value) - { - return this.WriteArray(value.Data); - } + 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) - { - return this.WriteArray(value.Data); - } + 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) - { - return this.WriteArray(value.Data); - } + 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) - { - return this.WriteArray(value.Data); - } + public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value) => this.WriteArray(value.Data); /// /// Writes a diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs index 21b7b6421..17f15df71 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs @@ -4,7 +4,7 @@ using System; using System.IO; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Provides methods to write ICC data types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs index fe0c9b8b5..71e68f6af 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Color lookup table data type diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs index 0913cda41..721545df3 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Color Space Type diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs index bec71e0db..14b2dd960 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Colorant Encoding diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs index cfd2671e9..a620632da 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Curve Measurement Encodings diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs index e9b8cc26b..9a5ae1805 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Curve Segment Signature diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs index b13edb53f..de1f11636 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Enumerates the basic data types as defined in ICC.1:2010 version 4.3.0.0 diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs index 88cca866d..c8598b0e0 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Device attributes. Can be combined with a logical OR diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs index 2a375b0d1..11e5985af 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Formula curve segment type diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs index fe6575c41..937324194 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Measurement Geometry diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs index fe5d30922..d7f78889d 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Multi process element signature diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs index 374b4ad93..fce08a3af 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Formula curve segment type diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs index 1563078f7..035fd3d4d 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Enumerates the primary platform/operating system framework for which the profile was created diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs index 8f0427db2..3d59192a7 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Profile Class Name diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs index 1e9ec18e8..9fbe5b5b5 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Profile flags. Can be combined with a logical OR. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs index 61d34dca1..b19641e0f 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Enumerates the ICC Profile Tags as defined in ICC.1:2010 version 4.3.0.0 diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs index 7cb9c00f3..8ae241b44 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Rendering intent diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs index 839ab8c6b..b43ad52c4 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Screening flags. Can be combined with a logical OR. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs index f1d73c626..0631892b6 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Enumerates the screening spot types diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs index 4773ab6c2..f93d22f3e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Signature Name diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs index fe0c40657..fc8ebae5c 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Standard Illuminant diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs index a393c258b..1454b2827 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Standard Observer diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs index ad0db4df9..d7a18579e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Type Signature diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs index f69067041..019bf9202 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Represents an error that happened while reading or writing a corrupt/invalid ICC profile diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs index 5d75a6df9..afdd7ebfb 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -4,7 +4,7 @@ using System; using System.Security.Cryptography; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Represents an ICC profile diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs index 189b40275..326dd351e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Contains all values of an ICC profile header. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs index 9f9d373ae..30a6de40d 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Reads and parses ICC data from a byte array diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs index 4c6bb0785..658d4c2f1 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The data of an ICC tag entry diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs index 91a3bba54..186f06df0 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Contains methods for writing ICC profiles. @@ -70,6 +70,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc 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) diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs index 09931a1f9..c825a3535 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A placeholder (might be used for future ICC versions) diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs index 6aba18632..7ee7f821d 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A CLUT (color lookup table) element to process data diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs index 7585fc212..c6409e3c1 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A set of curves to process data @@ -17,9 +16,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// 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)); - } + => this.Curves = curves ?? throw new ArgumentNullException(nameof(curves)); /// /// Gets an array of one dimensional curves @@ -31,16 +28,13 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc { if (base.Equals(other) && other is IccCurveSetProcessElement element) { - return this.Curves.SequenceEqual(element.Curves); + return this.Curves.AsSpan().SequenceEqual(element.Curves); } return false; } /// - public bool Equals(IccCurveSetProcessElement other) - { - return this.Equals((IccMultiProcessElement)other); - } + 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 index 7e0e1eda8..d6f42aea7 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A placeholder (might be used for future ICC versions) diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs index e6170f768..0d8683397 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs @@ -5,7 +5,7 @@ using System; using SixLabors.ImageSharp.Primitives; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A matrix element to process data diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs index db2d56cc3..24649d8b5 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// An element to process data diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs index e9a812d8c..813271d48 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The chromaticity tag type provides basic chromaticity data @@ -80,10 +80,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc public double[][] ChannelValues { get; } /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccChromaticityTagDataEntry entry && this.Equals(entry); - } + public override bool Equals(IccTagDataEntry other) => other is IccChromaticityTagDataEntry entry && this.Equals(entry); /// public bool Equals(IccChromaticityTagDataEntry other) @@ -102,10 +99,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override bool Equals(object obj) - { - return obj is IccChromaticityTagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccChromaticityTagDataEntry other && this.Equals(other); /// public override int GetHashCode() @@ -162,7 +156,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc for (int i = 0; i < this.ChannelValues.Length; i++) { - if (!this.ChannelValues[i].SequenceEqual(entry.ChannelValues[i])) + if (!this.ChannelValues[i].AsSpan().SequenceEqual(entry.ChannelValues[i])) { return false; } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs index b5f8fd5c4..4136b9a38 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This tag specifies the laydown order in which colorants diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs index 9f2b4c90a..aff33ff82 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The purpose of this tag is to identify the colorants used in @@ -42,10 +41,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc public IccColorantTableEntry[] ColorantData { get; } /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccColorantTableTagDataEntry entry && this.Equals(entry); - } + public override bool Equals(IccTagDataEntry other) => other is IccColorantTableTagDataEntry entry && this.Equals(entry); /// public bool Equals(IccColorantTableTagDataEntry other) @@ -60,19 +56,13 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return true; } - return base.Equals(other) && this.ColorantData.SequenceEqual(other.ColorantData); + return base.Equals(other) && this.ColorantData.AsSpan().SequenceEqual(other.ColorantData); } /// - public override bool Equals(object obj) - { - return obj is IccColorantTableTagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccColorantTableTagDataEntry other && this.Equals(other); /// - public override int GetHashCode() - { - return HashCode.Combine(this.Signature, this.ColorantData); - } + 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 index 4d393dfb3..bcf9d5c9e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type contains the PostScript product name to which this profile diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs index 0d34d3ceb..f2205cbad 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The type contains a one-dimensional table of double values. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs index 0b8367303..6a0b6c05e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Text; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The dataType is a simple data structure that contains diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs index 104d24309..371397a6f 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type is a representation of the time and date. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs index 0a114ea1f..50bfca175 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type represents an array of doubles (from 32bit fixed point values). diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs index dd180b299..8f7db49ad 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs @@ -2,10 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This structure represents a color transform using tables @@ -13,7 +12,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// internal sealed class IccLut16TagDataEntry : IccTagDataEntry, IEquatable { - private static readonly float[,] IdentityMatrix = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + private static readonly float[,] IdentityMatrix = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; /// /// Initializes a new instance of the class. @@ -66,7 +70,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc 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.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)); @@ -106,10 +110,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc public IccLut[] OutputValues { get; } /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccLut16TagDataEntry entry && this.Equals(entry); - } + public override bool Equals(IccTagDataEntry other) => other is IccLut16TagDataEntry entry && this.Equals(entry); /// public bool Equals(IccLut16TagDataEntry other) @@ -126,16 +127,13 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return base.Equals(other) && this.Matrix.Equals(other.Matrix) - && this.InputValues.SequenceEqual(other.InputValues) + && this.InputValues.AsSpan().SequenceEqual(other.InputValues) && this.ClutValues.Equals(other.ClutValues) - && this.OutputValues.SequenceEqual(other.OutputValues); + && this.OutputValues.AsSpan().SequenceEqual(other.OutputValues); } /// - public override bool Equals(object obj) - { - return obj is IccLut16TagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccLut16TagDataEntry other && this.Equals(other); /// public override int GetHashCode() @@ -148,7 +146,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc this.OutputValues); } - private Matrix4x4 CreateMatrix(float[,] matrix) + private static Matrix4x4 CreateMatrix(float[,] matrix) { return new Matrix4x4( matrix[0, 0], diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs index b345d14a5..3a1859a1c 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs @@ -5,7 +5,7 @@ using System; using System.Linq; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This structure represents a color transform using tables @@ -13,7 +13,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// internal sealed class IccLut8TagDataEntry : IccTagDataEntry, IEquatable { - private static readonly float[,] IdentityMatrix = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + private static readonly float[,] IdentityMatrix = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; /// /// Initializes a new instance of the class. @@ -109,10 +114,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc public IccLut[] OutputValues { get; } /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccLut8TagDataEntry entry && this.Equals(entry); - } + public override bool Equals(IccTagDataEntry other) => other is IccLut8TagDataEntry entry && this.Equals(entry); /// public bool Equals(IccLut8TagDataEntry other) @@ -129,16 +131,13 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return base.Equals(other) && this.Matrix.Equals(other.Matrix) - && this.InputValues.SequenceEqual(other.InputValues) + && this.InputValues.AsSpan().SequenceEqual(other.InputValues) && this.ClutValues.Equals(other.ClutValues) - && this.OutputValues.SequenceEqual(other.OutputValues); + && this.OutputValues.AsSpan().SequenceEqual(other.OutputValues); } /// - public override bool Equals(object obj) - { - return obj is IccLut8TagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccLut8TagDataEntry other && this.Equals(other); /// public override int GetHashCode() diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs index f7c094632..88e3c4cae 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -5,7 +5,8 @@ using System; using System.Linq; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +// TODO: Review the use of base IccTagDataEntry comparison. +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This structure represents a color transform. @@ -15,12 +16,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Initializes a new instance of the class. /// - /// A Curve - /// CLUT - /// M Curve + /// B Curve /// Two dimensional conversion matrix (3x3) /// One dimensional conversion matrix (3x1) - /// B Curve + /// M Curve + /// CLUT + /// A Curve public IccLutAToBTagDataEntry( IccTagDataEntry[] curveB, float[,] matrix3x3, @@ -35,12 +36,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Initializes a new instance of the class. /// - /// A Curve - /// CLUT - /// M Curve + /// B Curve /// Two dimensional conversion matrix (3x3) /// One dimensional conversion matrix (3x1) - /// B Curve + /// M Curve + /// CLUT + /// A Curve /// Tag Signature public IccLutAToBTagDataEntry( IccTagDataEntry[] curveB, @@ -145,10 +146,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc public IccTagDataEntry[] CurveA { get; } /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccLutAToBTagDataEntry entry && this.Equals(entry); - } + public override bool Equals(IccTagDataEntry other) => other is IccLutAToBTagDataEntry entry && this.Equals(entry); /// public bool Equals(IccLutAToBTagDataEntry other) @@ -169,23 +167,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc && 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); + && EqualsCurve(this.CurveB, other.CurveB) + && EqualsCurve(this.CurveM, other.CurveM) + && EqualsCurve(this.CurveA, other.CurveA); } /// - public override bool Equals(object obj) - { - return obj is IccLutAToBTagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => 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 hashCode = default; hashCode.Add(this.Signature); hashCode.Add(this.InputChannelCount); @@ -200,7 +193,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return hashCode.ToHashCode(); } - private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) { bool thisNull = thisCurves is null; bool entryNull = entryCurves is null; @@ -243,10 +236,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc && this.CurveA != null; } - private bool IsB() - { - return this.CurveB != null; - } + private bool IsB() => this.CurveB != null; private void VerifyCurve(IccTagDataEntry[] curves, string name) { diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs index 27572acd0..f8bf3f042 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -5,7 +5,8 @@ using System; using System.Linq; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +// TODO: Review the use of base IccTagDataEntry comparison. +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This structure represents a color transform. @@ -15,12 +16,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Initializes a new instance of the class. /// - /// A Curve - /// CLUT - /// M Curve + /// B Curve /// Two dimensional conversion matrix (3x3) /// One dimensional conversion matrix (3x1) - /// B Curve + /// M Curve + /// CLUT + /// A Curve public IccLutBToATagDataEntry( IccTagDataEntry[] curveB, float[,] matrix3x3, @@ -35,12 +36,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Initializes a new instance of the class. /// - /// A Curve - /// CLUT - /// M Curve + /// B Curve /// Two dimensional conversion matrix (3x3) /// One dimensional conversion matrix (3x1) - /// B Curve + /// M Curve + /// CLUT + /// A Curve /// Tag Signature public IccLutBToATagDataEntry( IccTagDataEntry[] curveB, @@ -145,10 +146,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc public IccTagDataEntry[] CurveA { get; } /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccLutBToATagDataEntry entry && this.Equals(entry); - } + public override bool Equals(IccTagDataEntry other) => other is IccLutBToATagDataEntry entry && this.Equals(entry); /// public bool Equals(IccLutBToATagDataEntry other) @@ -175,18 +173,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override bool Equals(object obj) - { - return obj is IccLutBToATagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => 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 hashCode = default; hashCode.Add(this.Signature); hashCode.Add(this.InputChannelCount); hashCode.Add(this.OutputChannelCount); @@ -243,10 +235,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc && this.CurveA != null; } - private bool IsB() - { - return this.CurveB != null; - } + private bool IsB() => this.CurveB != null; private void VerifyCurve(IccTagDataEntry[] curves, string name) { diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs index 9247ca593..ad3a9f2c3 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The measurementType information refers only to the internal diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs index 1a06eb588..34a027126 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This tag structure contains a set of records each referencing @@ -56,7 +55,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return true; } - return base.Equals(other) && this.Texts.SequenceEqual(other.Texts); + return base.Equals(other) && this.Texts.AsSpan().SequenceEqual(other.Texts); } /// diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs index f13fdb17f..f2713e8ce 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This structure represents a color transform, containing @@ -57,10 +57,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// public override bool Equals(IccTagDataEntry other) - { - var entry = other as IccMultiProcessElementsTagDataEntry; - return entry != null && this.Equals(entry); - } + => other is IccMultiProcessElementsTagDataEntry entry && this.Equals(entry); /// public bool Equals(IccMultiProcessElementsTagDataEntry other) @@ -78,14 +75,11 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return base.Equals(other) && this.InputChannelCount == other.InputChannelCount && this.OutputChannelCount == other.OutputChannelCount - && this.Data.SequenceEqual(other.Data); + && this.Data.AsSpan().SequenceEqual(other.Data); } /// - public override bool Equals(object obj) - { - return obj is IccMultiProcessElementsTagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccMultiProcessElementsTagDataEntry other && this.Equals(other); /// public override int GetHashCode() diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs index 957b8d5c3..4fd0cfcfb 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The namedColor2Type is a count value and array of structures @@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc && string.Equals(this.Prefix, other.Prefix) && string.Equals(this.Suffix, other.Suffix) && this.VendorFlags == other.VendorFlags - && this.Colors.SequenceEqual(other.Colors); + && this.Colors.AsSpan().SequenceEqual(other.Colors); } /// diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs index 9ec497d3b..752d05aae 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The parametricCurveType describes a one-dimensional curve by diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs index ff6911526..7897c394f 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type is an array of structures, each of which contains information @@ -29,9 +28,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// Tag Signature public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions, IccProfileTag tagSignature) : base(IccTypeSignature.ProfileSequenceDesc, tagSignature) - { - this.Descriptions = descriptions ?? throw new ArgumentNullException(nameof(descriptions)); - } + => this.Descriptions = descriptions ?? throw new ArgumentNullException(nameof(descriptions)); /// /// Gets the profile descriptions @@ -40,9 +37,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// public override bool Equals(IccTagDataEntry other) - { - return other is IccProfileSequenceDescTagDataEntry entry && this.Equals(entry); - } + => other is IccProfileSequenceDescTagDataEntry entry && this.Equals(entry); /// public bool Equals(IccProfileSequenceDescTagDataEntry other) @@ -57,14 +52,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return true; } - return base.Equals(other) && this.Descriptions.SequenceEqual(other.Descriptions); + return base.Equals(other) && this.Descriptions.AsSpan().SequenceEqual(other.Descriptions); } /// public override bool Equals(object obj) - { - return obj is IccProfileSequenceDescTagDataEntry other && this.Equals(other); - } + => 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 index c6cc0903c..06e52cb63 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type is an array of structures, each of which contains information diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs index 494aa2888..e9c8af9be 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The purpose of this tag type is to provide a mechanism to relate physical @@ -51,10 +51,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc public IccResponseCurve[] Curves { get; } /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccResponseCurveSet16TagDataEntry entry && this.Equals(entry); - } + public override bool Equals(IccTagDataEntry other) => other is IccResponseCurveSet16TagDataEntry entry && this.Equals(entry); /// public bool Equals(IccResponseCurveSet16TagDataEntry other) @@ -71,14 +68,11 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return base.Equals(other) && this.ChannelCount == other.ChannelCount - && this.Curves.SequenceEqual(other.Curves); + && this.Curves.AsSpan().SequenceEqual(other.Curves); } /// - public override bool Equals(object obj) - { - return obj is IccResponseCurveSet16TagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccResponseCurveSet16TagDataEntry other && this.Equals(other); /// public override int GetHashCode() diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs index a073291ad..bf64e4fed 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type describes various screening parameters including diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs index 287f0efb0..87d369f85 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Typically this type is used for registered tags that can diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs index 8e6f4bc19..4134779c3 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The TextDescriptionType contains three types of text description. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs index ab3b3fb6f..6cf9e9154 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This is a simple text structure that contains a text string. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs index 464cbb9e7..3e4a5ff3a 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type represents an array of doubles (from 32bit values). diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs index 1e7a7f8a9..46799b16a 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type represents an array of unsigned shorts. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs index affdc8720..9fbbf8bf5 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type represents an array of unsigned 32bit integers. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs index 36d48d613..09bec9b7a 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type represents an array of unsigned 64bit integers. @@ -26,10 +25,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// The array data /// Tag Signature public IccUInt64ArrayTagDataEntry(ulong[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt64Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } + : base(IccTypeSignature.UInt64Array, tagSignature) => this.Data = data ?? throw new ArgumentNullException(nameof(data)); /// /// Gets the array data @@ -37,10 +33,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc public ulong[] Data { get; } /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccUInt64ArrayTagDataEntry entry && this.Equals(entry); - } + public override bool Equals(IccTagDataEntry other) => other is IccUInt64ArrayTagDataEntry entry && this.Equals(entry); /// public bool Equals(IccUInt64ArrayTagDataEntry other) @@ -59,10 +52,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override bool Equals(object obj) - { - return obj is IccUInt64ArrayTagDataEntry other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccUInt64ArrayTagDataEntry other && this.Equals(other); /// public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs index b2f267757..099916d89 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type represents an array of bytes. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs index 510930e39..d28b6edc9 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type contains curves representing the under color removal and black generation diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs index a0089e735..0227983fa 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This tag stores data of an unknown tag data entry diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs index bd636d4f2..2b2893c38 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// This type represents a set of viewing condition parameters. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs index c1c14d8cb..505b73a89 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// The XYZType contains an array of XYZ values. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs index 3f7cad3af..bd8f784ae 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs @@ -4,7 +4,7 @@ using System; using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Color Lookup Table @@ -134,10 +134,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override bool Equals(object obj) - { - return obj is IccClut other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccClut other && this.Equals(other); /// public override int GetHashCode() diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs index 8f273dd60..db1feea9a 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Entry of ICC colorant table @@ -35,22 +35,22 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - /// Gets the colorant name + /// Gets the colorant name. /// public string Name { get; } /// - /// Gets the first PCS value + /// Gets the first PCS value. /// public ushort Pcs1 { get; } /// - /// Gets the second PCS value + /// Gets the second PCS value. /// public ushort Pcs2 { get; } /// - /// Gets the third PCS value + /// Gets the third PCS value. /// public ushort Pcs3 { get; } @@ -110,9 +110,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override string ToString() - { - return $"{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 index 00ededca4..b2852c8a3 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A string with a specific locale. @@ -49,9 +49,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc this.Text == other.Text; /// - public override string ToString() - { - return $"{this.Culture.Name}: {this.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 index c46d6884b..9e0c6c40d 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Lookup Table diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs index b7cb5bc49..3c640ab03 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A specific color with a name @@ -55,10 +54,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// True if the parameter is equal to the parameter; otherwise, false. /// - public static bool operator ==(IccNamedColor left, IccNamedColor right) - { - return left.Equals(right); - } + public static bool operator ==(IccNamedColor left, IccNamedColor right) => left.Equals(right); /// /// Compares two objects for equality. @@ -68,23 +64,17 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// 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 static bool operator !=(IccNamedColor left, IccNamedColor right) => !left.Equals(right); /// - public override bool Equals(object obj) - { - return obj is IccNamedColor other && this.Equals(other); - } + 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.SequenceEqual(other.PcsCoordinates) - && this.DeviceCoordinates.SequenceEqual(other.DeviceCoordinates); + && this.PcsCoordinates.AsSpan().SequenceEqual(other.PcsCoordinates) + && this.DeviceCoordinates.AsSpan().SequenceEqual(other.DeviceCoordinates); } /// diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs index 745312f56..c9b75903d 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Position of an object within an ICC profile diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs index 7e7c527ea..d630f015e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// ICC Profile description @@ -68,18 +67,15 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// 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); + 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) - { - return obj is IccProfileDescription other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccProfileDescription other && this.Equals(other); /// public override int GetHashCode() diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs index f64d5409a..4a73f7e07 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// ICC Profile ID @@ -31,27 +31,27 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - /// Gets the first part of the ID + /// Gets the first part of the ID. /// public uint Part1 { get; } /// - /// Gets the second part of the ID + /// Gets the second part of the ID. /// public uint Part2 { get; } /// - /// Gets the third part of the ID + /// Gets the third part of the ID. /// public uint Part3 { get; } /// - /// Gets the fourth part of the ID + /// 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 + /// Gets a value indicating whether the ID is set or just consists of zeros. /// public bool IsSet => !this.Equals(Zero); @@ -86,10 +86,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override bool Equals(object obj) - { - return obj is IccProfileId other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccProfileId other && this.Equals(other); /// public bool Equals(IccProfileId other) => @@ -109,14 +106,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override string ToString() - { - return $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}"; - } + public override string ToString() => $"{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'); - } + 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 index ae451c5db..6bf420b49 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Description of a profile within a sequence. @@ -34,14 +33,11 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// public bool Equals(IccProfileSequenceIdentifier other) => - this.Id.Equals(other.Id) && - this.Description.SequenceEqual(other.Description); + this.Id.Equals(other.Id) + && this.Description.AsSpan().SequenceEqual(other.Description); /// - public override bool Equals(object obj) - { - return obj is IccProfileSequenceIdentifier other && this.Equals(other); - } + 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 index 8cae86992..859080263 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Associates a normalized device code with a measurement value diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs index e8885a66b..354442b47 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// A single channel of a @@ -26,12 +26,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - /// Gets the screen frequency + /// Gets the screen frequency. /// public float Frequency { get; } /// - /// Gets the angle in degrees + /// Gets the angle in degrees. /// public float Angle { get; } @@ -89,9 +89,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override string ToString() - { - return $"{this.Frequency}Hz; {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 index d93e068c5..889dec41e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Entry of ICC tag table @@ -24,17 +24,17 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - /// Gets the signature of the tag + /// Gets the signature of the tag. /// public IccProfileTag Signature { get; } /// - /// Gets the offset of entry in bytes + /// Gets the offset of entry in bytes. /// public uint Offset { get; } /// - /// Gets the size of entry in bytes + /// Gets the size of entry in bytes. /// public uint DataSize { get; } @@ -69,10 +69,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } /// - public override bool Equals(object obj) - { - return obj is IccTagTableEntry other && this.Equals(other); - } + public override bool Equals(object obj) => obj is IccTagTableEntry other && this.Equals(other); /// public bool Equals(IccTagTableEntry other) => @@ -81,15 +78,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc this.DataSize.Equals(other.DataSize); /// - public override int GetHashCode() - { - return HashCode.Combine(this.Signature, this.Offset, this.DataSize); - } + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Offset, this.DataSize); /// - public override string ToString() - { - return $"{this.Signature} (Offset: {this.Offset}; Size: {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 index 2486cc80a..b388fa5a4 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccVersion.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccVersion.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.MetaData.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { /// /// Represents the ICC profile version number. diff --git a/src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs b/src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs deleted file mode 100644 index 2572b3293..000000000 --- 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/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 127740686..21ec2a3fd 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -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. /// diff --git a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs b/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs deleted file mode 100644 index 7e093de04..000000000 --- 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/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 5c8e506ae..d2a6fbf50 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -112,21 +112,15 @@ 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); } } @@ -161,21 +155,15 @@ 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); } } } diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs new file mode 100644 index 000000000..5df5dc62b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// 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 effitiently applied in + /// + /// or + /// + /// knowing the pixel type. + /// + [Flags] + internal 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, + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs new file mode 100644 index 000000000..529041481 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Extension and utility methods for . + /// + internal static class PixelConversionModifiersExtensions + { + [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/Alpha8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs index 75b7ede82..cd864b545 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs @@ -87,6 +87,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.PackedValue = source.A; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromGray8(Gray8 source) => this.PackedValue = byte.MaxValue; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index 8fc301631..8981c8745 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -142,6 +142,22 @@ namespace SixLabors.ImageSharp.PixelFormats 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. /// @@ -187,6 +203,10 @@ namespace SixLabors.ImageSharp.PixelFormats [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) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 96ff7da6f..a0b059dfc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -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. /// @@ -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) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index a2e4dc880..9e42de388 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -87,6 +87,10 @@ namespace SixLabors.ImageSharp.PixelFormats [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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index 1d156222f..ea7a96188 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -98,6 +98,22 @@ namespace SixLabors.ImageSharp.PixelFormats 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. /// @@ -159,6 +175,10 @@ 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; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 110b51822..6fcac6291 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index dcfb25a64..abb3eb19e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index 43a03dc5d..ab8a13c16 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -111,6 +111,10 @@ namespace SixLabors.ImageSharp.PixelFormats [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()); @@ -145,7 +149,7 @@ namespace SixLabors.ImageSharp.PixelFormats public override 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.##})"); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs index 2ec965dfb..8481fc62c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -42,29 +43,16 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); - } - - /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); - } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); } - /// internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { @@ -235,6 +223,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromArgb32(sp); } } + /// + internal 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 0a58504e1..3a4c7b459 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 711a9d1c1..4b2b3a02e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -42,30 +43,17 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); - } - - /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); - } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } - /// internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { @@ -209,6 +197,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromBgr24(sp); } } + /// + internal 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 84b89aa32..cfaefeda9 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 b669dd534..95d85c7c8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -42,29 +43,16 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); - } - - /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); - } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); } - /// internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { @@ -235,6 +223,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromBgra32(sp); } } + /// + internal 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 004ceff51..58ecfa5a6 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/Gray16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs index 288c5d92e..e6ed75d49 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromGray16(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToGray16(configuration, sourcePixels, destinationPixels); + } + } } -} \ 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 index 3cbc01e88..ac484bdbc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# 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 index f7e94788e..c9a3a06fe 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromGray8(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToGray8(configuration, sourcePixels, destinationPixels); + } + } } -} \ 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 index d35843ccd..ffa15af9d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Gray8"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs index dbf3102c4..ef224c02d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -42,30 +43,17 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); - } - - /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, false); - } - - /// - internal override void FromScaledVector4(Configuration configuration, ReadOnlySpan sourceVectors, Span destPixels) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, true); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToScaledVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors) + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, true); + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } - /// internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { @@ -209,6 +197,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgb24(sp); } } + /// + internal 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 d96c3684b..fc149b238 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 30c9972bb..694388899 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgb48(sp); } } + /// + internal 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 7bff33638..957c8f886 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 da2ce3770..f0c8bc884 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -211,6 +212,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgba32(sp); } } + /// + internal 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 6b9e2d124..905e2cc58 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 42c40ad5d..0faa6eb44 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgba64(sp); } } + /// + internal 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 d15945f94..03179a392 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 f0675cb5b..17e9469f3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude @@ -22,6 +22,20 @@ using System.Runtime.InteropServices; // Types with Rgba32-combatible to/from Vector4 conversion static readonly string[] Rgba32CompatibleTypes = { "Argb32", "Bgra32", "Rgb24", "Bgr24" }; + void GenerateGenericConverterMethods(string pixelType) + { +#> + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span<<#=pixelType#>> destinationPixels) + { + PixelOperations.Instance.To<#=pixelType#>(configuration, sourcePixels, destinationPixels); + } +<#+ + } + void GenerateDefaultSelfConversionMethods(string pixelType) { #> @@ -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) + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span<<#=pixelType#>> destPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, false); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(<#=removeTheseModifiers#>)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors) + internal 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 index 2e98a28ad..195a8bedb 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -62,11 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [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); - } + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -106,6 +102,10 @@ namespace SixLabors.ImageSharp.PixelFormats ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromGray8(Gray8 source) => this.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); @@ -172,9 +172,9 @@ namespace SixLabors.ImageSharp.PixelFormats { 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)); + vector.X, + vector.Y, + vector.Z); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs index 512bee39d..09597a949 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,15 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [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; - } + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -97,6 +89,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromGray8(Gray8 source) => this.PackedValue = source.PackedValue; @@ -162,4 +158,4 @@ namespace SixLabors.ImageSharp.PixelFormats 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 8323cf3e8..580cc5399 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -88,6 +88,10 @@ 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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index cb915459b..e2ed931b7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -99,6 +99,10 @@ 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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 9f60ca8c7..3b30ebd1e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -107,6 +107,10 @@ 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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index d39cfd402..cd95e87ec 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -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. /// @@ -107,10 +107,11 @@ 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)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index 82698d508..73a3d3262 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -110,6 +110,10 @@ 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()); @@ -164,10 +168,10 @@ namespace SixLabors.ImageSharp.PixelFormats { vector = Vector4.Clamp(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; } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index b9cab1e7d..8d7c400b4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -105,6 +105,10 @@ 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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index 3bc74e6c6..453b1c1b7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -112,6 +112,10 @@ 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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index 6dc623518..0411f8f3e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -93,6 +93,10 @@ 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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 86565731d..469dbbad4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -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 /// . @@ -156,6 +172,10 @@ namespace SixLabors.ImageSharp.PixelFormats 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; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index eda116a46..f59036ec7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -126,6 +126,10 @@ 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) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index 895added1..38f61d56c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -99,6 +99,10 @@ 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()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs index 032d0b546..f9cc3256c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs @@ -11,711 +11,711 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// - public static readonly Rgba32 AliceBlue = NamedColors.AliceBlue; + public static readonly Rgba32 AliceBlue = Color.AliceBlue; /// /// Represents a matching the W3C definition that has an hex value of #FAEBD7. /// - public static readonly Rgba32 AntiqueWhite = NamedColors.AntiqueWhite; + public static readonly Rgba32 AntiqueWhite = Color.AntiqueWhite; /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Rgba32 Aqua = NamedColors.Aqua; + public static readonly Rgba32 Aqua = Color.Aqua; /// /// Represents a matching the W3C definition that has an hex value of #7FFFD4. /// - public static readonly Rgba32 Aquamarine = NamedColors.Aquamarine; + public static readonly Rgba32 Aquamarine = Color.Aquamarine; /// /// Represents a matching the W3C definition that has an hex value of #F0FFFF. /// - public static readonly Rgba32 Azure = NamedColors.Azure; + public static readonly Rgba32 Azure = Color.Azure; /// /// Represents a matching the W3C definition that has an hex value of #F5F5DC. /// - public static readonly Rgba32 Beige = NamedColors.Beige; + public static readonly Rgba32 Beige = Color.Beige; /// /// Represents a matching the W3C definition that has an hex value of #FFE4C4. /// - public static readonly Rgba32 Bisque = NamedColors.Bisque; + public static readonly Rgba32 Bisque = Color.Bisque; /// /// Represents a matching the W3C definition that has an hex value of #000000. /// - public static readonly Rgba32 Black = NamedColors.Black; + public static readonly Rgba32 Black = Color.Black; /// /// Represents a matching the W3C definition that has an hex value of #FFEBCD. /// - public static readonly Rgba32 BlanchedAlmond = NamedColors.BlanchedAlmond; + public static readonly Rgba32 BlanchedAlmond = Color.BlanchedAlmond; /// /// Represents a matching the W3C definition that has an hex value of #0000FF. /// - public static readonly Rgba32 Blue = NamedColors.Blue; + public static readonly Rgba32 Blue = Color.Blue; /// /// Represents a matching the W3C definition that has an hex value of #8A2BE2. /// - public static readonly Rgba32 BlueViolet = NamedColors.BlueViolet; + public static readonly Rgba32 BlueViolet = Color.BlueViolet; /// /// Represents a matching the W3C definition that has an hex value of #A52A2A. /// - public static readonly Rgba32 Brown = NamedColors.Brown; + public static readonly Rgba32 Brown = Color.Brown; /// /// Represents a matching the W3C definition that has an hex value of #DEB887. /// - public static readonly Rgba32 BurlyWood = NamedColors.BurlyWood; + public static readonly Rgba32 BurlyWood = Color.BurlyWood; /// /// Represents a matching the W3C definition that has an hex value of #5F9EA0. /// - public static readonly Rgba32 CadetBlue = NamedColors.CadetBlue; + public static readonly Rgba32 CadetBlue = Color.CadetBlue; /// /// Represents a matching the W3C definition that has an hex value of #7FFF00. /// - public static readonly Rgba32 Chartreuse = NamedColors.Chartreuse; + public static readonly Rgba32 Chartreuse = Color.Chartreuse; /// /// Represents a matching the W3C definition that has an hex value of #D2691E. /// - public static readonly Rgba32 Chocolate = NamedColors.Chocolate; + public static readonly Rgba32 Chocolate = Color.Chocolate; /// /// Represents a matching the W3C definition that has an hex value of #FF7F50. /// - public static readonly Rgba32 Coral = NamedColors.Coral; + public static readonly Rgba32 Coral = Color.Coral; /// /// Represents a matching the W3C definition that has an hex value of #6495ED. /// - public static readonly Rgba32 CornflowerBlue = NamedColors.CornflowerBlue; + public static readonly Rgba32 CornflowerBlue = Color.CornflowerBlue; /// /// Represents a matching the W3C definition that has an hex value of #FFF8DC. /// - public static readonly Rgba32 Cornsilk = NamedColors.Cornsilk; + public static readonly Rgba32 Cornsilk = Color.Cornsilk; /// /// Represents a matching the W3C definition that has an hex value of #DC143C. /// - public static readonly Rgba32 Crimson = NamedColors.Crimson; + public static readonly Rgba32 Crimson = Color.Crimson; /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Rgba32 Cyan = NamedColors.Cyan; + public static readonly Rgba32 Cyan = Color.Cyan; /// /// Represents a matching the W3C definition that has an hex value of #00008B. /// - public static readonly Rgba32 DarkBlue = NamedColors.DarkBlue; + public static readonly Rgba32 DarkBlue = Color.DarkBlue; /// /// Represents a matching the W3C definition that has an hex value of #008B8B. /// - public static readonly Rgba32 DarkCyan = NamedColors.DarkCyan; + public static readonly Rgba32 DarkCyan = Color.DarkCyan; /// /// Represents a matching the W3C definition that has an hex value of #B8860B. /// - public static readonly Rgba32 DarkGoldenrod = NamedColors.DarkGoldenrod; + public static readonly Rgba32 DarkGoldenrod = Color.DarkGoldenrod; /// /// Represents a matching the W3C definition that has an hex value of #A9A9A9. /// - public static readonly Rgba32 DarkGray = NamedColors.DarkGray; + public static readonly Rgba32 DarkGray = Color.DarkGray; /// /// Represents a matching the W3C definition that has an hex value of #006400. /// - public static readonly Rgba32 DarkGreen = NamedColors.DarkGreen; + public static readonly Rgba32 DarkGreen = Color.DarkGreen; /// /// Represents a matching the W3C definition that has an hex value of #BDB76B. /// - public static readonly Rgba32 DarkKhaki = NamedColors.DarkKhaki; + public static readonly Rgba32 DarkKhaki = Color.DarkKhaki; /// /// Represents a matching the W3C definition that has an hex value of #8B008B. /// - public static readonly Rgba32 DarkMagenta = NamedColors.DarkMagenta; + public static readonly Rgba32 DarkMagenta = Color.DarkMagenta; /// /// Represents a matching the W3C definition that has an hex value of #556B2F. /// - public static readonly Rgba32 DarkOliveGreen = NamedColors.DarkOliveGreen; + public static readonly Rgba32 DarkOliveGreen = Color.DarkOliveGreen; /// /// Represents a matching the W3C definition that has an hex value of #FF8C00. /// - public static readonly Rgba32 DarkOrange = NamedColors.DarkOrange; + public static readonly Rgba32 DarkOrange = Color.DarkOrange; /// /// Represents a matching the W3C definition that has an hex value of #9932CC. /// - public static readonly Rgba32 DarkOrchid = NamedColors.DarkOrchid; + public static readonly Rgba32 DarkOrchid = Color.DarkOrchid; /// /// Represents a matching the W3C definition that has an hex value of #8B0000. /// - public static readonly Rgba32 DarkRed = NamedColors.DarkRed; + public static readonly Rgba32 DarkRed = Color.DarkRed; /// /// Represents a matching the W3C definition that has an hex value of #E9967A. /// - public static readonly Rgba32 DarkSalmon = NamedColors.DarkSalmon; + public static readonly Rgba32 DarkSalmon = Color.DarkSalmon; /// /// Represents a matching the W3C definition that has an hex value of #8FBC8B. /// - public static readonly Rgba32 DarkSeaGreen = NamedColors.DarkSeaGreen; + public static readonly Rgba32 DarkSeaGreen = Color.DarkSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #483D8B. /// - public static readonly Rgba32 DarkSlateBlue = NamedColors.DarkSlateBlue; + public static readonly Rgba32 DarkSlateBlue = Color.DarkSlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #2F4F4F. /// - public static readonly Rgba32 DarkSlateGray = NamedColors.DarkSlateGray; + public static readonly Rgba32 DarkSlateGray = Color.DarkSlateGray; /// /// Represents a matching the W3C definition that has an hex value of #00CED1. /// - public static readonly Rgba32 DarkTurquoise = NamedColors.DarkTurquoise; + public static readonly Rgba32 DarkTurquoise = Color.DarkTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #9400D3. /// - public static readonly Rgba32 DarkViolet = NamedColors.DarkViolet; + public static readonly Rgba32 DarkViolet = Color.DarkViolet; /// /// Represents a matching the W3C definition that has an hex value of #FF1493. /// - public static readonly Rgba32 DeepPink = NamedColors.DeepPink; + public static readonly Rgba32 DeepPink = Color.DeepPink; /// /// Represents a matching the W3C definition that has an hex value of #00BFFF. /// - public static readonly Rgba32 DeepSkyBlue = NamedColors.DeepSkyBlue; + public static readonly Rgba32 DeepSkyBlue = Color.DeepSkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #696969. /// - public static readonly Rgba32 DimGray = NamedColors.DimGray; + public static readonly Rgba32 DimGray = Color.DimGray; /// /// Represents a matching the W3C definition that has an hex value of #1E90FF. /// - public static readonly Rgba32 DodgerBlue = NamedColors.DodgerBlue; + public static readonly Rgba32 DodgerBlue = Color.DodgerBlue; /// /// Represents a matching the W3C definition that has an hex value of #B22222. /// - public static readonly Rgba32 Firebrick = NamedColors.Firebrick; + public static readonly Rgba32 Firebrick = Color.Firebrick; /// /// Represents a matching the W3C definition that has an hex value of #FFFAF0. /// - public static readonly Rgba32 FloralWhite = NamedColors.FloralWhite; + public static readonly Rgba32 FloralWhite = Color.FloralWhite; /// /// Represents a matching the W3C definition that has an hex value of #228B22. /// - public static readonly Rgba32 ForestGreen = NamedColors.ForestGreen; + public static readonly Rgba32 ForestGreen = Color.ForestGreen; /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Rgba32 Fuchsia = NamedColors.Fuchsia; + public static readonly Rgba32 Fuchsia = Color.Fuchsia; /// /// Represents a matching the W3C definition that has an hex value of #DCDCDC. /// - public static readonly Rgba32 Gainsboro = NamedColors.Gainsboro; + public static readonly Rgba32 Gainsboro = Color.Gainsboro; /// /// Represents a matching the W3C definition that has an hex value of #F8F8FF. /// - public static readonly Rgba32 GhostWhite = NamedColors.GhostWhite; + public static readonly Rgba32 GhostWhite = Color.GhostWhite; /// /// Represents a matching the W3C definition that has an hex value of #FFD700. /// - public static readonly Rgba32 Gold = NamedColors.Gold; + public static readonly Rgba32 Gold = Color.Gold; /// /// Represents a matching the W3C definition that has an hex value of #DAA520. /// - public static readonly Rgba32 Goldenrod = NamedColors.Goldenrod; + public static readonly Rgba32 Goldenrod = Color.Goldenrod; /// /// Represents a matching the W3C definition that has an hex value of #808080. /// - public static readonly Rgba32 Gray = NamedColors.Gray; + public static readonly Rgba32 Gray = Color.Gray; /// /// Represents a matching the W3C definition that has an hex value of #008000. /// - public static readonly Rgba32 Green = NamedColors.Green; + public static readonly Rgba32 Green = Color.Green; /// /// Represents a matching the W3C definition that has an hex value of #ADFF2F. /// - public static readonly Rgba32 GreenYellow = NamedColors.GreenYellow; + public static readonly Rgba32 GreenYellow = Color.GreenYellow; /// /// Represents a matching the W3C definition that has an hex value of #F0FFF0. /// - public static readonly Rgba32 Honeydew = NamedColors.Honeydew; + public static readonly Rgba32 Honeydew = Color.Honeydew; /// /// Represents a matching the W3C definition that has an hex value of #FF69B4. /// - public static readonly Rgba32 HotPink = NamedColors.HotPink; + public static readonly Rgba32 HotPink = Color.HotPink; /// /// Represents a matching the W3C definition that has an hex value of #CD5C5C. /// - public static readonly Rgba32 IndianRed = NamedColors.IndianRed; + public static readonly Rgba32 IndianRed = Color.IndianRed; /// /// Represents a matching the W3C definition that has an hex value of #4B0082. /// - public static readonly Rgba32 Indigo = NamedColors.Indigo; + public static readonly Rgba32 Indigo = Color.Indigo; /// /// Represents a matching the W3C definition that has an hex value of #FFFFF0. /// - public static readonly Rgba32 Ivory = NamedColors.Ivory; + public static readonly Rgba32 Ivory = Color.Ivory; /// /// Represents a matching the W3C definition that has an hex value of #F0E68C. /// - public static readonly Rgba32 Khaki = NamedColors.Khaki; + public static readonly Rgba32 Khaki = Color.Khaki; /// /// Represents a matching the W3C definition that has an hex value of #E6E6FA. /// - public static readonly Rgba32 Lavender = NamedColors.Lavender; + public static readonly Rgba32 Lavender = Color.Lavender; /// /// Represents a matching the W3C definition that has an hex value of #FFF0F5. /// - public static readonly Rgba32 LavenderBlush = NamedColors.LavenderBlush; + public static readonly Rgba32 LavenderBlush = Color.LavenderBlush; /// /// Represents a matching the W3C definition that has an hex value of #7CFC00. /// - public static readonly Rgba32 LawnGreen = NamedColors.LawnGreen; + public static readonly Rgba32 LawnGreen = Color.LawnGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFFACD. /// - public static readonly Rgba32 LemonChiffon = NamedColors.LemonChiffon; + public static readonly Rgba32 LemonChiffon = Color.LemonChiffon; /// /// Represents a matching the W3C definition that has an hex value of #ADD8E6. /// - public static readonly Rgba32 LightBlue = NamedColors.LightBlue; + public static readonly Rgba32 LightBlue = Color.LightBlue; /// /// Represents a matching the W3C definition that has an hex value of #F08080. /// - public static readonly Rgba32 LightCoral = NamedColors.LightCoral; + public static readonly Rgba32 LightCoral = Color.LightCoral; /// /// Represents a matching the W3C definition that has an hex value of #E0FFFF. /// - public static readonly Rgba32 LightCyan = NamedColors.LightCyan; + public static readonly Rgba32 LightCyan = Color.LightCyan; /// /// Represents a matching the W3C definition that has an hex value of #FAFAD2. /// - public static readonly Rgba32 LightGoldenrodYellow = NamedColors.LightGoldenrodYellow; + public static readonly Rgba32 LightGoldenrodYellow = Color.LightGoldenrodYellow; /// /// Represents a matching the W3C definition that has an hex value of #D3D3D3. /// - public static readonly Rgba32 LightGray = NamedColors.LightGray; + public static readonly Rgba32 LightGray = Color.LightGray; /// /// Represents a matching the W3C definition that has an hex value of #90EE90. /// - public static readonly Rgba32 LightGreen = NamedColors.LightGreen; + public static readonly Rgba32 LightGreen = Color.LightGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFB6C1. /// - public static readonly Rgba32 LightPink = NamedColors.LightPink; + public static readonly Rgba32 LightPink = Color.LightPink; /// /// Represents a matching the W3C definition that has an hex value of #FFA07A. /// - public static readonly Rgba32 LightSalmon = NamedColors.LightSalmon; + public static readonly Rgba32 LightSalmon = Color.LightSalmon; /// /// Represents a matching the W3C definition that has an hex value of #20B2AA. /// - public static readonly Rgba32 LightSeaGreen = NamedColors.LightSeaGreen; + public static readonly Rgba32 LightSeaGreen = Color.LightSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #87CEFA. /// - public static readonly Rgba32 LightSkyBlue = NamedColors.LightSkyBlue; + public static readonly Rgba32 LightSkyBlue = Color.LightSkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #778899. /// - public static readonly Rgba32 LightSlateGray = NamedColors.LightSlateGray; + public static readonly Rgba32 LightSlateGray = Color.LightSlateGray; /// /// Represents a matching the W3C definition that has an hex value of #B0C4DE. /// - public static readonly Rgba32 LightSteelBlue = NamedColors.LightSteelBlue; + public static readonly Rgba32 LightSteelBlue = Color.LightSteelBlue; /// /// Represents a matching the W3C definition that has an hex value of #FFFFE0. /// - public static readonly Rgba32 LightYellow = NamedColors.LightYellow; + public static readonly Rgba32 LightYellow = Color.LightYellow; /// /// Represents a matching the W3C definition that has an hex value of #00FF00. /// - public static readonly Rgba32 Lime = NamedColors.Lime; + public static readonly Rgba32 Lime = Color.Lime; /// /// Represents a matching the W3C definition that has an hex value of #32CD32. /// - public static readonly Rgba32 LimeGreen = NamedColors.LimeGreen; + public static readonly Rgba32 LimeGreen = Color.LimeGreen; /// /// Represents a matching the W3C definition that has an hex value of #FAF0E6. /// - public static readonly Rgba32 Linen = NamedColors.Linen; + public static readonly Rgba32 Linen = Color.Linen; /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Rgba32 Magenta = NamedColors.Magenta; + public static readonly Rgba32 Magenta = Color.Magenta; /// /// Represents a matching the W3C definition that has an hex value of #800000. /// - public static readonly Rgba32 Maroon = NamedColors.Maroon; + public static readonly Rgba32 Maroon = Color.Maroon; /// /// Represents a matching the W3C definition that has an hex value of #66CDAA. /// - public static readonly Rgba32 MediumAquamarine = NamedColors.MediumAquamarine; + public static readonly Rgba32 MediumAquamarine = Color.MediumAquamarine; /// /// Represents a matching the W3C definition that has an hex value of #0000CD. /// - public static readonly Rgba32 MediumBlue = NamedColors.MediumBlue; + public static readonly Rgba32 MediumBlue = Color.MediumBlue; /// /// Represents a matching the W3C definition that has an hex value of #BA55D3. /// - public static readonly Rgba32 MediumOrchid = NamedColors.MediumOrchid; + public static readonly Rgba32 MediumOrchid = Color.MediumOrchid; /// /// Represents a matching the W3C definition that has an hex value of #9370DB. /// - public static readonly Rgba32 MediumPurple = NamedColors.MediumPurple; + public static readonly Rgba32 MediumPurple = Color.MediumPurple; /// /// Represents a matching the W3C definition that has an hex value of #3CB371. /// - public static readonly Rgba32 MediumSeaGreen = NamedColors.MediumSeaGreen; + public static readonly Rgba32 MediumSeaGreen = Color.MediumSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #7B68EE. /// - public static readonly Rgba32 MediumSlateBlue = NamedColors.MediumSlateBlue; + public static readonly Rgba32 MediumSlateBlue = Color.MediumSlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #00FA9A. /// - public static readonly Rgba32 MediumSpringGreen = NamedColors.MediumSpringGreen; + public static readonly Rgba32 MediumSpringGreen = Color.MediumSpringGreen; /// /// Represents a matching the W3C definition that has an hex value of #48D1CC. /// - public static readonly Rgba32 MediumTurquoise = NamedColors.MediumTurquoise; + public static readonly Rgba32 MediumTurquoise = Color.MediumTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #C71585. /// - public static readonly Rgba32 MediumVioletRed = NamedColors.MediumVioletRed; + public static readonly Rgba32 MediumVioletRed = Color.MediumVioletRed; /// /// Represents a matching the W3C definition that has an hex value of #191970. /// - public static readonly Rgba32 MidnightBlue = NamedColors.MidnightBlue; + public static readonly Rgba32 MidnightBlue = Color.MidnightBlue; /// /// Represents a matching the W3C definition that has an hex value of #F5FFFA. /// - public static readonly Rgba32 MintCream = NamedColors.MintCream; + public static readonly Rgba32 MintCream = Color.MintCream; /// /// Represents a matching the W3C definition that has an hex value of #FFE4E1. /// - public static readonly Rgba32 MistyRose = NamedColors.MistyRose; + public static readonly Rgba32 MistyRose = Color.MistyRose; /// /// Represents a matching the W3C definition that has an hex value of #FFE4B5. /// - public static readonly Rgba32 Moccasin = NamedColors.Moccasin; + public static readonly Rgba32 Moccasin = Color.Moccasin; /// /// Represents a matching the W3C definition that has an hex value of #FFDEAD. /// - public static readonly Rgba32 NavajoWhite = NamedColors.NavajoWhite; + public static readonly Rgba32 NavajoWhite = Color.NavajoWhite; /// /// Represents a matching the W3C definition that has an hex value of #000080. /// - public static readonly Rgba32 Navy = NamedColors.Navy; + public static readonly Rgba32 Navy = Color.Navy; /// /// Represents a matching the W3C definition that has an hex value of #FDF5E6. /// - public static readonly Rgba32 OldLace = NamedColors.OldLace; + public static readonly Rgba32 OldLace = Color.OldLace; /// /// Represents a matching the W3C definition that has an hex value of #808000. /// - public static readonly Rgba32 Olive = NamedColors.Olive; + public static readonly Rgba32 Olive = Color.Olive; /// /// Represents a matching the W3C definition that has an hex value of #6B8E23. /// - public static readonly Rgba32 OliveDrab = NamedColors.OliveDrab; + public static readonly Rgba32 OliveDrab = Color.OliveDrab; /// /// Represents a matching the W3C definition that has an hex value of #FFA500. /// - public static readonly Rgba32 Orange = NamedColors.Orange; + public static readonly Rgba32 Orange = Color.Orange; /// /// Represents a matching the W3C definition that has an hex value of #FF4500. /// - public static readonly Rgba32 OrangeRed = NamedColors.OrangeRed; + public static readonly Rgba32 OrangeRed = Color.OrangeRed; /// /// Represents a matching the W3C definition that has an hex value of #DA70D6. /// - public static readonly Rgba32 Orchid = NamedColors.Orchid; + public static readonly Rgba32 Orchid = Color.Orchid; /// /// Represents a matching the W3C definition that has an hex value of #EEE8AA. /// - public static readonly Rgba32 PaleGoldenrod = NamedColors.PaleGoldenrod; + public static readonly Rgba32 PaleGoldenrod = Color.PaleGoldenrod; /// /// Represents a matching the W3C definition that has an hex value of #98FB98. /// - public static readonly Rgba32 PaleGreen = NamedColors.PaleGreen; + public static readonly Rgba32 PaleGreen = Color.PaleGreen; /// /// Represents a matching the W3C definition that has an hex value of #AFEEEE. /// - public static readonly Rgba32 PaleTurquoise = NamedColors.PaleTurquoise; + public static readonly Rgba32 PaleTurquoise = Color.PaleTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #DB7093. /// - public static readonly Rgba32 PaleVioletRed = NamedColors.PaleVioletRed; + public static readonly Rgba32 PaleVioletRed = Color.PaleVioletRed; /// /// Represents a matching the W3C definition that has an hex value of #FFEFD5. /// - public static readonly Rgba32 PapayaWhip = NamedColors.PapayaWhip; + public static readonly Rgba32 PapayaWhip = Color.PapayaWhip; /// /// Represents a matching the W3C definition that has an hex value of #FFDAB9. /// - public static readonly Rgba32 PeachPuff = NamedColors.PeachPuff; + public static readonly Rgba32 PeachPuff = Color.PeachPuff; /// /// Represents a matching the W3C definition that has an hex value of #CD853F. /// - public static readonly Rgba32 Peru = NamedColors.Peru; + public static readonly Rgba32 Peru = Color.Peru; /// /// Represents a matching the W3C definition that has an hex value of #FFC0CB. /// - public static readonly Rgba32 Pink = NamedColors.Pink; + public static readonly Rgba32 Pink = Color.Pink; /// /// Represents a matching the W3C definition that has an hex value of #DDA0DD. /// - public static readonly Rgba32 Plum = NamedColors.Plum; + public static readonly Rgba32 Plum = Color.Plum; /// /// Represents a matching the W3C definition that has an hex value of #B0E0E6. /// - public static readonly Rgba32 PowderBlue = NamedColors.PowderBlue; + public static readonly Rgba32 PowderBlue = Color.PowderBlue; /// /// Represents a matching the W3C definition that has an hex value of #800080. /// - public static readonly Rgba32 Purple = NamedColors.Purple; + public static readonly Rgba32 Purple = Color.Purple; /// /// Represents a matching the W3C definition that has an hex value of #663399. /// - public static readonly Rgba32 RebeccaPurple = NamedColors.RebeccaPurple; + public static readonly Rgba32 RebeccaPurple = Color.RebeccaPurple; /// /// Represents a matching the W3C definition that has an hex value of #FF0000. /// - public static readonly Rgba32 Red = NamedColors.Red; + public static readonly Rgba32 Red = Color.Red; /// /// Represents a matching the W3C definition that has an hex value of #BC8F8F. /// - public static readonly Rgba32 RosyBrown = NamedColors.RosyBrown; + public static readonly Rgba32 RosyBrown = Color.RosyBrown; /// /// Represents a matching the W3C definition that has an hex value of #4169E1. /// - public static readonly Rgba32 RoyalBlue = NamedColors.RoyalBlue; + public static readonly Rgba32 RoyalBlue = Color.RoyalBlue; /// /// Represents a matching the W3C definition that has an hex value of #8B4513. /// - public static readonly Rgba32 SaddleBrown = NamedColors.SaddleBrown; + public static readonly Rgba32 SaddleBrown = Color.SaddleBrown; /// /// Represents a matching the W3C definition that has an hex value of #FA8072. /// - public static readonly Rgba32 Salmon = NamedColors.Salmon; + public static readonly Rgba32 Salmon = Color.Salmon; /// /// Represents a matching the W3C definition that has an hex value of #F4A460. /// - public static readonly Rgba32 SandyBrown = NamedColors.SandyBrown; + public static readonly Rgba32 SandyBrown = Color.SandyBrown; /// /// Represents a matching the W3C definition that has an hex value of #2E8B57. /// - public static readonly Rgba32 SeaGreen = NamedColors.SeaGreen; + public static readonly Rgba32 SeaGreen = Color.SeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFF5EE. /// - public static readonly Rgba32 SeaShell = NamedColors.SeaShell; + public static readonly Rgba32 SeaShell = Color.SeaShell; /// /// Represents a matching the W3C definition that has an hex value of #A0522D. /// - public static readonly Rgba32 Sienna = NamedColors.Sienna; + public static readonly Rgba32 Sienna = Color.Sienna; /// /// Represents a matching the W3C definition that has an hex value of #C0C0C0. /// - public static readonly Rgba32 Silver = NamedColors.Silver; + public static readonly Rgba32 Silver = Color.Silver; /// /// Represents a matching the W3C definition that has an hex value of #87CEEB. /// - public static readonly Rgba32 SkyBlue = NamedColors.SkyBlue; + public static readonly Rgba32 SkyBlue = Color.SkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #6A5ACD. /// - public static readonly Rgba32 SlateBlue = NamedColors.SlateBlue; + public static readonly Rgba32 SlateBlue = Color.SlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #708090. /// - public static readonly Rgba32 SlateGray = NamedColors.SlateGray; + public static readonly Rgba32 SlateGray = Color.SlateGray; /// /// Represents a matching the W3C definition that has an hex value of #FFFAFA. /// - public static readonly Rgba32 Snow = NamedColors.Snow; + public static readonly Rgba32 Snow = Color.Snow; /// /// Represents a matching the W3C definition that has an hex value of #00FF7F. /// - public static readonly Rgba32 SpringGreen = NamedColors.SpringGreen; + public static readonly Rgba32 SpringGreen = Color.SpringGreen; /// /// Represents a matching the W3C definition that has an hex value of #4682B4. /// - public static readonly Rgba32 SteelBlue = NamedColors.SteelBlue; + public static readonly Rgba32 SteelBlue = Color.SteelBlue; /// /// Represents a matching the W3C definition that has an hex value of #D2B48C. /// - public static readonly Rgba32 Tan = NamedColors.Tan; + public static readonly Rgba32 Tan = Color.Tan; /// /// Represents a matching the W3C definition that has an hex value of #008080. /// - public static readonly Rgba32 Teal = NamedColors.Teal; + public static readonly Rgba32 Teal = Color.Teal; /// /// Represents a matching the W3C definition that has an hex value of #D8BFD8. /// - public static readonly Rgba32 Thistle = NamedColors.Thistle; + public static readonly Rgba32 Thistle = Color.Thistle; /// /// Represents a matching the W3C definition that has an hex value of #FF6347. /// - public static readonly Rgba32 Tomato = NamedColors.Tomato; + public static readonly Rgba32 Tomato = Color.Tomato; /// /// Represents a matching the W3C definition that has an hex value of #FFFFFF. /// - public static readonly Rgba32 Transparent = NamedColors.Transparent; + public static readonly Rgba32 Transparent = Color.Transparent; /// /// Represents a matching the W3C definition that has an hex value of #40E0D0. /// - public static readonly Rgba32 Turquoise = NamedColors.Turquoise; + public static readonly Rgba32 Turquoise = Color.Turquoise; /// /// Represents a matching the W3C definition that has an hex value of #EE82EE. /// - public static readonly Rgba32 Violet = NamedColors.Violet; + public static readonly Rgba32 Violet = Color.Violet; /// /// Represents a matching the W3C definition that has an hex value of #F5DEB3. /// - public static readonly Rgba32 Wheat = NamedColors.Wheat; + public static readonly Rgba32 Wheat = Color.Wheat; /// /// Represents a matching the W3C definition that has an hex value of #FFFFFF. /// - public static readonly Rgba32 White = NamedColors.White; + public static readonly Rgba32 White = Color.White; /// /// Represents a matching the W3C definition that has an hex value of #F5F5F5. /// - public static readonly Rgba32 WhiteSmoke = NamedColors.WhiteSmoke; + public static readonly Rgba32 WhiteSmoke = Color.WhiteSmoke; /// /// Represents a matching the W3C definition that has an hex value of #FFFF00. /// - public static readonly Rgba32 Yellow = NamedColors.Yellow; + public static readonly Rgba32 Yellow = Color.Yellow; /// /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// - public static readonly Rgba32 YellowGreen = NamedColors.YellowGreen; + public static readonly Rgba32 YellowGreen = Color.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 004b25cd3..3da5d1855 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs @@ -4,6 +4,8 @@ using System; using System.Numerics; using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.PixelFormats.Utils; using SixLabors.Memory; namespace SixLabors.ImageSharp.PixelFormats @@ -22,49 +24,33 @@ namespace SixLabors.ImageSharp.PixelFormats internal override void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) + Span destVectors, + PixelConversionModifiers modifiers) { Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); destVectors = destVectors.Slice(0, sourcePixels.Length); - SimdUtils.BulkConvertByteToNormalizedFloat( MemoryMarshal.Cast(sourcePixels), MemoryMarshal.Cast(destVectors)); + Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); } /// - internal override void FromVector4( + internal override void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destPixels) + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) { Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); destPixels = destPixels.Slice(0, sourceVectors.Length); - + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( MemoryMarshal.Cast(sourceVectors), MemoryMarshal.Cast(destPixels)); } - - /// - 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); - } } } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 5a16704ef..ba259ca8e 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. // 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; @@ -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)] + 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; + } } /// @@ -169,6 +175,22 @@ namespace SixLabors.ImageSharp.PixelFormats 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 /// . @@ -217,7 +239,20 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static Rgba32 FromHex(string hex) => ColorBuilder.FromHex(hex); + public static Rgba32 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)); + } + + packedValue = BinaryPrimitives.ReverseEndianness(packedValue); + return Unsafe.As(ref packedValue); + } /// public PixelOperations CreatePixelOperations() => new PixelOperations(); @@ -266,6 +301,10 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromGray8(Gray8 source) @@ -406,5 +445,42 @@ namespace SixLabors.ImageSharp.PixelFormats 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 5ae5492e2..978d9b015 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -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,7 +56,86 @@ 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 = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.R = (ushort)MathF.Round(vector.X); + this.G = (ushort)MathF.Round(vector.Y); + this.B = (ushort)MathF.Round(vector.Z); + this.A = (ushort)MathF.Round(vector.W); + } + + /// + /// Gets or sets the RGB components of this struct as . /// public Rgb48 Rgb { @@ -76,6 +156,22 @@ namespace SixLabors.ImageSharp.PixelFormats 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. /// @@ -154,6 +250,10 @@ 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) @@ -217,6 +317,74 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba64(Rgba64 source) => this = source; + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public 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 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 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 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 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); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs index bffaf57dd..b331dbd99 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs @@ -3,8 +3,11 @@ 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( + internal override void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destinationColors) + Span sourceVectors, + Span destinationColors, + PixelConversionModifiers modifiers) { Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationColors, nameof(destinationColors)); + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); MemoryMarshal.Cast(sourceVectors).CopyTo(destinationColors); } - /// - internal override void ToScaledVector4( - Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationVectors) - => this.ToVector4(configuration, sourceColors, destinationVectors); - /// internal override void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) + Span destVectors, + PixelConversionModifiers modifiers) { Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); MemoryMarshal.Cast(sourcePixels).CopyTo(destVectors); + Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); + } + + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref Gray8 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.ConvertFromRgbaScaledVector4(sp); + } + } + + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref Gray16 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destBaseRef, i); + + 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 d65a5ade7..38e75d183 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static RgbaVector FromHex(string hex) => ColorBuilder.FromHex(hex); + public static RgbaVector FromHex(string hex) => Color.FromHex(hex).ToPixel(); /// public PixelOperations CreatePixelOperations() => new PixelOperations(); @@ -134,6 +134,10 @@ 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()); @@ -152,10 +156,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)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 96fe15ed6..803a77b23 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -111,6 +111,10 @@ 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()); @@ -129,10 +133,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)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index d224f8eb4..c52b29347 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -116,6 +116,10 @@ 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()); diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index 207a8767d..700281992 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -657,5 +657,77 @@ namespace SixLabors.ImageSharp.PixelFormats { 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. + internal virtual void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Bgra5551 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromBgra5551Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + { + this.FromBgra5551(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + } + + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + 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)] + internal void ToBgra5551Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToBgra5551(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt index 8579423b3..860301232 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -136,5 +136,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}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 115dd7a43..2b2d79c73 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -7,6 +7,8 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -24,129 +26,121 @@ 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 apply during the conversion + internal virtual void FromVector4Destructive( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destPixels) + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); - Utils.Vector4Converters.Default.DangerousFromVector4(sourceVectors, destPixels); + Utils.Vector4Converters.Default.FromVector4(sourceVectors, destPixels, modifiers); } + /// + /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. + /// The method is DESTRUCTIVE altering the contents of . + /// + /// + /// The destructive behavior is a design choice for performance reasons. + /// In a typical use case the contents of are abandoned after the conversion. + /// + /// A to configure internal operations + /// The to the source vectors. + /// The to the destination colors. + internal void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels) => + this.FromVector4Destructive(configuration, sourceVectors, destPixels, PixelConversionModifiers.None); + /// /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. /// /// A to configure internal operations /// The to the source colors. /// The to the destination vectors. + /// The to apply during the conversion internal virtual void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) + Span destVectors, + PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); - Utils.Vector4Converters.Default.DangerousToVector4(sourcePixels, destVectors); + Utils.Vector4Converters.Default.ToVector4(sourcePixels, destVectors, modifiers); } /// - /// 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. + internal virtual void ToVector4( Configuration configuration, - ReadOnlySpan sourceVectors, - Span destinationColors) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationColors, nameof(destinationColors)); - - Utils.Vector4Converters.Default.DangerousFromScaledVector4(sourceVectors, destinationColors); - } + ReadOnlySpan sourcePixels, + Span destVectors) => + this.ToVector4(configuration, sourcePixels, destVectors, PixelConversionModifiers.None); - /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. - /// - /// A to configure internal operations - /// The to the source colors. - /// The to the destination vectors. - internal virtual void ToScaledVector4( + internal virtual void From( Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationVectors) + ReadOnlySpan sourcePixels, + Span destinationPixels) + where TSourcePixel : struct, IPixel { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceColors, destinationVectors, nameof(destinationVectors)); + const int SliceLength = 1024; + int numberOfSlices = sourcePixels.Length / SliceLength; + using (IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(SliceLength)) + { + Span vectorSpan = tempVectors.GetSpan(); + for (int i = 0; i < numberOfSlices; i++) + { + 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); + } - Utils.Vector4Converters.Default.DangerousToScaledVector4(sourceColors, destinationVectors); + int endOfCompleteSlices = numberOfSlices * SliceLength; + int remainder = sourcePixels.Length - endOfCompleteSlices; + if (remainder > 0) + { + 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); + } + } } /// - /// Converts 'sourceColors.Length' pixels from 'sourceColors' into 'destinationColors'. + /// Converts 'sourcePixels.Length' pixels from 'sourcePixels' into 'destinationPixels'. /// /// The destination pixel type. - /// A to configure internal operations - /// The to the source colors. - /// The to the destination colors. + /// A to configure internal operations. + /// The to the source pixels. + /// The to the destination pixels. internal virtual void To( Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationColors) + ReadOnlySpan sourcePixels, + Span destinationPixels) where TDestinationPixel : struct, 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)) - { - ref Gray16 gray16Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationColors)); - for (int i = 0; i < count; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref gray16Ref, i); - dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); - } - - return; - } + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - if (typeof(TDestinationPixel) == typeof(Gray8)) - { - ref Gray8 gray8Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationColors)); - for (int i = 0; i < count; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref gray8Ref, i); - dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); - } - - return; - } - - // Normal conversion - ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationColors); - for (int i = 0; i < count; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); - ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + PixelOperations.Instance.From(configuration, sourcePixels, destinationPixels); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index 139dbfa10..4c4e60276 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -1,8 +1,13 @@ -using System; +// 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.Companding; + namespace SixLabors.ImageSharp.PixelFormats.Utils { /// @@ -12,13 +17,75 @@ 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 : struct, 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 : struct, 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 : struct, 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 : struct, 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 @@ -35,7 +102,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousToVector4( + private static void UnsafeToVector4Core( ReadOnlySpan sourcePixels, Span destVectors) where TPixel : struct, IPixel @@ -52,7 +119,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousFromScaledVector4( + private static void UnsafeFromScaledVector4Core( ReadOnlySpan sourceVectors, Span destinationColors) where TPixel : struct, IPixel @@ -69,7 +136,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } [MethodImpl(InliningOptions.ShortMethod)] - internal static void DangerousToScaledVector4( + private static void UnsafeToScaledVector4Core( ReadOnlySpan sourceColors, Span destinationVectors) where TPixel : struct, IPixel diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 5609e606d..fe8d7dec3 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -1,9 +1,14 @@ -using System; +// 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.ColorSpaces.Companding; + namespace SixLabors.ImageSharp.PixelFormats.Utils { /// @@ -26,11 +31,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( @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils PixelOperations pixelOperations, ReadOnlySpan sourcePixels, Span destVectors, - bool scaled) + PixelConversionModifiers modifiers) where TPixel : struct, IPixel { Guard.NotNull(configuration, nameof(configuration)); @@ -49,7 +51,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils // Not worth for small buffers: if (count < Vector4ConversionThreshold) { - ToVector4Fallback(sourcePixels, destVectors, scaled); + Default.UnsafeToVector4(sourcePixels, destVectors, modifiers); return; } @@ -67,22 +69,22 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils 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) + PixelConversionModifiers modifiers) where TPixel : struct, IPixel { Guard.NotNull(configuration, nameof(configuration)); @@ -93,11 +95,14 @@ 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)) @@ -112,34 +117,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) diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs new file mode 100644 index 000000000..447869a7d --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.ColorSpaces.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)) + { + Vector4Utils.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)) + { + Vector4Utils.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 new file mode 100644 index 000000000..af2e9465a --- /dev/null +++ b/src/ImageSharp/Primitives/ColorMatrix.cs @@ -0,0 +1,459 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +#pragma warning disable SA1117 // Parameters should be on same line or separate lines +using System; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Primitives +{ + /// + /// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image. + /// + [StructLayout(LayoutKind.Sequential)] + public struct ColorMatrix : IEquatable + { + /// + /// Value at row 1, column 1 of the matrix. + /// + public float M11; + + /// + /// Value at row 1, column 2 of the matrix. + /// + public float M12; + + /// + /// Value at row 1, column 3 of the matrix. + /// + public float M13; + + /// + /// Value at row 1, column 4 of the matrix. + /// + public float M14; + + /// + /// Value at row 2, column 1 of the matrix. + /// + public float M21; + + /// + /// Value at row 2, column 2 of the matrix. + /// + public float M22; + + /// + /// Value at row 2, column 3 of the matrix. + /// + public float M23; + + /// + /// Value at row 2, column 4 of the matrix. + /// + public float M24; + + /// + /// Value at row 3, column 1 of the matrix. + /// + public float M31; + + /// + /// Value at row 3, column 2 of the matrix. + /// + public float M32; + + /// + /// Value at row 3, column 3 of the matrix. + /// + public float M33; + + /// + /// Value at row 3, column 4 of the matrix. + /// + public float M34; + + /// + /// Value at row 4, column 1 of the matrix. + /// + public float M41; + + /// + /// Value at row 4, column 2 of the matrix. + /// + public float M42; + + /// + /// Value at row 4, column 3 of the matrix. + /// + public float M43; + + /// + /// Value at row 4, column 4 of the matrix. + /// + public float M44; + + /// + /// Value at row 5, column 1 of the matrix. + /// + public float M51; + + /// + /// Value at row 5, column 2 of the matrix. + /// + public float M52; + + /// + /// Value at row 5, column 3 of the matrix. + /// + public float M53; + + /// + /// Value at row 5, column 4 of the matrix. + /// + public float M54; + + /// + /// Initializes a new instance of the struct. + /// + /// The value at row 1, column 1 of the matrix. + /// The value at row 1, column 2 of the matrix. + /// The value at row 1, column 3 of the matrix. + /// The value at row 1, column 4 of the matrix. + /// The value at row 2, column 1 of the matrix. + /// The value at row 2, column 2 of the matrix. + /// The value at row 2, column 3 of the matrix. + /// The value at row 2, column 4 of the matrix. + /// The value at row 3, column 1 of the matrix. + /// The value at row 3, column 2 of the matrix. + /// The value at row 3, column 3 of the matrix. + /// The value at row 3, column 4 of the matrix. + /// The value at row 4, column 1 of the matrix. + /// The value at row 4, column 2 of the matrix. + /// The value at row 4, column 3 of the matrix. + /// The value at row 4, column 4 of the matrix. + /// The value at row 5, column 1 of the matrix. + /// The value at row 5, column 2 of the matrix. + /// The value at row 5, column 3 of the matrix. + /// The value at row 5, column 4 of the matrix. + public ColorMatrix(float m11, float m12, float m13, float m14, + float m21, float m22, float m23, float m24, + float m31, float m32, float m33, float m34, + float m41, float m42, float m43, float m44, + float m51, float m52, float m53, float m54) + { + this.M11 = m11; + this.M12 = m12; + this.M13 = m13; + this.M14 = m14; + + this.M21 = m21; + this.M22 = m22; + this.M23 = m23; + this.M24 = m24; + + this.M31 = m31; + this.M32 = m32; + this.M33 = m33; + this.M34 = m34; + + this.M41 = m41; + this.M42 = m42; + this.M43 = m43; + this.M44 = m44; + + this.M51 = m51; + this.M52 = m52; + this.M53 = m53; + this.M54 = m54; + } + + /// + /// Gets the multiplicative identity matrix. + /// + public static ColorMatrix Identity { get; } = + new ColorMatrix(1F, 0F, 0F, 0F, + 0F, 1F, 0F, 0F, + 0F, 0F, 1F, 0F, + 0F, 0F, 0F, 1F, + 0F, 0F, 0F, 0F); + + /// + /// Gets a value indicating whether the matrix is the identity matrix. + /// + public bool IsIdentity + { + get + { + // Check diagonal element first for early out. + return this.M11 == 1F && this.M22 == 1F && this.M33 == 1F && this.M44 == 1F + && this.M12 == 0F && this.M13 == 0F && this.M14 == 0F + && this.M21 == 0F && this.M23 == 0F && this.M24 == 0F + && this.M31 == 0F && this.M32 == 0F && this.M34 == 0F + && this.M41 == 0F && this.M42 == 0F && this.M43 == 0F + && this.M51 == 0F && this.M52 == 0F && this.M53 == 0F && this.M54 == 0F; + } + } + + /// + /// Adds two matrices together. + /// + /// The first source matrix. + /// The second source matrix. + /// The resulting matrix. + public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2) + { + ColorMatrix m; + + m.M11 = value1.M11 + value2.M11; + m.M12 = value1.M12 + value2.M12; + m.M13 = value1.M13 + value2.M13; + m.M14 = value1.M14 + value2.M14; + m.M21 = value1.M21 + value2.M21; + m.M22 = value1.M22 + value2.M22; + m.M23 = value1.M23 + value2.M23; + m.M24 = value1.M24 + value2.M24; + m.M31 = value1.M31 + value2.M31; + m.M32 = value1.M32 + value2.M32; + m.M33 = value1.M33 + value2.M33; + m.M34 = value1.M34 + value2.M34; + m.M41 = value1.M41 + value2.M41; + m.M42 = value1.M42 + value2.M42; + m.M43 = value1.M43 + value2.M43; + m.M44 = value1.M44 + value2.M44; + m.M51 = value1.M51 + value2.M51; + m.M52 = value1.M52 + value2.M52; + m.M53 = value1.M53 + value2.M53; + m.M54 = value1.M54 + value2.M54; + + return m; + } + + /// + /// Subtracts the second matrix from the first. + /// + /// The first source matrix. + /// The second source matrix. + /// The result of the subtraction. + public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2) + { + ColorMatrix m; + + m.M11 = value1.M11 - value2.M11; + m.M12 = value1.M12 - value2.M12; + m.M13 = value1.M13 - value2.M13; + m.M14 = value1.M14 - value2.M14; + m.M21 = value1.M21 - value2.M21; + m.M22 = value1.M22 - value2.M22; + m.M23 = value1.M23 - value2.M23; + m.M24 = value1.M24 - value2.M24; + m.M31 = value1.M31 - value2.M31; + m.M32 = value1.M32 - value2.M32; + m.M33 = value1.M33 - value2.M33; + m.M34 = value1.M34 - value2.M34; + m.M41 = value1.M41 - value2.M41; + m.M42 = value1.M42 - value2.M42; + m.M43 = value1.M43 - value2.M43; + m.M44 = value1.M44 - value2.M44; + m.M51 = value1.M51 - value2.M51; + m.M52 = value1.M52 - value2.M52; + m.M53 = value1.M53 - value2.M53; + m.M54 = value1.M54 - value2.M54; + + return m; + } + + /// + /// Returns a new matrix with the negated elements of the given matrix. + /// + /// The source matrix. + /// The negated matrix. + public static unsafe ColorMatrix operator -(ColorMatrix value) + { + ColorMatrix m; + + m.M11 = -value.M11; + m.M12 = -value.M12; + m.M13 = -value.M13; + m.M14 = -value.M14; + m.M21 = -value.M21; + m.M22 = -value.M22; + m.M23 = -value.M23; + m.M24 = -value.M24; + m.M31 = -value.M31; + m.M32 = -value.M32; + m.M33 = -value.M33; + m.M34 = -value.M34; + m.M41 = -value.M41; + m.M42 = -value.M42; + m.M43 = -value.M43; + m.M44 = -value.M44; + m.M51 = -value.M51; + m.M52 = -value.M52; + m.M53 = -value.M53; + m.M54 = -value.M54; + + return m; + } + + /// + /// Multiplies a matrix by another matrix. + /// + /// The first source matrix. + /// The second source matrix. + /// The result of the multiplication. + public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2) + { + ColorMatrix m; + + // First row + m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); + m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); + m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); + m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); + + // Second row + m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); + m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); + m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); + m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); + + // Third row + m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); + m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); + m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); + m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); + + // Fourth row + m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); + m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); + m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); + m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); + + // Fifth row + m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; + m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; + m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53; + m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54; + + return m; + } + + /// + /// Multiplies a matrix by a scalar value. + /// + /// The source matrix. + /// The scaling factor. + /// The scaled matrix. + public static ColorMatrix operator *(ColorMatrix value1, float value2) + { + ColorMatrix m; + + m.M11 = value1.M11 * value2; + m.M12 = value1.M12 * value2; + m.M13 = value1.M13 * value2; + m.M14 = value1.M14 * value2; + m.M21 = value1.M21 * value2; + m.M22 = value1.M22 * value2; + m.M23 = value1.M23 * value2; + m.M24 = value1.M24 * value2; + m.M31 = value1.M31 * value2; + m.M32 = value1.M32 * value2; + m.M33 = value1.M33 * value2; + m.M34 = value1.M34 * value2; + m.M41 = value1.M41 * value2; + m.M42 = value1.M42 * value2; + m.M43 = value1.M43 * value2; + m.M44 = value1.M44 * value2; + m.M51 = value1.M51 * value2; + m.M52 = value1.M52 * value2; + m.M53 = value1.M53 * value2; + m.M54 = value1.M54 * value2; + + return m; + } + + /// + /// Returns a boolean indicating whether the given two matrices are equal. + /// + /// The first matrix to compare. + /// The second matrix to compare. + /// True if the given matrices are equal; False otherwise. + public static bool operator ==(ColorMatrix value1, ColorMatrix value2) => value1.Equals(value2); + + /// + /// Returns a boolean indicating whether the given two matrices are not equal. + /// + /// The first matrix to compare. + /// The second matrix to compare. + /// True if the given matrices are equal; False otherwise. + public static bool operator !=(ColorMatrix value1, ColorMatrix value2) => !value1.Equals(value2); + + /// + public override bool Equals(object obj) => obj is ColorMatrix matrix && this.Equals(matrix); + + /// + public bool Equals(ColorMatrix other) => + this.M11 == other.M11 + && this.M12 == other.M12 + && this.M13 == other.M13 + && this.M14 == other.M14 + && this.M21 == other.M21 + && this.M22 == other.M22 + && this.M23 == other.M23 + && this.M24 == other.M24 + && this.M31 == other.M31 + && this.M32 == other.M32 + && this.M33 == other.M33 + && this.M34 == other.M34 + && this.M41 == other.M41 + && this.M42 == other.M42 + && this.M43 == other.M43 + && this.M44 == other.M44 + && this.M51 == other.M51 + && this.M52 == other.M52 + && this.M53 == other.M53 + && this.M54 == other.M54; + + /// + public override int GetHashCode() + { + HashCode hash = default; + hash.Add(this.M11); + hash.Add(this.M12); + hash.Add(this.M13); + hash.Add(this.M14); + hash.Add(this.M21); + hash.Add(this.M22); + hash.Add(this.M23); + hash.Add(this.M24); + hash.Add(this.M31); + hash.Add(this.M32); + hash.Add(this.M33); + hash.Add(this.M34); + hash.Add(this.M41); + hash.Add(this.M42); + hash.Add(this.M43); + hash.Add(this.M44); + hash.Add(this.M51); + hash.Add(this.M52); + hash.Add(this.M53); + hash.Add(this.M54); + return hash.ToHashCode(); + } + + /// + public override string ToString() + { + CultureInfo ci = CultureInfo.CurrentCulture; + + return string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}", + this.M11.ToString(ci), this.M12.ToString(ci), this.M13.ToString(ci), this.M14.ToString(ci), + this.M21.ToString(ci), this.M22.ToString(ci), this.M23.ToString(ci), this.M24.ToString(ci), + this.M31.ToString(ci), this.M32.ToString(ci), this.M33.ToString(ci), this.M34.ToString(ci), + this.M41.ToString(ci), this.M42.ToString(ci), this.M43.ToString(ci), this.M44.ToString(ci), + this.M51.ToString(ci), this.M52.ToString(ci), this.M53.ToString(ci), this.M54.ToString(ci)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/LongRational.cs b/src/ImageSharp/Primitives/LongRational.cs index d790b110d..b15aa4022 100644 --- a/src/ImageSharp/Primitives/LongRational.cs +++ b/src/ImageSharp/Primitives/LongRational.cs @@ -8,7 +8,7 @@ using System.Text; namespace SixLabors.ImageSharp.Primitives { /// - /// 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/ValueSize.cs b/src/ImageSharp/Primitives/ValueSize.cs index e5e086540..577e9187a 100644 --- a/src/ImageSharp/Primitives/ValueSize.cs +++ b/src/ImageSharp/Primitives/ValueSize.cs @@ -7,7 +7,7 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Primitives { /// - /// 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 +28,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 +59,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 +88,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 +98,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 +114,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/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/BlackWhiteExtensions.cs deleted file mode 100644 index 0484fa84e..000000000 --- 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 624da239b..000000000 --- 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/ColorBlindnessExtensions.cs b/src/ImageSharp/Processing/ColorBlindnessExtensions.cs deleted file mode 100644 index 331635895..000000000 --- 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/CropExtensions.cs b/src/ImageSharp/Processing/CropExtensions.cs deleted file mode 100644 index 1c0d80afc..000000000 --- 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/DefaultInternalImageProcessorContext.cs b/src/ImageSharp/Processing/DefaultImageProcessorContext.cs similarity index 72% rename from src/ImageSharp/Processing/DefaultInternalImageProcessorContext.cs rename to src/ImageSharp/Processing/DefaultImageProcessorContext.cs index 43ba25972..d0c4c076f 100644 --- a/src/ImageSharp/Processing/DefaultInternalImageProcessorContext.cs +++ b/src/ImageSharp/Processing/DefaultImageProcessorContext.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing /// Performs processor application operations on the source image /// /// The pixel format - internal class DefaultInternalImageProcessorContext : IInternalImageProcessingContext + internal class DefaultImageProcessorContext : IInternalImageProcessingContext where TPixel : struct, IPixel { private readonly bool mutate; @@ -21,11 +21,11 @@ namespace SixLabors.ImageSharp.Processing private Image destination; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The image. /// The mutate. - public DefaultInternalImageProcessorContext(Image source, bool mutate) + public DefaultImageProcessorContext(Image source, bool mutate) { this.mutate = mutate; this.source = source; @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing public MemoryAllocator MemoryAllocator => this.source.GetConfiguration().MemoryAllocator; /// - public Image Apply() + public Image GetResultImage() { if (!this.mutate && this.destination is null) { @@ -53,8 +53,19 @@ namespace SixLabors.ImageSharp.Processing /// public Size GetCurrentSize() => this.GetCurrentBounds().Size; - /// - public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + var processorImplementation = processor.CreatePixelSpecificProcessor(); + return this.ApplyProcessor(processorImplementation, rectangle); + } + + public IImageProcessingContext ApplyProcessor(IImageProcessor processor) + { + var processorImplementation = processor.CreatePixelSpecificProcessor(); + return this.ApplyProcessor(processorImplementation); + } + + private IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { if (!this.mutate && this.destination is null) { @@ -74,8 +85,7 @@ namespace SixLabors.ImageSharp.Processing return this; } - /// - public IImageProcessingContext ApplyProcessor(IImageProcessor processor) + private IImageProcessingContext ApplyProcessor(IImageProcessor processor) { return this.ApplyProcessor(processor, this.GetCurrentBounds()); } diff --git a/src/ImageSharp/Processing/DitherExtensions.cs b/src/ImageSharp/Processing/DitherExtensions.cs deleted file mode 100644 index 795561e70..000000000 --- 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/EntropyCropExtensions.cs b/src/ImageSharp/Processing/EntropyCropExtensions.cs deleted file mode 100644 index 157e69ef2..000000000 --- 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/AutoOrientExtensions.cs b/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs similarity index 54% rename from src/ImageSharp/Processing/AutoOrientExtensions.cs rename to src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs index d11fc9623..a831e2d9a 100644 --- a/src/ImageSharp/Processing/AutoOrientExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs @@ -7,18 +7,17 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of auto-orientation operations to the type. + /// 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 pixel format. /// The image to auto rotate. - /// The - public static IImageProcessingContext AutoOrient(this IImageProcessingContext source) - where TPixel : struct, IPixel - => source.ApplyProcessor(new AutoOrientProcessor()); + /// 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/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs similarity index 50% rename from src/ImageSharp/Processing/BackgroundColorExtensions.cs rename to src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs index 1ad2c9237..dd1cc1ed2 100644 --- a/src/ImageSharp/Processing/BackgroundColorExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs @@ -1,67 +1,69 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Overlays; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of a background color to the type. + /// 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 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); + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) => + 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); + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + Color color, + Rectangle rectangle) => + 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + GraphicsOptions options, + Color color) => + 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); + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + Rectangle rectangle) => + source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/BinaryDiffuseExtensions.cs b/src/ImageSharp/Processing/Extensions/BinaryDiffuseExtensions.cs similarity index 56% rename from src/ImageSharp/Processing/BinaryDiffuseExtensions.cs rename to src/ImageSharp/Processing/Extensions/BinaryDiffuseExtensions.cs index 788942dde..760102aac 100644 --- a/src/ImageSharp/Processing/BinaryDiffuseExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/BinaryDiffuseExtensions.cs @@ -1,7 +1,6 @@ // 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; @@ -9,55 +8,61 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds binary diffusion extensions to the type. + /// Defines extension methods to apply binary diffusion on an + /// using Mutate/Clone. /// 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDiffuse( + this IImageProcessingContext source, + IErrorDiffuser diffuser, + float threshold) => + 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); + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDiffuse( + this IImageProcessingContext source, + IErrorDiffuser diffuser, + float threshold, + Rectangle rectangle) => + 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDiffuse( + this IImageProcessingContext source, + IErrorDiffuser diffuser, + float threshold, + Color upperColor, + Color lowerColor) => + 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. @@ -66,9 +71,16 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDiffuse( + this IImageProcessingContext source, + IErrorDiffuser diffuser, + float threshold, + Color upperColor, + Color lowerColor, + Rectangle rectangle) => + 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/Extensions/BinaryDitherExtensions.cs similarity index 52% rename from src/ImageSharp/Processing/BinaryDitherExtensions.cs rename to src/ImageSharp/Processing/Extensions/BinaryDitherExtensions.cs index 617770196..e8ce252a2 100644 --- a/src/ImageSharp/Processing/BinaryDitherExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/BinaryDitherExtensions.cs @@ -1,7 +1,6 @@ // 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; @@ -9,52 +8,54 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds binary dithering extensions to the type. + /// 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 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext + BinaryDither(this IImageProcessingContext source, IOrderedDither dither) => + 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IOrderedDither dither, + Color upperColor, + Color lowerColor) => + 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); + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IOrderedDither dither, + Rectangle rectangle) => + 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. @@ -62,9 +63,13 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IOrderedDither dither, + Color upperColor, + Color lowerColor, + Rectangle rectangle) => + 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/Extensions/BinaryThresholdExtensions.cs similarity index 55% rename from src/ImageSharp/Processing/BinaryThresholdExtensions.cs rename to src/ImageSharp/Processing/Extensions/BinaryThresholdExtensions.cs index 31f81ba4b..35aa681e3 100644 --- a/src/ImageSharp/Processing/BinaryThresholdExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/BinaryThresholdExtensions.cs @@ -1,59 +1,59 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Binarization; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds binary thresholding extensions to the type. + /// 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 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)); + /// 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 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); + /// 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 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)); + /// 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 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. @@ -61,9 +61,13 @@ namespace SixLabors.ImageSharp.Processing /// /// 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); + /// 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/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs new file mode 100644 index 000000000..ee34cd99e --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +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/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs new file mode 100644 index 000000000..f3400c24e --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs @@ -0,0 +1,45 @@ +// 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 +{ + /// + /// 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/BrightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs similarity index 64% rename from src/ImageSharp/Processing/BrightnessExtensions.cs rename to src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs index 2f252ad30..db8409176 100644 --- a/src/ImageSharp/Processing/BrightnessExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs @@ -8,7 +8,8 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the alteration of the brightness component to the type. + /// Defines extensions that allow the alteration of the brightness component of an + /// using Mutate/Clone. /// public static class BrightnessExtensions { @@ -19,13 +20,11 @@ namespace SixLabors.ImageSharp.Processing /// 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)); + /// 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. @@ -34,15 +33,13 @@ namespace SixLabors.ImageSharp.Processing /// 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); + /// 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/ColorBlindnessExtensions.cs b/src/ImageSharp/Processing/Extensions/ColorBlindnessExtensions.cs new file mode 100644 index 000000000..b8d503955 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/ColorBlindnessExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +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/ContrastExtensions.cs b/src/ImageSharp/Processing/Extensions/ContrastExtensions.cs similarity index 63% rename from src/ImageSharp/Processing/ContrastExtensions.cs rename to src/ImageSharp/Processing/Extensions/ContrastExtensions.cs index 776aa6751..bdfd7c98a 100644 --- a/src/ImageSharp/Processing/ContrastExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/ContrastExtensions.cs @@ -1,14 +1,14 @@ // 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. + /// Defines extensions that allow the alteration of the contrast component of an + /// using Mutate/Clone. /// public static class ContrastExtensions { @@ -19,13 +19,11 @@ namespace SixLabors.ImageSharp.Processing /// 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)); + /// 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. @@ -34,15 +32,13 @@ namespace SixLabors.ImageSharp.Processing /// 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); + /// 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/CropExtensions.cs b/src/ImageSharp/Processing/Extensions/CropExtensions.cs new file mode 100644 index 000000000..7ec85169e --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/CropExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; + +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/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/DetectEdgesExtensions.cs similarity index 50% rename from src/ImageSharp/Processing/DetectEdgesExtensions.cs rename to src/ImageSharp/Processing/Extensions/DetectEdgesExtensions.cs index 5ac89df29..837b26910 100644 --- a/src/ImageSharp/Processing/DetectEdgesExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/DetectEdgesExtensions.cs @@ -1,89 +1,86 @@ // 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.Convolution; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds edge detection extensions to the type. + /// Defines edge detection extensions applicable on an using Mutate/Clone. /// public static class DetectEdgesExtensions { /// - /// Detects any edges within the image. Uses the filter + /// 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => + DetectEdges(source, new SobelProcessor(true)); /// - /// Detects any edges within the image. Uses the filter + /// 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) => + 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectionOperators filter) => + 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectionOperators filter, + bool grayscale) => + 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectionOperators filter, + Rectangle rectangle, + bool grayscale = true) => + 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 + /// The to allow chaining of operations. + private static IImageProcessingContext DetectEdges(this IImageProcessingContext source, IImageProcessor filter) { return source.ApplyProcessor(filter); } @@ -91,65 +88,65 @@ namespace SixLabors.ImageSharp.Processing /// /// 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 + /// The to allow chaining of operations. + private static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + Rectangle rectangle, + IImageProcessor filter) { source.ApplyProcessor(filter, rectangle); return source; } - private static IEdgeDetectorProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale) - where TPixel : struct, IPixel + private static IImageProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale) { - IEdgeDetectorProcessor processor; + IImageProcessor processor; switch (filter) { case EdgeDetectionOperators.Kayyali: - processor = new KayyaliProcessor(grayscale); + processor = new KayyaliProcessor(grayscale); break; case EdgeDetectionOperators.Kirsch: - processor = new KirschProcessor(grayscale); + processor = new KirschProcessor(grayscale); break; case EdgeDetectionOperators.Laplacian3x3: - processor = new Laplacian3x3Processor(grayscale); + processor = new Laplacian3x3Processor(grayscale); break; case EdgeDetectionOperators.Laplacian5x5: - processor = new Laplacian5x5Processor(grayscale); + processor = new Laplacian5x5Processor(grayscale); break; case EdgeDetectionOperators.LaplacianOfGaussian: - processor = new LaplacianOfGaussianProcessor(grayscale); + processor = new LaplacianOfGaussianProcessor(grayscale); break; case EdgeDetectionOperators.Prewitt: - processor = new PrewittProcessor(grayscale); + processor = new PrewittProcessor(grayscale); break; case EdgeDetectionOperators.RobertsCross: - processor = new RobertsCrossProcessor(grayscale); + processor = new RobertsCrossProcessor(grayscale); break; case EdgeDetectionOperators.Robinson: - processor = new RobinsonProcessor(grayscale); + processor = new RobinsonProcessor(grayscale); break; case EdgeDetectionOperators.Scharr: - processor = new ScharrProcessor(grayscale); + processor = new ScharrProcessor(grayscale); break; default: - processor = new SobelProcessor(grayscale); + processor = new SobelProcessor(grayscale); break; } diff --git a/src/ImageSharp/Processing/DiffuseExtensions.cs b/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs similarity index 51% rename from src/ImageSharp/Processing/DiffuseExtensions.cs rename to src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs index 768d28116..f9a1bdde0 100644 --- a/src/ImageSharp/Processing/DiffuseExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs @@ -1,82 +1,84 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using System; + using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Dithering { /// - /// Adds diffusion extensions to the type. + /// Defines extension methods to apply diffusion to an + /// using Mutate/Clone. /// 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); + /// The to allow chaining of operations. + public static IImageProcessingContext Diffuse(this IImageProcessingContext source) => + 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); + /// The to allow chaining of operations. + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, float threshold) => + 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext Diffuse( + this IImageProcessingContext source, + IErrorDiffuser diffuser, + float threshold) => + 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); + /// The to allow chaining of operations. + public static IImageProcessingContext Diffuse( + this IImageProcessingContext source, + IErrorDiffuser diffuser, + float threshold, + Rectangle rectangle) => + 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)); + /// The to allow chaining of operations. + public static IImageProcessingContext Diffuse( + this IImageProcessingContext source, + IErrorDiffuser diffuser, + float threshold, + ReadOnlyMemory palette) => + 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. @@ -84,9 +86,13 @@ namespace SixLabors.ImageSharp.Processing.Dithering /// /// 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); + /// The to allow chaining of operations. + public static IImageProcessingContext Diffuse( + this IImageProcessingContext source, + IErrorDiffuser diffuser, + float threshold, + ReadOnlyMemory palette, + Rectangle rectangle) => + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/DitherExtensions.cs new file mode 100644 index 000000000..f83a9e9e8 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/DitherExtensions.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.Primitives; + +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 Bayer4x4 ordered dithering. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither(this IImageProcessingContext source) => + Dither(source, KnownDitherers.BayerDither4x4); + + /// + /// 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, IOrderedDither dither) => + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither)); + + /// + /// 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, + IOrderedDither dither, + ReadOnlyMemory palette) => + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette)); + + /// + /// 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, + IOrderedDither dither, + Rectangle rectangle) => + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), 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, + IOrderedDither dither, + ReadOnlyMemory palette, + Rectangle rectangle) => + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/EntropyCropExtensions.cs b/src/ImageSharp/Processing/Extensions/EntropyCropExtensions.cs new file mode 100644 index 000000000..de5296d83 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/EntropyCropExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// 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/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/FilterExtensions.cs similarity index 51% rename from src/ImageSharp/Processing/FilterExtensions.cs rename to src/ImageSharp/Processing/Extensions/FilterExtensions.cs index bfae4ae65..5a66502ce 100644 --- a/src/ImageSharp/Processing/FilterExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/FilterExtensions.cs @@ -1,41 +1,38 @@ // 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.Processors.Filters; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of composable filters to the type. + /// Defines extensions that allow the application of composable filters to an + /// using Mutate/Clone. /// 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, Matrix4x4 matrix) - where TPixel : struct, IPixel - => source.ApplyProcessor(new FilterProcessor(matrix)); + /// The to allow chaining of operations. + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix) + => 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, Matrix4x4 matrix, Rectangle rectangle) - where TPixel : struct, IPixel - => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); + /// The to allow chaining of operations. + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) + => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/FlipExtensions.cs b/src/ImageSharp/Processing/Extensions/FlipExtensions.cs similarity index 50% rename from src/ImageSharp/Processing/FlipExtensions.cs rename to src/ImageSharp/Processing/Extensions/FlipExtensions.cs index dfbff7e4d..f6b3c0c37 100644 --- a/src/ImageSharp/Processing/FlipExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/FlipExtensions.cs @@ -1,25 +1,23 @@ // 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. + /// 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 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)); + /// 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/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs new file mode 100644 index 000000000..e527a14b7 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs @@ -0,0 +1,45 @@ +// 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 +{ + /// + /// 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/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/GaussianSharpenExtensions.cs new file mode 100644 index 000000000..79f4a0cc3 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/GaussianSharpenExtensions.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +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/GlowExtensions.cs b/src/ImageSharp/Processing/Extensions/GlowExtensions.cs new file mode 100644 index 000000000..39734882b --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/GlowExtensions.cs @@ -0,0 +1,175 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; + +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, GraphicsOptions.Default); + + /// + /// 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, GraphicsOptions.Default, 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, GraphicsOptions.Default, 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(GraphicsOptions.Default, 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(GraphicsOptions.Default, 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(color, radius, options), 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(color, radius, options)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/GrayscaleExtensions.cs b/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs similarity index 55% rename from src/ImageSharp/Processing/GrayscaleExtensions.cs rename to src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs index 9ab664056..a87341025 100644 --- a/src/ImageSharp/Processing/GrayscaleExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs @@ -9,56 +9,49 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of grayscale toning to the type. + /// 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 pixel format. /// The image this method extends. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source) - where TPixel : struct, IPixel + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source) => 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 + /// 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 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 + /// 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 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 + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount) { - IImageProcessor processor = mode == GrayscaleMode.Bt709 - ? (IImageProcessor)new GrayscaleBt709Processor(amount) - : new GrayscaleBt601Processor(1F); + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor(amount) + : new GrayscaleBt601Processor(1F); source.ApplyProcessor(processor); return source; @@ -67,61 +60,53 @@ namespace SixLabors.ImageSharp.Processing /// /// 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 + /// 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 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 + /// 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 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 + /// 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 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 + /// 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); + 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/HistogramEqualizationExtensions.cs b/src/ImageSharp/Processing/Extensions/HistogramEqualizationExtensions.cs new file mode 100644 index 000000000..72962a3f9 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/HistogramEqualizationExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// 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/HueExtensions.cs b/src/ImageSharp/Processing/Extensions/HueExtensions.cs similarity index 53% rename from src/ImageSharp/Processing/HueExtensions.cs rename to src/ImageSharp/Processing/Extensions/HueExtensions.cs index 246d4bf2b..3c1239da6 100644 --- a/src/ImageSharp/Processing/HueExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/HueExtensions.cs @@ -8,33 +8,30 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the alteration of the hue component to the type. + /// 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 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)); + /// 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 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); + /// 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/InvertExtensions.cs b/src/ImageSharp/Processing/Extensions/InvertExtensions.cs new file mode 100644 index 000000000..c45f24c2e --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/InvertExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +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/KodachromeExtensions.cs b/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs similarity index 51% rename from src/ImageSharp/Processing/KodachromeExtensions.cs rename to src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs index e438b131e..810094a18 100644 --- a/src/ImageSharp/Processing/KodachromeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs @@ -8,31 +8,28 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the recreation of an old Kodachrome camera effect to the type. + /// 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 pixel format. /// The image this method extends. - /// The . - public static IImageProcessingContext Kodachrome(this IImageProcessingContext source) - where TPixel : struct, IPixel - => source.ApplyProcessor(new KodachromeProcessor()); + /// 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 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); + /// 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/LomographExtensions.cs b/src/ImageSharp/Processing/Extensions/LomographExtensions.cs similarity index 51% rename from src/ImageSharp/Processing/LomographExtensions.cs rename to src/ImageSharp/Processing/Extensions/LomographExtensions.cs index 7dff16402..dd7ab21ec 100644 --- a/src/ImageSharp/Processing/LomographExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/LomographExtensions.cs @@ -8,31 +8,28 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the recreation of an old Lomograph camera effect to the type. + /// 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 pixel format. /// The image this method extends. - /// The . - public static IImageProcessingContext Lomograph(this IImageProcessingContext source) - where TPixel : struct, IPixel - => source.ApplyProcessor(new LomographProcessor()); + /// The to allow chaining of operations. + public static IImageProcessingContext Lomograph(this IImageProcessingContext source) + => 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); + /// The to allow chaining of operations. + public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new LomographProcessor(), rectangle); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/OilPaintExtensions.cs b/src/ImageSharp/Processing/Extensions/OilPaintExtensions.cs similarity index 56% rename from src/ImageSharp/Processing/OilPaintExtensions.cs rename to src/ImageSharp/Processing/Extensions/OilPaintExtensions.cs index b6fa4149a..1aa98c8c1 100644 --- a/src/ImageSharp/Processing/OilPaintExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/OilPaintExtensions.cs @@ -1,14 +1,14 @@ // 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. + /// Defines oil painting effect extensions applicable on an + /// using Mutate/Clone. /// public static class OilPaintExtensions { @@ -16,52 +16,48 @@ namespace SixLabors.ImageSharp.Processing /// 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); + /// 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 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); + /// 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 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)); + /// 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 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); + /// 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/OpacityExtensions.cs b/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs similarity index 53% rename from src/ImageSharp/Processing/OpacityExtensions.cs rename to src/ImageSharp/Processing/Extensions/OpacityExtensions.cs index fc3fd331d..ecf6ce783 100644 --- a/src/ImageSharp/Processing/OpacityExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs @@ -8,33 +8,30 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the alteration of the opacity component to the type. + /// 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 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)); + /// 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 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); + /// 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/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/PadExtensions.cs new file mode 100644 index 000000000..270380084 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/PadExtensions.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.Primitives; + +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/PixelateExtensions.cs b/src/ImageSharp/Processing/Extensions/PixelateExtensions.cs new file mode 100644 index 000000000..bf40af91a --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/PixelateExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Effects; +using SixLabors.Primitives; + +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/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs similarity index 51% rename from src/ImageSharp/Processing/PolaroidExtensions.cs rename to src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs index 5d4beee22..eace46357 100644 --- a/src/ImageSharp/Processing/PolaroidExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs @@ -8,31 +8,28 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the recreation of an old Polaroid camera effect to the type. + /// 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 pixel format. /// The image this method extends. - /// The . - public static IImageProcessingContext Polaroid(this IImageProcessingContext source) - where TPixel : struct, IPixel - => source.ApplyProcessor(new PolaroidProcessor()); + /// The to allow chaining of operations. + public static IImageProcessingContext Polaroid(this IImageProcessingContext source) + => 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); + /// The to allow chaining of operations. + public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new PolaroidProcessor(), rectangle); } } diff --git a/src/ImageSharp/Processing/ProcessingExtensions.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs similarity index 59% rename from src/ImageSharp/Processing/ProcessingExtensions.cs rename to src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs index 9d06c61d4..2304a44c3 100644 --- a/src/ImageSharp/Processing/ProcessingExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; @@ -14,16 +15,15 @@ namespace SixLabors.ImageSharp.Processing 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. + /// 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 to allow chaining of operations. - public static IImageProcessingContext Apply(this IImageProcessingContext source, Action> operation) - where TPixel : struct, IPixel => source.ApplyProcessor(new DelegateProcessor(operation)); + public static void Mutate(this Image source, Action operation) + { + ProcessingVisitor visitor = new ProcessingVisitor(operation, true); + source.AcceptVisitor(visitor); + } /// /// Mutates the source image by applying the image operation to it. @@ -31,15 +31,15 @@ namespace SixLabors.ImageSharp.Processing /// The pixel format. /// The image to mutate. /// The operation to perform on the source. - public static void Mutate(this Image source, Action> operation) + 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); + IInternalImageProcessingContext operationsRunner = source.GetConfiguration().ImageOperationsProvider + .CreateImageProcessingContext(source, true); operation(operationsRunner); - operationsRunner.Apply(); } /// @@ -48,15 +48,28 @@ namespace SixLabors.ImageSharp.Processing /// The pixel format. /// The image to mutate. /// The operations to perform on the source. - public static void Mutate(this Image source, params IImageProcessor[] operations) + 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); + 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 image to clone. + /// The operation to perform on the clone. + /// The new . + public static Image Clone(this Image source, Action operation) + { + ProcessingVisitor visitor = new ProcessingVisitor(operation, false); + source.AcceptVisitor(visitor); + return visitor.ResultImage; } /// @@ -66,15 +79,16 @@ namespace SixLabors.ImageSharp.Processing /// The image to clone. /// The operation to perform on the clone. /// The new - public static Image Clone(this Image source, Action> operation) + 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); + IInternalImageProcessingContext operationsRunner = source.GetConfiguration().ImageOperationsProvider + .CreateImageProcessingContext(source, false); operation(operationsRunner); - return operationsRunner.Apply(); + return operationsRunner.GetResultImage(); } /// @@ -84,33 +98,58 @@ namespace SixLabors.ImageSharp.Processing /// The image to clone. /// The operations to perform on the clone. /// The new - public static Image Clone(this Image source, params IImageProcessor[] operations) + 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); + IInternalImageProcessingContext operationsRunner = source.GetConfiguration().ImageOperationsProvider + .CreateImageProcessingContext(source, false); operationsRunner.ApplyProcessors(operations); - return operationsRunner.Apply(); + return operationsRunner.GetResultImage(); } /// /// 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 + public static IImageProcessingContext ApplyProcessors( + this IImageProcessingContext source, + params IImageProcessor[] operations) { - foreach (IImageProcessor p in operations) + foreach (IImageProcessor p in operations) { source = source.ApplyProcessor(p); } return source; } + + private class ProcessingVisitor : IImageVisitor + { + private readonly Action operation; + + private readonly bool mutate; + + public ProcessingVisitor(Action operation, bool mutate) + { + this.operation = operation; + this.mutate = mutate; + } + + public Image ResultImage { get; private set; } + + public void Visit(Image image) + where TPixel : struct, IPixel + { + IInternalImageProcessingContext operationsRunner = image.GetConfiguration() + .ImageOperationsProvider.CreateImageProcessingContext(image, this.mutate); + this.operation(operationsRunner); + this.ResultImage = operationsRunner.GetResultImage(); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/QuantizeExtensions.cs new file mode 100644 index 000000000..3410ee6be --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/QuantizeExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// 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)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ResizeExtensions.cs b/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs similarity index 67% rename from src/ImageSharp/Processing/ResizeExtensions.cs rename to src/ImageSharp/Processing/Extensions/ResizeExtensions.cs index 7b6c14d7d..4578b4353 100644 --- a/src/ImageSharp/Processing/ResizeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs @@ -8,122 +8,106 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of resize operations to the type. + /// Defines extensions that allow the application of resize operations on an + /// using Mutate/Clone. /// public static class ResizeExtensions { /// /// Resizes an image in accordance with the given . /// - /// The pixel format. /// The image to resize. /// The resize options. - /// The + /// 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) - where TPixel : struct, IPixel - => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); + public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) + => 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 + /// 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) - where TPixel : struct, IPixel + 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 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 + /// 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) - where TPixel : struct, IPixel + 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 pixel format. /// The image to resize. /// The target image width. /// The target image height. - /// The + /// 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) - where TPixel : struct, IPixel + 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 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 + /// 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) - where TPixel : struct, IPixel + 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 pixel format. /// The image to resize. /// The target image width. /// The target image height. /// The to perform the resampling. - /// The + /// 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) - where TPixel : struct, IPixel + 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 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 + /// 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) - where TPixel : struct, IPixel + 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 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 + /// 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) - where TPixel : struct, IPixel + 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 pixel format. /// The image to resize. /// The target image width. /// The target image height. @@ -135,23 +119,21 @@ namespace SixLabors.ImageSharp.Processing /// 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 + /// 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, + 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); + => 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. @@ -160,16 +142,15 @@ namespace SixLabors.ImageSharp.Processing /// 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 + /// 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, + 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)); + => source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/RotateExtensions.cs b/src/ImageSharp/Processing/Extensions/RotateExtensions.cs new file mode 100644 index 000000000..395462ae3 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/RotateExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// 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/RotateFlipExtensions.cs b/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs similarity index 60% rename from src/ImageSharp/Processing/RotateFlipExtensions.cs rename to src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs index 27ddc8de9..4d5d90c30 100644 --- a/src/ImageSharp/Processing/RotateFlipExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs @@ -6,20 +6,19 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of rotate-flip operations to the type. + /// 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 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 + /// 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/SaturateExtensions.cs b/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs similarity index 63% rename from src/ImageSharp/Processing/SaturateExtensions.cs rename to src/ImageSharp/Processing/Extensions/SaturateExtensions.cs index ba45ae12c..e9ba820b6 100644 --- a/src/ImageSharp/Processing/SaturateExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs @@ -8,7 +8,8 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the alteration of the saturation component to the type. + /// Defines extensions that allow the alteration of the saturation component of an + /// using Mutate/Clone. /// public static class SaturateExtensions { @@ -19,13 +20,11 @@ namespace SixLabors.ImageSharp.Processing /// 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)); + /// 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. @@ -34,15 +33,13 @@ namespace SixLabors.ImageSharp.Processing /// 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); + /// 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/SepiaExtensions.cs b/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs similarity index 52% rename from src/ImageSharp/Processing/SepiaExtensions.cs rename to src/ImageSharp/Processing/Extensions/SepiaExtensions.cs index 08676ee62..5ee5151fa 100644 --- a/src/ImageSharp/Processing/SepiaExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs @@ -8,56 +8,49 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of sepia toning to the type. + /// 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 pixel format. /// The image this method extends. - /// The . - public static IImageProcessingContext Sepia(this IImageProcessingContext source) - where TPixel : struct, IPixel + /// 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 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)); + /// 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 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 + /// 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 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); + /// 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/SkewExtensions.cs b/src/ImageSharp/Processing/Extensions/SkewExtensions.cs similarity index 53% rename from src/ImageSharp/Processing/SkewExtensions.cs rename to src/ImageSharp/Processing/Extensions/SkewExtensions.cs index 07e3c6087..77a46af0d 100644 --- a/src/ImageSharp/Processing/SkewExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/SkewExtensions.cs @@ -1,39 +1,40 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing { /// - /// Adds extensions that allow the application of skew operations to the type. + /// 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 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); + /// 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 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())); + /// 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/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/TransformExtensions.cs similarity index 59% rename from src/ImageSharp/Processing/TransformExtensions.cs rename to src/ImageSharp/Processing/Extensions/TransformExtensions.cs index db14b6baf..7fffb71d2 100644 --- a/src/ImageSharp/Processing/TransformExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/TransformExtensions.cs @@ -3,60 +3,54 @@ 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. + /// 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 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); + 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 pixel format. - /// The . + /// The . /// The affine transform builder. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The to allow chaining of operations. + 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); + 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 pixel format. - /// The . + /// The . /// The source rectangle /// The affine transform builder. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The to allow chaining of operations. + 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); @@ -66,69 +60,61 @@ namespace SixLabors.ImageSharp.Processing /// /// Performs an affine transform of an image using the specified sampling algorithm. /// - /// The pixel format. - /// The . + /// 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, + /// The to allow chaining of operations. + 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), + 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); + /// 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 pixel format. - /// The . + /// The . /// The projective transform builder. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The to allow chaining of operations. + 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); + 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 pixel format. - /// The . + /// The . /// The source rectangle /// The projective transform builder. /// The to perform the resampling. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, + /// The to allow chaining of operations. + 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); @@ -138,23 +124,21 @@ namespace SixLabors.ImageSharp.Processing /// /// Performs a projective transform of an image using the specified sampling algorithm. /// - /// The pixel format. - /// The . + /// 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, + /// The to allow chaining of operations. + 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), + new ProjectiveTransformProcessor(transform, sampler, targetDimensions), sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs b/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs new file mode 100644 index 000000000..74a59d3e1 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs @@ -0,0 +1,179 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; + +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, GraphicsOptions.Default); + + /// + /// 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, GraphicsOptions.Default, 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, GraphicsOptions.Default, 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, GraphicsOptions.Default, 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(GraphicsOptions.Default, 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(color, radiusX, radiusY, options), rectangle); + + private static IImageProcessingContext VignetteInternal( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radiusX, + ValueSize radiusY) => + source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/GaussianBlurExtensions.cs deleted file mode 100644 index 165c4ce1a..000000000 --- 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 675bbc142..000000000 --- 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 8b6e8ffc2..000000000 --- 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/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs deleted file mode 100644 index 8dabfcc05..000000000 --- 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/IImageProcessingContext.cs b/src/ImageSharp/Processing/IImageProcessingContext.cs new file mode 100644 index 000000000..509b1313d --- /dev/null +++ b/src/ImageSharp/Processing/IImageProcessingContext.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// A pixel-agnostic interface to queue up image operations to apply to an image. + /// + public interface IImageProcessingContext + { + /// + /// 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); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs index 1ec2d191f..948e70b44 100644 --- a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs +++ b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing public IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) where TPixel : struct, IPixel { - return new DefaultInternalImageProcessorContext(source, mutate); + return new DefaultImageProcessorContext(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 4897cc58b..000000000 --- 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.cs b/src/ImageSharp/Processing/IInternalImageProcessingContext.cs new file mode 100644 index 000000000..55303b1ef --- /dev/null +++ b/src/ImageSharp/Processing/IInternalImageProcessingContext.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// 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 : struct, 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(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/InvertExtensions.cs b/src/ImageSharp/Processing/InvertExtensions.cs deleted file mode 100644 index 9e031bc95..000000000 --- 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/KnownFilterMatrices.cs b/src/ImageSharp/Processing/KnownFilterMatrices.cs index cf0d19ff8..9c725d027 100644 --- a/src/ImageSharp/Processing/KnownFilterMatrices.cs +++ b/src/ImageSharp/Processing/KnownFilterMatrices.cs @@ -2,19 +2,28 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; - +using SixLabors.ImageSharp.Primitives; + +// Many of these matrices are tranlated from Chromium project where +// SkScalar[] is memory-mapped to a row-major matrix. +// The following translates to our column-major form: +// +// | 0| 1| 2| 3| 4| |0|5|10|15| |M11|M12|M13|M14| +// | 5| 6| 7| 8| 9| |1|6|11|16| |M21|M22|M23|M24| +// |10|11|12|13|14| = |2|7|12|17| = |M31|M32|M33|M34| +// |15|16|17|18|19| |3|8|13|18| |M41|M42|M43|M44| +// |4|9|14|19| |M51|M52|M53|M54| namespace SixLabors.ImageSharp.Processing { /// - /// A collection of known values for composing filters + /// A collection of known values for composing filters /// public static class KnownFilterMatrices { /// /// Gets a filter recreating Achromatomaly (Color desensitivity) color blindness /// - public static Matrix4x4 AchromatomalyFilter { get; } = new Matrix4x4 + public static ColorMatrix AchromatomalyFilter { get; } = new ColorMatrix { M11 = .618F, M12 = .163F, @@ -31,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets a filter recreating Achromatopsia (Monochrome) color blindness. /// - public static Matrix4x4 AchromatopsiaFilter { get; } = new Matrix4x4 + public static ColorMatrix AchromatopsiaFilter { get; } = new ColorMatrix { M11 = .299F, M12 = .299F, @@ -42,97 +51,97 @@ namespace SixLabors.ImageSharp.Processing M31 = .114F, M32 = .114F, M33 = .114F, - M44 = 1 + M44 = 1F }; /// /// Gets a filter recreating Deuteranomaly (Green-Weak) color blindness. /// - public static Matrix4x4 DeuteranomalyFilter { get; } = new Matrix4x4 + public static ColorMatrix DeuteranomalyFilter { get; } = new ColorMatrix { - M11 = 0.8F, - M12 = 0.258F, - M21 = 0.2F, - M22 = 0.742F, - M23 = 0.142F, - M33 = 0.858F, - M44 = 1 + M11 = .8F, + M12 = .258F, + M21 = .2F, + M22 = .742F, + M23 = .142F, + M33 = .858F, + M44 = 1F }; /// /// Gets a filter recreating Deuteranopia (Green-Blind) color blindness. /// - public static Matrix4x4 DeuteranopiaFilter { get; } = new Matrix4x4 + public static ColorMatrix DeuteranopiaFilter { get; } = new ColorMatrix { - M11 = 0.625F, - M12 = 0.7F, - M21 = 0.375F, - M22 = 0.3F, - M23 = 0.3F, - M33 = 0.7F, - M44 = 1 + M11 = .625F, + M12 = .7F, + M21 = .375F, + M22 = .3F, + M23 = .3F, + M33 = .7F, + M44 = 1F }; /// /// Gets a filter recreating Protanomaly (Red-Weak) color blindness. /// - public static Matrix4x4 ProtanomalyFilter { get; } = new Matrix4x4 + public static ColorMatrix ProtanomalyFilter { get; } = new ColorMatrix { - M11 = 0.817F, - M12 = 0.333F, - M21 = 0.183F, - M22 = 0.667F, - M23 = 0.125F, - M33 = 0.875F, - M44 = 1 + M11 = .817F, + M12 = .333F, + M21 = .183F, + M22 = .667F, + M23 = .125F, + M33 = .875F, + M44 = 1F }; /// /// Gets a filter recreating Protanopia (Red-Blind) color blindness. /// - public static Matrix4x4 ProtanopiaFilter { get; } = new Matrix4x4 + public static ColorMatrix ProtanopiaFilter { get; } = new ColorMatrix { - M11 = 0.567F, - M12 = 0.558F, - M21 = 0.433F, - M22 = 0.442F, - M23 = 0.242F, - M33 = 0.758F, - M44 = 1 + M11 = .567F, + M12 = .558F, + M21 = .433F, + M22 = .442F, + M23 = .242F, + M33 = .758F, + M44 = 1F }; /// /// Gets a filter recreating Tritanomaly (Blue-Weak) color blindness. /// - public static Matrix4x4 TritanomalyFilter { get; } = new Matrix4x4 + public static ColorMatrix TritanomalyFilter { get; } = new ColorMatrix { - M11 = 0.967F, - M21 = 0.33F, - M22 = 0.733F, - M23 = 0.183F, - M32 = 0.267F, - M33 = 0.817F, - M44 = 1 + M11 = .967F, + M21 = .33F, + M22 = .733F, + M23 = .183F, + M32 = .267F, + M33 = .817F, + M44 = 1F }; /// /// Gets a filter recreating Tritanopia (Blue-Blind) color blindness. /// - public static Matrix4x4 TritanopiaFilter { get; } = new Matrix4x4 + public static ColorMatrix TritanopiaFilter { get; } = new ColorMatrix { - M11 = 0.95F, - M21 = 0.05F, - M22 = 0.433F, - M23 = 0.475F, - M32 = 0.567F, - M33 = 0.525F, - M44 = 1 + M11 = .95F, + M21 = .05F, + M22 = .433F, + M23 = .475F, + M32 = .567F, + M33 = .525F, + M44 = 1F }; /// /// Gets an approximated black and white filter /// - public static Matrix4x4 BlackWhiteFilter { get; } = new Matrix4x4() + public static ColorMatrix BlackWhiteFilter { get; } = new ColorMatrix() { M11 = 1.5F, M12 = 1.5F, @@ -143,24 +152,24 @@ namespace SixLabors.ImageSharp.Processing M31 = 1.5F, M32 = 1.5F, M33 = 1.5F, - M41 = -1F, - M42 = -1F, - M43 = -1F, - M44 = 1 + M44 = 1F, + M51 = -1F, + M52 = -1F, + M53 = -1F, }; /// /// Gets a filter recreating an old Kodachrome camera effect. /// - public static Matrix4x4 KodachromeFilter { get; } = new Matrix4x4 + public static ColorMatrix KodachromeFilter { get; } = new ColorMatrix { - M11 = 0.7297023F, - M22 = 0.6109577F, - M33 = 0.597218F, - M41 = 0.105F, - M42 = 0.145F, - M43 = 0.155F, - M44 = 1 + M11 = .7297023F, + M22 = .6109577F, + M33 = .597218F, + M44 = 1F, + M51 = .105F, + M52 = .145F, + M53 = .155F, } * CreateSaturateFilter(1.2F) * CreateContrastFilter(1.35F); @@ -168,15 +177,15 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets a filter recreating an old Lomograph camera effect. /// - public static Matrix4x4 LomographFilter { get; } = new Matrix4x4 + public static ColorMatrix LomographFilter { get; } = new ColorMatrix { M11 = 1.5F, M22 = 1.45F, M33 = 1.16F, - M41 = -.1F, - M42 = -.02F, - M43 = -.07F, - M44 = 1 + M44 = 1F, + M51 = -.1F, + M52 = -.02F, + M53 = -.07F, } * CreateSaturateFilter(1.1F) * CreateContrastFilter(1.33F); @@ -184,21 +193,21 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets a filter recreating an old Polaroid camera effect. /// - public static Matrix4x4 PolaroidFilter { get; } = new Matrix4x4 + public static ColorMatrix PolaroidFilter { get; } = new ColorMatrix { M11 = 1.538F, - M12 = -0.062F, - M13 = -0.262F, - M21 = -0.022F, + M12 = -.062F, + M13 = -.262F, + M21 = -.022F, M22 = 1.578F, - M23 = -0.022F, + M23 = -.022F, M31 = .216F, M32 = -.16F, M33 = 1.5831F, - M41 = 0.02F, - M42 = -0.05F, - M43 = -0.05F, - M44 = 1 + M44 = 1F, + M51 = .02F, + M52 = -.05F, + M53 = -.05F }; /// @@ -209,18 +218,18 @@ namespace SixLabors.ImageSharp.Processing /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. /// /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static Matrix4x4 CreateBrightnessFilter(float amount) + /// The + public static ColorMatrix CreateBrightnessFilter(float amount) { Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new Matrix4x4 + return new ColorMatrix { M11 = amount, M22 = amount, M33 = amount, - M44 = 1 + M44 = 1F }; } @@ -232,23 +241,23 @@ namespace SixLabors.ImageSharp.Processing /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. /// /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static Matrix4x4 CreateContrastFilter(float amount) + /// The + public static ColorMatrix CreateContrastFilter(float amount) { Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc float contrast = (-.5F * amount) + .5F; - return new Matrix4x4 + return new ColorMatrix { M11 = amount, M22 = amount, M33 = amount, - M41 = contrast, - M42 = contrast, - M43 = contrast, - M44 = 1 + M44 = 1F, + M51 = contrast, + M52 = contrast, + M53 = contrast }; } @@ -257,26 +266,27 @@ namespace SixLabors.ImageSharp.Processing /// /// /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static Matrix4x4 CreateGrayscaleBt601Filter(float amount) + /// The + public static ColorMatrix CreateGrayscaleBt601Filter(float amount) { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); amount = 1F - amount; - // https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new Matrix4x4 - { - M11 = .299F + (.701F * amount), - M12 = .299F - (.299F * amount), - M13 = .299F - (.299F * amount), - M21 = .587F - (.587F * amount), - M22 = .587F + (.413F * amount), - M23 = .587F - (.587F * amount), - M31 = .114F - (.114F * amount), - M32 = .114F - (.114F * amount), - M33 = .114F + (.886F * amount), - M44 = 1 - }; + ColorMatrix m = default; + m.M11 = .299F + (.701F * amount); + m.M21 = .587F - (.587F * amount); + m.M31 = 1F - (m.M11 + m.M21); + + m.M12 = .299F - (.299F * amount); + m.M22 = .587F + (.2848F * amount); + m.M32 = 1F - (m.M12 + m.M22); + + m.M13 = .299F - (.299F * amount); + m.M23 = .587F - (.587F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; + + return m; } /// @@ -284,34 +294,36 @@ namespace SixLabors.ImageSharp.Processing /// /// /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static Matrix4x4 CreateGrayscaleBt709Filter(float amount) + /// The + public static ColorMatrix CreateGrayscaleBt709Filter(float amount) { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); amount = 1F - amount; // https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new Matrix4x4 - { - M11 = .2126F + (.7874F * amount), - M12 = .2126F - (.2126F * amount), - M13 = .2126F - (.2126F * amount), - M21 = .7152F - (.7152F * amount), - M22 = .7152F + (.2848F * amount), - M23 = .7152F - (.7152F * amount), - M31 = .0722F - (.0722F * amount), - M32 = .0722F - (.0722F * amount), - M33 = .0722F + (.9278F * amount), - M44 = 1 - }; + ColorMatrix m = default; + m.M11 = .2126F + (.7874F * amount); + m.M21 = .7152F - (.7152F * amount); + m.M31 = 1F - (m.M11 + m.M21); + + m.M12 = .2126F - (.2126F * amount); + m.M22 = .7152F + (.2848F * amount); + m.M32 = 1F - (m.M12 + m.M22); + + m.M13 = .2126F - (.2126F * amount); + m.M23 = .7152F - (.7152F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; + + return m; } /// /// Create a hue filter matrix using the given angle in degrees. /// /// The angle of rotation in degrees. - /// The - public static Matrix4x4 CreateHueFilter(float degrees) + /// The + public static ColorMatrix CreateHueFilter(float degrees) { // Wrap the angle round at 360. degrees %= 360; @@ -329,18 +341,20 @@ namespace SixLabors.ImageSharp.Processing // The matrix is set up to preserve the luminance of the image. // See http://graficaobscura.com/matrix/index.html // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx - return new Matrix4x4 + return new ColorMatrix { M11 = .213F + (cosRadian * .787F) - (sinRadian * .213F), - M12 = .213F - (cosRadian * .213F) - (sinRadian * 0.143F), - M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F), M21 = .715F - (cosRadian * .715F) - (sinRadian * .715F), - M22 = .715F + (cosRadian * .285F) + (sinRadian * 0.140F), - M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F), M31 = .072F - (cosRadian * .072F) + (sinRadian * .928F), - M32 = .072F - (cosRadian * .072F) - (sinRadian * 0.283F), + + M12 = .213F - (cosRadian * .213F) + (sinRadian * .143F), + M22 = .715F + (cosRadian * .285F) + (sinRadian * .140F), + M32 = .072F - (cosRadian * .072F) - (sinRadian * .283F), + + M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F), + M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F), M33 = .072F + (cosRadian * .928F) + (sinRadian * .072F), - M44 = 1 + M44 = 1F }; } @@ -348,23 +362,23 @@ namespace SixLabors.ImageSharp.Processing /// Create an invert filter matrix using the given amount. /// /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static Matrix4x4 CreateInvertFilter(float amount) + /// The + public static ColorMatrix CreateInvertFilter(float amount) { Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc float invert = 1F - (2F * amount); - return new Matrix4x4 + return new ColorMatrix { M11 = invert, M22 = invert, M33 = invert, - M41 = amount, - M42 = amount, - M43 = amount, - M44 = 1 + M44 = 1F, + M51 = amount, + M52 = amount, + M53 = amount, }; } @@ -372,17 +386,17 @@ namespace SixLabors.ImageSharp.Processing /// Create an opacity filter matrix using the given amount. /// /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static Matrix4x4 CreateOpacityFilter(float amount) + /// The + public static ColorMatrix CreateOpacityFilter(float amount) { Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new Matrix4x4 + return new ColorMatrix { - M11 = 1, - M22 = 1, - M33 = 1, + M11 = 1F, + M22 = 1F, + M33 = 1F, M44 = amount }; } @@ -395,25 +409,27 @@ namespace SixLabors.ImageSharp.Processing /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results /// /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static Matrix4x4 CreateSaturateFilter(float amount) + /// The + public static ColorMatrix CreateSaturateFilter(float amount) { Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new Matrix4x4 - { - M11 = .213F + (.787F * amount), - M12 = .213F - (.213F * amount), - M13 = .213F - (.213F * amount), - M21 = .715F - (.715F * amount), - M22 = .715F + (.285F * amount), - M23 = .715F - (.715F * amount), - M31 = 1F - ((.213F + (.787F * amount)) + (.715F - (.715F * amount))), - M32 = 1F - ((.213F - (.213F * amount)) + (.715F + (.285F * amount))), - M33 = 1F - ((.213F - (.213F * amount)) + (.715F - (.715F * amount))), - M44 = 1 - }; + ColorMatrix m = default; + m.M11 = .213F + (.787F * amount); + m.M21 = .715F - (.715F * amount); + m.M31 = 1F - (m.M11 + m.M21); + + m.M12 = .213F - (.213F * amount); + m.M22 = .715F + (.285F * amount); + m.M32 = 1F - (m.M12 + m.M22); + + m.M13 = .213F - (.213F * amount); + m.M23 = .715F - (.715F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; + + return m; } /// @@ -421,25 +437,27 @@ namespace SixLabors.ImageSharp.Processing /// The formula used matches the svg specification. /// /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static Matrix4x4 CreateSepiaFilter(float amount) + /// The + public static ColorMatrix CreateSepiaFilter(float amount) { Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); amount = 1F - amount; // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new Matrix4x4 + return new ColorMatrix { M11 = .393F + (.607F * amount), - M12 = .349F - (.349F * amount), - M13 = .272F - (.272F * amount), M21 = .769F - (.769F * amount), - M22 = .686F + (.314F * amount), - M23 = .534F - (.534F * amount), M31 = .189F - (.189F * amount), + + M12 = .349F - (.349F * amount), + M22 = .686F + (.314F * amount), M32 = .168F - (.168F * amount), + + M13 = .272F - (.272F * amount), + M23 = .534F - (.534F * amount), M33 = .131F + (.869F * amount), - M44 = 1 + M44 = 1F }; } } diff --git a/src/ImageSharp/Processing/PadExtensions.cs b/src/ImageSharp/Processing/PadExtensions.cs deleted file mode 100644 index f73033968..000000000 --- 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/PixelateExtensions.cs b/src/ImageSharp/Processing/PixelateExtensions.cs deleted file mode 100644 index 4507f6392..000000000 --- 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/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs index 32cc2f434..812983664 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs @@ -2,22 +2,19 @@ // 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 + public class BinaryErrorDiffusionProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The error diffuser public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser) @@ -26,23 +23,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization } /// - /// Initializes a new instance of the class. + /// 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) + : this(diffuser, threshold, Color.White, Color.Black) { } /// - /// Initializes a new instance of the class. + /// 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) + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold, Color upperColor, Color lowerColor) { Guard.NotNull(diffuser, nameof(diffuser)); Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); @@ -66,57 +63,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// /// Gets the color to use for pixels that are above the threshold. /// - public TPixel UpperColor { get; } + public Color UpperColor { get; } /// /// Gets the color to use for pixels that fall below the threshold. /// - public TPixel LowerColor { get; } + public Color LowerColor { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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); - } - } + return new BinaryErrorDiffusionProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs new file mode 100644 index 000000000..e3828aeb6 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs @@ -0,0 +1,77 @@ +// 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 + { + private readonly BinaryErrorDiffusionProcessor definition; + + public BinaryErrorDiffusionProcessor(BinaryErrorDiffusionProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + TPixel upperColor = this.definition.UpperColor.ToPixel(); + TPixel lowerColor = this.definition.LowerColor.ToPixel(); + IErrorDiffuser diffuser = this.definition.Diffuser; + + byte threshold = (byte)MathF.Round(this.definition.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 ? upperColor : lowerColor; + 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 index cfdaf107c..6cf1a9526 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs @@ -2,36 +2,33 @@ // 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. + /// Defines a binary threshold filtering using ordered dithering. /// - /// The pixel format. - internal class BinaryOrderedDitherProcessor : ImageProcessor - where TPixel : struct, IPixel + public class BinaryOrderedDitherProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The ordered ditherer. public BinaryOrderedDitherProcessor(IOrderedDither dither) - : this(dither, NamedColors.White, NamedColors.Black) + : this(dither, Color.White, Color.Black) { } /// - /// Initializes a new instance of the class. + /// 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) + public BinaryOrderedDitherProcessor(IOrderedDither dither, Color upperColor, Color lowerColor) { this.Dither = dither ?? throw new ArgumentNullException(nameof(dither)); this.UpperColor = upperColor; @@ -46,55 +43,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// /// Gets the color to use for pixels that are above the threshold. /// - public TPixel UpperColor { get; } + public Color UpperColor { get; } /// /// Gets the color to use for pixels that fall below the threshold. /// - public TPixel LowerColor { get; } + public Color LowerColor { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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); - } - } + return new BinaryOrderedDitherProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs new file mode 100644 index 000000000..b3d174dfb --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs @@ -0,0 +1,75 @@ +// 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 + { + private readonly BinaryOrderedDitherProcessor definition; + + public BinaryOrderedDitherProcessor(BinaryOrderedDitherProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + IOrderedDither dither = this.definition.Dither; + TPixel upperColor = this.definition.UpperColor.ToPixel(); + TPixel lowerColor = this.definition.LowerColor.ToPixel(); + + 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; + } + + dither.Dither(source, sourcePixel, upperColor, 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 67dcfc7f1..83701aa8a 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -3,36 +3,31 @@ 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 +41,20 @@ 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; } + public Color LowerColor { get; } - /// - protected override void OnFrameApply( - ImageFrame source, - Rectangle sourceRectangle, - Configuration configuration) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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); - - 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; - } - } - }); + return new BinaryThresholdProcessor(this); } } } \ 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 000000000..0d90d3647 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// 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 + { + private readonly BinaryThresholdProcessor definition; + + public BinaryThresholdProcessor(BinaryThresholdProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F); + TPixel upper = this.definition.UpperColor.ToPixel(); + TPixel lower = this.definition.LowerColor.ToPixel(); + + 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); + + 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; + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 644d6c9e1..4e56e75d3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -3,66 +3,48 @@ 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; } + public BoxBlurProcessor() + : this(DefaultRadius) + { + } /// - /// Gets the vertical gradient operator. + /// Gets the Radius. /// - 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 Box kernel. - /// - /// The - private DenseMatrix CreateBoxKernel() + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - int size = this.kernelSize; - var kernel = new DenseMatrix(size, 1); - - kernel.Fill(1F / size); - - return kernel; + return new BoxBlurProcessor(this); } } } \ 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 000000000..f4252e840 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies box blur processing to the image. + /// + /// The pixel format. + internal class BoxBlurProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly BoxBlurProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + /// The defining the processor parameters. + public BoxBlurProcessor(BoxBlurProcessor definition) + { + this.definition = definition; + 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, + Rectangle sourceRectangle, + Configuration configuration) => + new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply( + source, + sourceRectangle, + configuration); + + /// + /// 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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs similarity index 58% rename from src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs rename to src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index bd1419e4b..299b1d41c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -1,8 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; +using System.Runtime.InteropServices; + using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; @@ -23,11 +25,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2DProcessor(DenseMatrix kernelX, DenseMatrix kernelY) + /// Whether the convolution filter is applied to alpha as well as the color channels. + public Convolution2DProcessor(in DenseMatrix kernelX, in DenseMatrix kernelY, bool preserveAlpha) { Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.KernelX = kernelX; this.KernelY = kernelY; + this.PreserveAlpha = preserveAlpha; } /// @@ -40,6 +44,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// 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, @@ -48,6 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { DenseMatrix matrixY = this.KernelY; DenseMatrix matrixX = this.KernelX; + bool preserveAlpha = this.PreserveAlpha; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startY = interest.Y; @@ -71,18 +81,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { Span vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); - for (int x = 0; x < width; x++) + if (preserveAlpha) + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve2D3( + in matrixY, + in matrixX, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + else { - DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve2D4( + in matrixY, + in matrixX, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs similarity index 65% rename from src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs rename to src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 05007c370..03268c9dd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -3,7 +3,7 @@ using System; using System.Numerics; - +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; @@ -24,10 +24,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2PassProcessor(DenseMatrix kernelX, DenseMatrix kernelY) + /// Whether the convolution filter is applied to alpha as well as the color channels. + public Convolution2PassProcessor( + in DenseMatrix kernelX, + in DenseMatrix kernelY, + bool preserveAlpha) { this.KernelX = kernelX; this.KernelY = kernelY; + this.PreserveAlpha = preserveAlpha; } /// @@ -40,13 +45,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public DenseMatrix KernelY { get; } + /// + /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } + /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { using (Buffer2D firstPassPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) { - 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); @@ -72,6 +80,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Configuration configuration) { DenseMatrix matrix = kernel; + bool preserveAlpha = this.PreserveAlpha; + int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; @@ -89,18 +99,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { Span vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); - for (int x = 0; x < width; x++) + if (preserveAlpha) + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve3( + in matrix, + sourcePixels, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + else { - DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX); + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve4( + in matrix, + sourcePixels, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs new file mode 100644 index 000000000..661ab523d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Primitives; + +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.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs similarity index 55% rename from src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs rename to src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 8ef64bdac..6c3b9a46f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; @@ -22,17 +23,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Initializes a new instance of the class. /// /// The 2d gradient operator. - public ConvolutionProcessor(DenseMatrix kernelXY) => this.KernelXY = kernelXY; + /// Whether the convolution filter is applied to alpha as well as the color channels. + public ConvolutionProcessor(in DenseMatrix kernelXY, bool preserveAlpha) + { + this.KernelXY = kernelXY; + this.PreserveAlpha = preserveAlpha; + } /// /// Gets the 2d 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, Rectangle sourceRectangle, Configuration configuration) { DenseMatrix matrix = this.KernelXY; + bool preserveAlpha = this.PreserveAlpha; + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; @@ -55,18 +68,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { Span vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); - for (int x = 0; x < width; x++) + if (preserveAlpha) + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve3( + in matrix, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + else { - DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve4( + in matrix, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs similarity index 76% rename from src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs rename to src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs index 892771649..f6771288f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Defines a processor that detects edges within an image using two one-dimensional matrices. /// /// The pixel format. - internal abstract class EdgeDetector2DProcessor : ImageProcessor, IEdgeDetectorProcessor + internal class EdgeDetector2DProcessor : ImageProcessor where TPixel : struct, IPixel { /// @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// 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) + internal EdgeDetector2DProcessor(in DenseMatrix kernelX, in DenseMatrix kernelY, bool grayscale) { Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.KernelX = kernelX; @@ -39,18 +39,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// 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 OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + => new Convolution2DProcessor(this.KernelX, this.KernelY, true).Apply(source, sourceRectangle, configuration); /// protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { if (this.Grayscale) { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); + new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs similarity index 66% rename from src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs rename to src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 4165cf024..5995ac844 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -5,14 +5,12 @@ 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 @@ -21,74 +19,37 @@ 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 abstract class EdgeDetectorCompassProcessor : ImageProcessor, IEdgeDetectorProcessor + internal class EdgeDetectorCompassProcessor : ImageProcessor where TPixel : struct, IPixel { /// /// Initializes a new instance of the class. /// + /// Gets the kernels to use. /// Whether to convert the image to grayscale before performing edge detection. - protected EdgeDetectorCompassProcessor(bool grayscale) + internal EdgeDetectorCompassProcessor(CompassKernels kernels, bool grayscale) { this.Grayscale = grayscale; + this.Kernels = kernels; } - /// - /// Gets the North gradient operator - /// - public abstract DenseMatrix North { get; } - - /// - /// Gets the NorthWest gradient operator - /// - public abstract DenseMatrix NorthWest { get; } - - /// - /// Gets the West gradient operator - /// - public abstract DenseMatrix West { get; } - - /// - /// Gets the SouthWest gradient operator - /// - public abstract DenseMatrix SouthWest { get; } - - /// - /// Gets the South gradient operator - /// - public abstract DenseMatrix South { get; } - - /// - /// Gets the SouthEast gradient operator - /// - public abstract DenseMatrix SouthEast { get; } - - /// - /// Gets the East gradient operator - /// - public abstract DenseMatrix East { get; } - - /// - /// Gets the NorthEast gradient operator - /// - public abstract DenseMatrix NorthEast { get; } + private CompassKernels Kernels { get; } - /// - public bool Grayscale { get; } + private bool Grayscale { get; } /// protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { if (this.Grayscale) { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); + 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 }; + DenseMatrix[] kernels = this.Kernels.Flatten(); int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; @@ -104,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution // we need a clean copy for each pass to start from using (ImageFrame cleanCopy = source.Clone()) { - new ConvolutionProcessor(kernels[0]).Apply(source, sourceRectangle, configuration); + new ConvolutionProcessor(kernels[0], true).Apply(source, sourceRectangle, configuration); if (kernels.Length == 1) { @@ -133,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { using (ImageFrame pass = cleanCopy.Clone()) { - new ConvolutionProcessor(kernels[i]).Apply(pass, sourceRectangle, configuration); + new ConvolutionProcessor(kernels[i], true).Apply(pass, sourceRectangle, configuration); Buffer2D passPixels = pass.PixelBuffer; Buffer2D targetPixels = source.PixelBuffer; @@ -147,10 +108,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { int offsetY = y - shiftY; - ref TPixel passPixelsBase = - ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = - ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); + 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++) { @@ -158,8 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution // 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); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); var pixelValue = Vector4.Max( currentPassPixel.ToVector4(), diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index 9173bc229..75be15bcc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -2,51 +2,30 @@ // 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. /// - /// The pixel format. - internal abstract class EdgeDetectorProcessor : ImageProcessor, IEdgeDetectorProcessor - where TPixel : struct, IPixel + public abstract 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) + /// A value indicating whether to convert the image to grayscale before performing edge detection. + protected EdgeDetectorProcessor(bool grayscale) { - this.KernelXY = kernelXY; this.Grayscale = grayscale; } - /// - public bool Grayscale { get; } - /// - /// Gets the 2d gradient operator. + /// Gets a value indicating whether to convert the image to grayscale before performing edge detection. /// - public DenseMatrix KernelXY { get; } - - /// - protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - if (this.Grayscale) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - } + public bool Grayscale { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new ConvolutionProcessor(this.KernelXY).Apply(source, sourceRectangle, configuration); - } + /// + public abstract IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel; } } \ 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 000000000..041c54803 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +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. + /// + /// The pixel format. + internal class EdgeDetectorProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The 2d gradient operator. + /// Whether to convert the image to grayscale before performing edge detection. + public EdgeDetectorProcessor(in DenseMatrix kernelXY, bool grayscale) + { + this.KernelXY = kernelXY; + this.Grayscale = grayscale; + } + + public bool Grayscale { get; } + + /// + /// Gets the 2d gradient operator. + /// + public DenseMatrix KernelXY { 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) + => new ConvolutionProcessor(this.KernelXY, true).Apply(source, sourceRectangle, configuration); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs index b3bc15d39..764f4ca51 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -1,38 +1,40 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// 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 +45,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 +56,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 +66,15 @@ 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() + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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; + return new GaussianBlurProcessor(this); } } } \ 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 000000000..a129ff547 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies Gaussian blur processing to an image. + /// + /// The pixel format. + internal class GaussianBlurProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The defining the processor parameters. + public GaussianBlurProcessor(GaussianBlurProcessor definition) + { + 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, + Rectangle sourceRectangle, + Configuration configuration) => + new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply( + source, + sourceRectangle, + configuration); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 786bf7757..23282af36 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -3,38 +3,38 @@ 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 default value for . + /// + public const float DefaultSigma = 3f; + /// - /// The maximum size of the kernel in either direction. + /// Initializes a new instance of the class. /// - private readonly int kernelSize; + public GaussianSharpenProcessor() + : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) + { + } /// - /// 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)) + /// 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 +45,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 +56,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 +66,15 @@ 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() + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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; + return new GaussianSharpenProcessor(this); } } } \ 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 000000000..edde9f9e7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies Gaussian sharpening processing to the image. + /// + /// The pixel format. + internal class GaussianSharpenProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The defining the processor parameters. + public GaussianSharpenProcessor(GaussianSharpenProcessor definition) + { + 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, Rectangle sourceRectangle, Configuration configuration) + => new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/IEdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/IEdgeDetectorProcessor.cs deleted file mode 100644 index b2ecbf115..000000000 --- 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/KayyaliProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs index 8652efa12..2d0f056b6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs @@ -1,24 +1,30 @@ // 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. + /// Defines edge detection processing using the Kayyali operator filter. + /// See . /// - /// The pixel format. - internal class KayyaliProcessor : EdgeDetector2DProcessor - where TPixel : struct, IPixel + public sealed class KayyaliProcessor : EdgeDetectorProcessor { /// - /// 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. public KayyaliProcessor(bool grayscale) - : base(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY, grayscale) + : base(grayscale) + { + } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() { + return new EdgeDetector2DProcessor( + KayyaliKernels.KayyaliX, + KayyaliKernels.KayyaliY, + this.Grayscale); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs new file mode 100644 index 000000000..f44de9105 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + internal abstract class CompassKernels + { + /// + /// Gets the North gradient operator. + /// + public abstract DenseMatrix North { get; } + + /// + /// Gets the NorthWest gradient operator. + /// + public abstract DenseMatrix NorthWest { get; } + + /// + /// Gets the West gradient operator. + /// + public abstract DenseMatrix West { get; } + + /// + /// Gets the SouthWest gradient operator. + /// + public abstract DenseMatrix SouthWest { get; } + + /// + /// Gets the South gradient operator. + /// + public abstract DenseMatrix South { get; } + + /// + /// Gets the SouthEast gradient operator. + /// + public abstract DenseMatrix SouthEast { get; } + + /// + /// Gets the East gradient operator. + /// + public abstract DenseMatrix East { get; } + + /// + /// Gets the NorthEast gradient operator. + /// + public abstract DenseMatrix NorthEast { get; } + + public DenseMatrix[] Flatten() => + new[] + { + this.North, this.NorthWest, this.West, this.SouthWest, + this.South, this.SouthEast, this.East, this.NorthEast + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/KayyaliKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/KayyaliKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs similarity index 81% rename from src/ImageSharp/Processing/Processors/Convolution/KirschKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs index 86232e306..882b87075 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KirschKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs @@ -8,12 +8,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Contains the eight matrices used for Kirsch edge detection /// - internal static class KirschKernels + internal class KirschKernels : CompassKernels { /// /// Gets the North gradient operator /// - public static DenseMatrix KirschNorth => + public override DenseMatrix North => new float[,] { { 5, 5, 5 }, @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthWest gradient operator /// - public static DenseMatrix KirschNorthWest => + public override DenseMatrix NorthWest => new float[,] { { 5, 5, -3 }, @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the West gradient operator /// - public static DenseMatrix KirschWest => + public override DenseMatrix West => new float[,] { { 5, -3, -3 }, @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthWest gradient operator /// - public static DenseMatrix KirschSouthWest => + public override DenseMatrix SouthWest => new float[,] { { -3, -3, -3 }, @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the South gradient operator /// - public static DenseMatrix KirschSouth => + public override DenseMatrix South => new float[,] { { -3, -3, -3 }, @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthEast gradient operator /// - public static DenseMatrix KirschSouthEast => + public override DenseMatrix SouthEast => new float[,] { { -3, -3, -3 }, @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the East gradient operator /// - public static DenseMatrix KirschEast => + public override DenseMatrix East => new float[,] { { -3, -3, 5 }, @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthEast gradient operator /// - public static DenseMatrix KirschNorthEast => + public override DenseMatrix NorthEast => new float[,] { { -3, 5, 5 }, diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernelFactory.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/LaplacianKernelFactory.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/LaplacianKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/PrewittKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/PrewittKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/RobertsCrossKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs similarity index 78% rename from src/ImageSharp/Processing/Processors/Convolution/RobinsonKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs index 4f47184e3..699d669ec 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs @@ -6,14 +6,14 @@ using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Contains the kernels used for Robinson edge detection + /// Contains the kernels used for Robinson edge detection. /// - internal static class RobinsonKernels + internal class RobinsonKernels : CompassKernels { /// /// Gets the North gradient operator /// - public static DenseMatrix RobinsonNorth => + public override DenseMatrix North => new float[,] { { 1, 2, 1 }, @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthWest gradient operator /// - public static DenseMatrix RobinsonNorthWest => + public override DenseMatrix NorthWest => new float[,] { { 2, 1, 0 }, @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the West gradient operator /// - public static DenseMatrix RobinsonWest => + public override DenseMatrix West => new float[,] { { 1, 0, -1 }, @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthWest gradient operator /// - public static DenseMatrix RobinsonSouthWest => + public override DenseMatrix SouthWest => new float[,] { { 0, -1, -2 }, @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the South gradient operator /// - public static DenseMatrix RobinsonSouth => + public override DenseMatrix South => new float[,] { { -1, -2, -1 }, @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthEast gradient operator /// - public static DenseMatrix RobinsonSouthEast => + public override DenseMatrix SouthEast => new float[,] { { -2, -1, 0 }, @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the East gradient operator /// - public static DenseMatrix RobinsonEast => + public override DenseMatrix East => new float[,] { { -1, 0, 1 }, @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthEast gradient operator /// - public static DenseMatrix RobinsonNorthEast => + public override DenseMatrix NorthEast => new float[,] { { 0, 1, 2 }, diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/ScharrKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/SobelKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs index c3188676f..9e9534422 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs @@ -1,20 +1,16 @@ // 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. + /// Defines edge detection using the Kirsch operator filter. + /// See . /// - /// The pixel format. - internal class KirschProcessor : EdgeDetectorCompassProcessor - where TPixel : struct, IPixel + public sealed class KirschProcessor : EdgeDetectorProcessor { /// - /// 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. public KirschProcessor(bool grayscale) @@ -22,28 +18,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { } - /// - 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; + /// + public override IImageProcessor CreatePixelSpecificProcessor() + { + return new EdgeDetectorCompassProcessor(new KirschKernels(), this.Grayscale); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs index f498d374c..9c9488fec 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs @@ -9,17 +9,21 @@ 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 + public sealed class Laplacian3x3Processor : EdgeDetectorProcessor { /// - /// 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. public Laplacian3x3Processor(bool grayscale) - : base(LaplacianKernels.Laplacian3x3, grayscale) + : base(grayscale) { } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() + { + return new EdgeDetectorProcessor(LaplacianKernels.Laplacian3x3, this.Grayscale); + } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs index 558acf7b3..fa0c8c5aa 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs @@ -1,25 +1,27 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Applies edge detection processing to the image using the Laplacian 5x5 operator filter. - /// + /// Defines edge detection processing using the Laplacian 5x5 operator filter. + /// . /// - /// The pixel format. - internal class Laplacian5x5Processor : EdgeDetectorProcessor - where TPixel : struct, IPixel + public sealed class Laplacian5x5Processor : EdgeDetectorProcessor { /// - /// 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. public Laplacian5x5Processor(bool grayscale) - : base(LaplacianKernels.Laplacian5x5, grayscale) + : base(grayscale) + { + } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() { + return new EdgeDetectorProcessor(LaplacianKernels.Laplacian5x5, this.Grayscale); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs index 6cc65dc58..2caff8201 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs @@ -1,25 +1,27 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// /// Applies edge detection processing to the image using the Laplacian of Gaussian operator filter. - /// + /// See . /// - /// The pixel format. - internal class LaplacianOfGaussianProcessor : EdgeDetectorProcessor - where TPixel : struct, IPixel + public sealed class LaplacianOfGaussianProcessor : EdgeDetectorProcessor { /// - /// 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. public LaplacianOfGaussianProcessor(bool grayscale) - : base(LaplacianKernels.LaplacianOfGaussianXY, grayscale) + : base(grayscale) + { + } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() { + return new EdgeDetectorProcessor(LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs index 75ef4dac6..29f6fc279 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs @@ -1,25 +1,27 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Applies edge detection processing to the image using the Prewitt operator filter. - /// + /// Defines edge detection using the Prewitt operator filter. + /// See . /// - /// The pixel format. - internal class PrewittProcessor : EdgeDetector2DProcessor - where TPixel : struct, IPixel + public sealed class PrewittProcessor : EdgeDetectorProcessor { /// - /// 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. public PrewittProcessor(bool grayscale) - : base(PrewittKernels.PrewittX, PrewittKernels.PrewittY, grayscale) + : base(grayscale) + { + } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() { + return new EdgeDetector2DProcessor(PrewittKernels.PrewittX, PrewittKernels.PrewittY, this.Grayscale); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs index d685860f6..a9db37a07 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs @@ -6,20 +6,24 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Applies edge detection processing to the image using the Roberts Cross operator filter. - /// + /// Defines edge detection processing using the Roberts Cross operator filter. + /// See . /// - /// The pixel format. - internal class RobertsCrossProcessor : EdgeDetector2DProcessor - where TPixel : struct, IPixel + public sealed class RobertsCrossProcessor : EdgeDetectorProcessor { /// - /// 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. public RobertsCrossProcessor(bool grayscale) - : base(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY, grayscale) + : base(grayscale) { } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() + { + return new EdgeDetector2DProcessor(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY, this.Grayscale); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs index 193c1008d..fd7378902 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs @@ -7,15 +7,13 @@ using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Applies edge detection processing to the image using the Robinson operator filter. - /// + /// Defines edge detection using the Robinson operator filter. + /// See . /// - /// The pixel format. - internal class RobinsonProcessor : EdgeDetectorCompassProcessor - where TPixel : struct, IPixel + public sealed class RobinsonProcessor : EdgeDetectorProcessor { /// - /// 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. public RobinsonProcessor(bool grayscale) @@ -23,28 +21,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { } - /// - 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; + /// + public override IImageProcessor CreatePixelSpecificProcessor() + { + return new EdgeDetectorCompassProcessor(new RobinsonKernels(), this.Grayscale); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs index 79fc0e79f..ec0183dc6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs @@ -6,20 +6,24 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Applies edge detection processing to the image using the Scharr operator filter. + /// Defines edge detection processing using the Scharr operator filter. /// /// - /// The pixel format. - internal class ScharrProcessor : EdgeDetector2DProcessor - where TPixel : struct, IPixel + public sealed class ScharrProcessor : EdgeDetectorProcessor { /// - /// 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. public ScharrProcessor(bool grayscale) - : base(ScharrKernels.ScharrX, ScharrKernels.ScharrY, grayscale) + : base(grayscale) { } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() + { + return new EdgeDetector2DProcessor(ScharrKernels.ScharrX, ScharrKernels.ScharrY, this.Grayscale); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs index 3ca53f6f0..3d5d1e7bf 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs @@ -2,24 +2,29 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// The Sobel operator filter. - /// + /// Defines edge detection using the Sobel operator filter. + /// See . /// - /// The pixel format. - internal class SobelProcessor : EdgeDetector2DProcessor - where TPixel : struct, IPixel + public sealed class SobelProcessor : EdgeDetectorProcessor { /// - /// 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. public SobelProcessor(bool grayscale) - : base(SobelKernels.SobelX, SobelKernels.SobelY, grayscale) + : base(grayscale) { } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() + { + return new EdgeDetector2DProcessor(SobelKernels.SobelX, SobelKernels.SobelY, this.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 7a9753d1a..000000000 --- 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 index 17c97ddc9..0461d179f 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. /// /// - public sealed class AtkinsonDiffuser : ErrorDiffuserBase + public sealed class AtkinsonDiffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs index 84455b24a..23d4321e9 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Applies error diffusion based dithering using the Burks image dithering algorithm. /// /// - public sealed class BurksDiffuser : ErrorDiffuserBase + public sealed class BurksDiffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs similarity index 94% rename from src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs rename to src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs index 642da2f00..1c8156bf5 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// /// The base class for performing error diffusion based dithering. /// - public abstract class ErrorDiffuserBase : IErrorDiffuser + public abstract class ErrorDiffuser : IErrorDiffuser { /// /// The vector to perform division. @@ -21,12 +21,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering private readonly Vector4 divisorVector; /// - /// The matrix width + /// The matrix width. /// private readonly int matrixHeight; /// - /// The matrix height + /// The matrix height. /// private readonly int matrixWidth; @@ -36,16 +36,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering private readonly int startingOffset; /// - /// The diffusion matrix + /// The diffusion matrix. /// private readonly DenseMatrix matrix; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The dithering matrix. /// The divisor. - internal ErrorDiffuserBase(DenseMatrix matrix, byte divisor) + internal ErrorDiffuser(in DenseMatrix matrix, byte divisor) { Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs index 911d3e8fd..e0b79c2b2 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs @@ -2,21 +2,19 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; + +using SixLabors.ImageSharp.Processing.Processors.Binarization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// - /// An that dithers an image using error diffusion. + /// Defines a dither operation 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 ErrorDiffusionPaletteProcessor : PaletteDitherProcessorBase - where TPixel : struct, IPixel + public sealed class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The error diffuser public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser) @@ -25,22 +23,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } /// - /// Initializes a new instance of the class. + /// 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) + : this(diffuser, threshold, Color.WebSafePalette) { } /// - /// Initializes a new instance of the class. + /// 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) + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, ReadOnlyMemory palette) : base(palette) { Guard.NotNull(diffuser, nameof(diffuser)); @@ -60,59 +58,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public float Threshold { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public override IImageProcessor CreatePixelSpecificProcessor() { - 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); - } - } + return new ErrorDiffusionPaletteProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs new file mode 100644 index 000000000..7edf287e8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs @@ -0,0 +1,81 @@ +// 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 : PaletteDitherProcessor + where TPixel : struct, IPixel + { + public ErrorDiffusionPaletteProcessor(ErrorDiffusionPaletteProcessor definition) + : base(definition) + { + } + + private new ErrorDiffusionPaletteProcessor Definition => (ErrorDiffusionPaletteProcessor)base.Definition; + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + byte threshold = (byte)MathF.Round(this.Definition.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.Definition.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/FloydSteinbergDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs index 6a7655b59..78a28a693 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. /// /// - public sealed class FloydSteinbergDiffuser : ErrorDiffuserBase + public sealed class FloydSteinbergDiffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs index a69557d6d..64c861083 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm. /// /// - public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuserBase + public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs index 1b4910a14..ac6554d4c 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs @@ -2,35 +2,30 @@ // 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. + /// Defines a dithering operation 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 + public sealed class OrderedDitherPaletteProcessor : PaletteDitherProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The ordered ditherer. public OrderedDitherPaletteProcessor(IOrderedDither dither) - : this(dither, NamedColors.WebSafePalette) + : this(dither, Color.WebSafePalette) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The ordered ditherer. /// The palette to select substitute colors from. - public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette) + public OrderedDitherPaletteProcessor(IOrderedDither dither, ReadOnlyMemory palette) : base(palette) => this.Dither = dither ?? throw new ArgumentNullException(nameof(dither)); /// @@ -38,57 +33,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// public IOrderedDither Dither { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public override IImageProcessor CreatePixelSpecificProcessor() { - 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); - } - } + return new OrderedDitherPaletteProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs new file mode 100644 index 000000000..eefa6e522 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.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 OrderedDitherPaletteProcessor : PaletteDitherProcessor + where TPixel : struct, IPixel + { + public OrderedDitherPaletteProcessor(OrderedDitherPaletteProcessor definition) + : base(definition) + { + } + + private new OrderedDitherPaletteProcessor Definition => (OrderedDitherPaletteProcessor)base.Definition; + + /// + 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.Definition.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 000000000..de798b64b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs @@ -0,0 +1,33 @@ +// 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 +{ + /// + /// The base class for dither and diffusion processors that consume a palette. + /// + public abstract class PaletteDitherProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The palette to select substitute colors from. + protected PaletteDitherProcessor(ReadOnlyMemory palette) + { + this.Palette = palette; + } + + /// + /// Gets the palette to select substitute colors from. + /// + public ReadOnlyMemory Palette { get; } + + /// + public abstract IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs similarity index 70% rename from src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs rename to src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 43f6c8a45..205b589b1 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -1,10 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// 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; @@ -14,29 +15,54 @@ 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 + internal abstract class PaletteDitherProcessor : ImageProcessor where TPixel : struct, IPixel { private readonly Dictionary> cache = new Dictionary>(); + private TPixel[] palette; + /// /// The vector representation of the image palette. /// private Vector4[] paletteVector; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The palette to select substitute colors from. - protected PaletteDitherProcessorBase(TPixel[] palette) + protected PaletteDitherProcessor(PaletteDitherProcessor definition) { - this.Palette = palette ?? throw new ArgumentNullException(nameof(palette)); + this.Definition = definition; } - /// - /// Gets the palette to select substitute colors from. - /// - public TPixel[] Palette { get; } + protected PaletteDitherProcessor Definition { get; } + + protected override void BeforeFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + base.BeforeFrameApply(source, sourceRectangle, configuration); + + // Lazy init palette: + if (this.palette is null) + { + ReadOnlySpan sourcePalette = this.Definition.Palette.Span; + this.palette = new TPixel[sourcePalette.Length]; + Color.ToPixel(configuration, sourcePalette, this.palette); + } + + // Lazy init paletteVector: + if (this.paletteVector is null) + { + this.paletteVector = new Vector4[this.palette.Length]; + PixelOperations.Instance.ToVector4( + configuration, + (ReadOnlySpan)this.palette, + (Span)this.paletteVector, + PixelConversionModifiers.Scale); + } + } /// /// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space. @@ -74,12 +100,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { leastDistance = distance; secondClosest = closest; - closest = this.Palette[index]; + closest = this.palette[index]; } else if (distance < secondLeastDistance) { secondLeastDistance = distance; - secondClosest = this.Palette[index]; + secondClosest = this.palette[index]; } } @@ -89,17 +115,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering 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/Sierra2Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs index ebde2ceaf..b489f8f28 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. /// /// - public sealed class Sierra2Diffuser : ErrorDiffuserBase + public sealed class Sierra2Diffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs index 144a83a82..04abc782a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. /// /// - public sealed class Sierra3Diffuser : ErrorDiffuserBase + public sealed class Sierra3Diffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs index d71fba9f2..2ac69cf45 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Applies error diffusion based dithering using the SierraLite image dithering algorithm. /// /// - public sealed class SierraLiteDiffuser : ErrorDiffuserBase + public sealed class SierraLiteDiffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs index 4b1323065..b929a28d3 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. /// - public sealed class StevensonArceDiffuser : ErrorDiffuserBase + public sealed class StevensonArceDiffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs index 1dd510a5e..bb3aebc3f 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// Applies error diffusion based dithering using the Stucki image dithering algorithm. /// /// - public sealed class StuckiDiffuser : ErrorDiffuserBase + public sealed class StuckiDiffuser : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index 1b17c470e..741ba9ece 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. // 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,20 @@ 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) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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); - } + return new OilPaintingProcessor(this); } } } \ 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 000000000..7efb71fa1 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -0,0 +1,129 @@ +// 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.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. + /// + /// Adapted from by Dewald Esterhuizen. + /// The pixel format. + internal class OilPaintingProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly OilPaintingProcessor definition; + + public OilPaintingProcessor(OilPaintingProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + int brushSize = this.definition.BrushSize; + if (brushSize <= 0 || brushSize > source.Height || brushSize > source.Width) + { + throw new ArgumentOutOfRangeException(nameof(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 = brushSize >> 1; + int levels = this.definition.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); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index 50f76efed..1599c9dab 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. // 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,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// public int Size { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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; - } - } - } - }); + return new PixelateProcessor(this); } } } \ 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 000000000..21f3bb774 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Common; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// Applies a pixelation effect processing to the image. + /// + /// The pixel format. + internal class PixelateProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly PixelateProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public PixelateProcessor(PixelateProcessor definition) + { + this.definition = definition; + } + + private int Size => this.definition.Size; + + /// + 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; + } + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs index 57c1bad39..0e6653d99 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. // 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 696a854ab..10a201b1d 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. // 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 9925ce5c2..425ae511f 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. // 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 b1b8ad747..77862b3e3 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. // 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 ebec464d5..3fdeafde3 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. // 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 0d1b1da90..7e1c0ca50 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. // 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 ae0727048..e4ed44e74 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. // 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 d3a44c066..36c43d844 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs @@ -1,61 +1,32 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; +using SixLabors.ImageSharp.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(Matrix4x4 matrix) - { - this.Matrix = matrix; - } + public FilterProcessor(ColorMatrix matrix) => this.Matrix = matrix; /// - /// Gets the used to apply the image filter. + /// Gets the used to apply the image filter. /// - public Matrix4x4 Matrix { get; } + public ColorMatrix Matrix { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public virtual IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - - Matrix4x4 matrix = this.Matrix; - - ParallelHelper.IterateRows( - interest, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = interest.X; x < interest.Right; x++) - { - ref TPixel pixel = ref row[x]; - var vector = Vector4.Transform(pixel.ToVector4(), matrix); - pixel.FromVector4(vector); - } - } - }); + return new FilterProcessor(this); } } } \ 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 000000000..ab60a4279 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Filters +{ + /// + /// Provides methods that accept a matrix to apply free-form filters to images. + /// + /// The pixel format. + internal class FilterProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly FilterProcessor definition; + + public FilterProcessor(FilterProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startX = interest.X; + + ColorMatrix matrix = this.definition.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.FromVector4Destructive(configuration, vectorSpan, rowSpan); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs index c933d4858..153a1a17c 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. // 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 1716773b4..ae6d5f6f7 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs @@ -2,18 +2,17 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; 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 +22,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 4c3a0c73e..05d3cbdc7 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. // 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 462c42070..99b138033 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. // 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 003766e8a..012b10ee0 100644 --- a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs @@ -8,12 +8,10 @@ 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/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs index 737ebf618..f5a1befff 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs @@ -1,33 +1,23 @@ // 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.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() : base(KnownFilterMatrices.LomographFilter) { } - /// - protected override void AfterFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle, configuration); - } + /// + public override IImageProcessor CreatePixelSpecificProcessor() => + new LomographProcessor(this); } } \ 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 000000000..e0f85945a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs @@ -0,0 +1,33 @@ +// 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.Processors.Filters +{ + /// + /// Converts the colors of the image recreating an old Lomograph effect. + /// + internal class LomographProcessor : FilterProcessor + where TPixel : struct, IPixel + { + private static readonly Color VeryDarkGreen = Color.FromRgba(0, 10, 0, 255); + + /// + /// Initializes a new instance of the class. + /// + /// The defining the parameters. + public LomographProcessor(LomographProcessor definition) + : base(definition) + { + } + + /// + protected override void AfterFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle, configuration); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs index 0fea61cad..922ca3233 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs @@ -8,12 +8,10 @@ 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 +21,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/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs index fb065ac17..676bbc06b 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs @@ -1,35 +1,23 @@ // 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.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() : base(KnownFilterMatrices.PolaroidFilter) { } - /// - 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); - } + /// + public override IImageProcessor CreatePixelSpecificProcessor() => + new PolaroidProcessor(this); } } \ 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 000000000..0f511ee29 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Filters +{ + /// + /// Converts the colors of the image recreating an old Polaroid effect. + /// + internal class PolaroidProcessor : FilterProcessor + where TPixel : struct, IPixel + { + private static readonly Color LightOrange = Color.FromRgba(255, 153, 102, 128); + + private static readonly Color VeryDarkOrange = Color.FromRgb(102, 34, 0); + + /// + /// Initializes a new instance of the class. + /// + /// The defining the parameters. + public PolaroidProcessor(PolaroidProcessor definition) + : base(definition) + { + } + + /// + protected override void AfterFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + new VignetteProcessor(VeryDarkOrange).Apply(source, sourceRectangle, configuration); + new GlowProcessor(LightOrange, source.Width / 4F).Apply(source, sourceRectangle, configuration); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs index 79eb70851..1c01b608e 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. // 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 c6a01439a..ec423bd9a 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. // 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 75e956071..2cc40664b 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. // 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 2009dccd5..34af41067 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. // 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 593f7f5b0..a31e52c7e 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. // 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 153ad5559..b622126f3 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. // 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/IImageProcessor.cs b/src/ImageSharp/Processing/Processors/IImageProcessor.cs index d7fe0465b..9b030a6fe 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor.cs @@ -2,30 +2,24 @@ // 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 for executing + /// the processing algorithm on an . /// - /// 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 pixel type. + /// The + IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, 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 000000000..90dfaf8db --- /dev/null +++ b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Implements an algorithm to alter the pixels of an image. + /// + /// The pixel format. + public interface IImageProcessor + where TPixel : struct, IPixel + { + /// + /// Applies the process to the specified portion of the specified . + /// + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// is null. + /// + /// + /// doesn't fit the dimension of the image. + /// + void Apply(Image source, Rectangle sourceRectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs new file mode 100644 index 000000000..91b2b30d0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + internal static class ImageProcessorExtensions + { + public static void Apply(this IImageProcessor processor, Image source, Rectangle sourceRectangle) + { + var visitor = new ApplyVisitor(processor, sourceRectangle); + source.AcceptVisitor(visitor); + } + + /// + /// Apply an to a frame. + /// Only works from processors implemented by an subclass. + /// + internal static void Apply( + this IImageProcessor processor, + ImageFrame frame, + Rectangle sourceRectangle, + Configuration configuration) + where TPixel : struct, IPixel + { + var processorImpl = (ImageProcessor)processor.CreatePixelSpecificProcessor(); + processorImpl.Apply(frame, sourceRectangle, configuration); + } + + private class ApplyVisitor : IImageVisitor + { + private readonly IImageProcessor processor; + + private readonly Rectangle sourceRectangle; + + public ApplyVisitor(IImageProcessor processor, Rectangle sourceRectangle) + { + this.processor = processor; + this.sourceRectangle = sourceRectangle; + } + + public void Visit(Image image) + where TPixel : struct, IPixel + { + var processorImpl = this.processor.CreatePixelSpecificProcessor(); + processorImpl.Apply(image, this.sourceRectangle); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs new file mode 100644 index 000000000..ad27ae020 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// 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. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + public AdaptiveHistogramEqualizationProcessor( + int luminanceLevels, + bool clipHistogram, + float clipLimitPercentage, + int numberOfTiles) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + 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() + { + return new AdaptiveHistogramEqualizationProcessor( + this.LuminanceLevels, + this.ClipHistogram, + this.ClipLimitPercentage, + this.NumberOfTiles); + } + } +} \ No newline at end of file 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 000000000..333444eb3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -0,0 +1,546 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Applies an adaptive histogram equalization to the image. The image is split up in tiles. For each tile a cumulative distribution function (cdf) is calculated. + /// To calculate the final equalized pixel value, the cdf value of four adjacent tiles will be interpolated. + /// + /// The pixel format. + internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicating whether to clip the histogram bins at a specific value. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + public AdaptiveHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage, int tiles) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); + Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); + + this.Tiles = tiles; + } + + /// + /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// + private int Tiles { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int numberOfPixels = sourceWidth * sourceHeight; + int tileWidth = (int)MathF.Ceiling(sourceWidth / (float)this.Tiles); + int tileHeight = (int)MathF.Ceiling(sourceHeight / (float)this.Tiles); + int pixelsInTile = tileWidth * tileHeight; + int halfTileWidth = tileWidth / 2; + int halfTileHeight = tileHeight / 2; + int luminanceLevels = this.LuminanceLevels; + + // The image is split up into tiles. For each tile the cumulative distribution function will be calculated. + using (var cdfData = new CdfTileData(configuration, sourceWidth, sourceHeight, this.Tiles, this.Tiles, tileWidth, tileHeight, luminanceLevels)) + { + cdfData.CalculateLookupTables(source, this); + + var tileYStartPositions = new List<(int y, int cdfY)>(); + int cdfY = 0; + for (int y = halfTileHeight; y < sourceHeight - halfTileHeight; y += tileHeight) + { + tileYStartPositions.Add((y, cdfY)); + cdfY++; + } + + Parallel.For( + 0, + tileYStartPositions.Count, + new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }, + index => + { + int cdfX = 0; + int tileX = 0; + int tileY = 0; + int y = tileYStartPositions[index].y; + int cdfYY = tileYStartPositions[index].cdfY; + + // It's unfortunate that we have to do this per iteration. + ref TPixel sourceBase = ref source.GetPixelReference(0, 0); + + cdfX = 0; + for (int x = halfTileWidth; x < sourceWidth - halfTileWidth; x += tileWidth) + { + tileY = 0; + int yEnd = Math.Min(y + tileHeight, sourceHeight); + int xEnd = Math.Min(x + tileWidth, sourceWidth); + for (int dy = y; dy < yEnd; dy++) + { + int dyOffSet = dy * sourceWidth; + tileX = 0; + for (int dx = x; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenFourTiles( + pixel, + cdfData, + this.Tiles, + this.Tiles, + tileX, + tileY, + cdfX, + cdfYY, + tileWidth, + tileHeight, + luminanceLevels); + + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfX++; + } + }); + + ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); + + // Fix left column + ProcessBorderColumn(ref pixelsBase, cdfData, 0, sourceWidth, sourceHeight, tileWidth, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); + + // Fix right column + int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth; + ProcessBorderColumn(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, sourceHeight, tileWidth, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); + + // Fix top row + ProcessBorderRow(ref pixelsBase, cdfData, 0, sourceWidth, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + + // Fix bottom row + int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; + ProcessBorderRow(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + + // Left top corner + ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + + // Left bottom corner + ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + + // Right top corner + ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + + // Right bottom corner + ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + } + } + + /// + /// Processes the part of a corner tile which was previously left out. It consists of 1 / 4 of a tile and does not need interpolation. + /// + /// The output pixels base reference. + /// The lookup table to remap the grey values. + /// The source image width. + /// The x-position in the CDF lookup map. + /// The y-position in the CDF lookup map. + /// X start position. + /// X end position. + /// Y start position. + /// Y end position. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessCornerTile( + ref TPixel pixelsBase, + CdfTileData cdfData, + int sourceWidth, + int cdfX, + int cdfY, + int xStart, + int xEnd, + int yStart, + int yEnd, + int luminanceLevels) + { + for (int dy = yStart; dy < yEnd; dy++) + { + int dyOffSet = dy * sourceWidth; + for (int dx = xStart; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelsBase, dyOffSet + dx); + float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels)); + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } + } + } + + /// + /// Processes a border column of the image which is half the size of the tile width. + /// + /// The output pixels reference. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The X index of the lookup table to use. + /// The source image width. + /// The source image height. + /// The width of a tile. + /// The height of a tile. + /// X start position in the image. + /// X end position of the image. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessBorderColumn( + ref TPixel pixelBase, + CdfTileData cdfData, + int cdfX, + int sourceWidth, + int sourceHeight, + int tileWidth, + int tileHeight, + int xStart, + int xEnd, + int luminanceLevels) + { + int halfTileWidth = tileWidth / 2; + int halfTileHeight = tileHeight / 2; + + int cdfY = 0; + for (int y = halfTileHeight; y < sourceHeight - halfTileHeight; y += tileHeight) + { + int yLimit = Math.Min(y + tileHeight, sourceHeight - 1); + int tileY = 0; + for (int dy = y; dy < yLimit; dy++) + { + int dyOffSet = dy * sourceWidth; + int tileX = halfTileWidth; + for (int dx = xStart; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfY++; + } + } + + /// + /// Processes a border row of the image which is half of the size of the tile height. + /// + /// The output pixels base reference. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The Y index of the lookup table to use. + /// The source image width. + /// The width of a tile. + /// Y start position in the image. + /// Y end position of the image. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessBorderRow( + ref TPixel pixelBase, + CdfTileData cdfData, + int cdfY, + int sourceWidth, + int tileWidth, + int yStart, + int yEnd, + int luminanceLevels) + { + int halfTileWidth = tileWidth / 2; + + int cdfX = 0; + for (int x = halfTileWidth; x < sourceWidth - halfTileWidth; x += tileWidth) + { + int tileY = 0; + for (int dy = yStart; dy < yEnd; dy++) + { + int dyOffSet = dy * sourceWidth; + int tileX = 0; + int xLimit = Math.Min(x + tileWidth, sourceWidth - 1); + for (int dx = x; dx < xLimit; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfX++; + } + } + + /// + /// Bilinear interpolation between four adjacent tiles. + /// + /// The pixel to remap the grey value from. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The number of tiles in the x-direction. + /// The number of tiles in the y-direction. + /// X position inside the tile. + /// Y position inside the tile. + /// X index of the top left lookup table to use. + /// Y index of the top left lookup table to use. + /// Width of one tile in pixels. + /// Height of one tile in pixels. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// A re-mapped grey value. + [MethodImpl(InliningOptions.ShortMethod)] + private static float InterpolateBetweenFourTiles( + TPixel sourcePixel, + CdfTileData cdfData, + int tileCountX, + int tileCountY, + int tileX, + int tileY, + int cdfX, + int cdfY, + int tileWidth, + int tileHeight, + int luminanceLevels) + { + int luminance = GetLuminance(sourcePixel, luminanceLevels); + float tx = tileX / (float)(tileWidth - 1); + float ty = tileY / (float)(tileHeight - 1); + + int yTop = cdfY; + int yBottom = Math.Min(tileCountY - 1, yTop + 1); + int xLeft = cdfX; + int xRight = Math.Min(tileCountX - 1, xLeft + 1); + + float cdfLeftTopLuminance = cdfData.RemapGreyValue(xLeft, yTop, luminance); + float cdfRightTopLuminance = cdfData.RemapGreyValue(xRight, yTop, luminance); + float cdfLeftBottomLuminance = cdfData.RemapGreyValue(xLeft, yBottom, luminance); + float cdfRightBottomLuminance = cdfData.RemapGreyValue(xRight, yBottom, luminance); + return BilinearInterpolation(tx, ty, cdfLeftTopLuminance, cdfRightTopLuminance, cdfLeftBottomLuminance, cdfRightBottomLuminance); + } + + /// + /// Linear interpolation between two tiles. + /// + /// The pixel to remap the grey value from. + /// The CDF lookup map. + /// X position inside the first tile. + /// Y position inside the first tile. + /// X position inside the second tile. + /// Y position inside the second tile. + /// Position inside the tile. + /// Width of the tile. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// A re-mapped grey value. + [MethodImpl(InliningOptions.ShortMethod)] + private static float InterpolateBetweenTwoTiles( + TPixel sourcePixel, + CdfTileData cdfData, + int tileX1, + int tileY1, + int tileX2, + int tileY2, + int tilePos, + int tileWidth, + int luminanceLevels) + { + int luminance = GetLuminance(sourcePixel, luminanceLevels); + float tx = tilePos / (float)(tileWidth - 1); + + float cdfLuminance1 = cdfData.RemapGreyValue(tileX1, tileY1, luminance); + float cdfLuminance2 = cdfData.RemapGreyValue(tileX2, tileY2, luminance); + return LinearInterpolation(cdfLuminance1, cdfLuminance2, tx); + } + + /// + /// Bilinear interpolation between four tiles. + /// + /// The interpolation value in x direction in the range of [0, 1]. + /// The interpolation value in y direction in the range of [0, 1]. + /// Luminance from top left tile. + /// Luminance from right top tile. + /// Luminance from left bottom tile. + /// Luminance from right bottom tile. + /// Interpolated Luminance. + [MethodImpl(InliningOptions.ShortMethod)] + private static float BilinearInterpolation(float tx, float ty, float lt, float rt, float lb, float rb) + => LinearInterpolation(LinearInterpolation(lt, rt, tx), LinearInterpolation(lb, rb, tx), ty); + + /// + /// Linear interpolation between two grey values. + /// + /// The left value. + /// The right value. + /// The interpolation value between the two values in the range of [0, 1]. + /// The interpolated value. + [MethodImpl(InliningOptions.ShortMethod)] + private static float LinearInterpolation(float left, float right, float t) + => left + ((right - left) * t); + + /// + /// Contains the results of the cumulative distribution function for all tiles. + /// + private sealed class CdfTileData : IDisposable + { + private readonly Configuration configuration; + private readonly MemoryAllocator memoryAllocator; + + // Used for storing the minimum value for each CDF entry. + private readonly Buffer2D cdfMinBuffer2D; + + // Used for storing the LUT for each CDF entry. + private readonly Buffer2D cdfLutBuffer2D; + private readonly int pixelsInTile; + private readonly int sourceWidth; + private readonly int sourceHeight; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int luminanceLevels; + private readonly List<(int y, int cdfY)> tileYStartPositions; + + public CdfTileData( + Configuration configuration, + int sourceWidth, + int sourceHeight, + int tileCountX, + int tileCountY, + int tileWidth, + int tileHeight, + int luminanceLevels) + { + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.luminanceLevels = luminanceLevels; + this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY); + this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY); + this.sourceWidth = sourceWidth; + this.sourceHeight = sourceHeight; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.pixelsInTile = tileWidth * tileHeight; + + // Calculate the start positions and rent buffers. + this.tileYStartPositions = new List<(int y, int cdfY)>(); + int cdfY = 0; + for (int y = 0; y < sourceHeight; y += tileHeight) + { + this.tileYStartPositions.Add((y, cdfY)); + cdfY++; + } + } + + public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) + { + int sourceWidth = this.sourceWidth; + int sourceHeight = this.sourceHeight; + int tileWidth = this.tileWidth; + int tileHeight = this.tileHeight; + int luminanceLevels = this.luminanceLevels; + MemoryAllocator memoryAllocator = this.memoryAllocator; + + Parallel.For( + 0, + this.tileYStartPositions.Count, + new ParallelOptions() { MaxDegreeOfParallelism = this.configuration.MaxDegreeOfParallelism }, + index => + { + int cdfX = 0; + int cdfY = this.tileYStartPositions[index].cdfY; + int y = this.tileYStartPositions[index].y; + int endY = Math.Min(y + tileHeight, sourceHeight); + ref TPixel sourceBase = ref source.GetPixelReference(0, 0); + ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); + + using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(luminanceLevels)) + { + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + for (int x = 0; x < sourceWidth; x += tileWidth) + { + histogram.Clear(); + ref int cdfBase = ref MemoryMarshal.GetReference(this.GetCdfLutSpan(cdfX, index)); + + int xlimit = Math.Min(x + tileWidth, sourceWidth); + for (int dy = y; dy < endY; dy++) + { + int dyOffset = dy * sourceWidth; + for (int dx = x; dx < xlimit; dx++) + { + int luminace = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), luminanceLevels); + histogram[luminace]++; + } + } + + if (processor.ClipHistogramEnabled) + { + processor.ClipHistogram(histogram, processor.ClipLimitPercentage, this.pixelsInTile); + } + + Unsafe.Add(ref cdfMinBase, cdfX) = processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + cdfX++; + } + } + }); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.GetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); + + /// + /// Remaps the grey value with the cdf. + /// + /// The tiles x-position. + /// The tiles y-position. + /// The original luminance. + /// The remapped luminance. + [MethodImpl(InliningOptions.ShortMethod)] + public float RemapGreyValue(int tilesX, int tilesY, int luminance) + { + int cdfMin = this.cdfMinBuffer2D[tilesX, tilesY]; + Span cdfSpan = this.GetCdfLutSpan(tilesX, tilesY); + return (this.pixelsInTile - cdfMin) == 0 + ? cdfSpan[luminance] / this.pixelsInTile + : cdfSpan[luminance] / (float)(this.pixelsInTile - cdfMin); + } + + public void Dispose() + { + this.cdfMinBuffer2D.Dispose(); + this.cdfLutBuffer2D.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs new file mode 100644 index 000000000..36f798975 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// 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. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + public AdaptiveHistogramEqualizationSlidingWindowProcessor( + int luminanceLevels, + bool clipHistogram, + float clipLimitPercentage, + int numberOfTiles) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + 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() + { + return new AdaptiveHistogramEqualizationSlidingWindowProcessor( + this.LuminanceLevels, + this.ClipHistogram, + this.ClipLimitPercentage, + this.NumberOfTiles); + } + } +} \ No newline at end of file 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 000000000..40e2d41d9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -0,0 +1,390 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Applies an adaptive histogram equalization to the image using an sliding window approach. + /// + /// The pixel format. + internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicating whether to clip the histogram bins at a specific value. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + public AdaptiveHistogramEqualizationSlidingWindowProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage, int tiles) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); + Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); + + this.Tiles = tiles; + } + + /// + /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// + private int Tiles { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + int numberOfPixels = source.Width * source.Height; + Span pixels = source.GetPixelSpan(); + + var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }; + int tileWidth = source.Width / this.Tiles; + int tileHeight = tileWidth; + int pixeInTile = tileWidth * tileHeight; + int halfTileHeight = tileHeight / 2; + int halfTileWidth = halfTileHeight; + var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixeInTile); + + using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) + { + // Process the inner tiles, which do not require to check the borders. + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: halfTileHeight, + yEnd: source.Height - halfTileHeight, + useFastPath: true, + configuration)); + + // Process the left border of the image. + Parallel.For( + 0, + halfTileWidth, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false, + configuration)); + + // Process the right border of the image. + Parallel.For( + source.Width - halfTileWidth, + source.Width, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false, + configuration)); + + // Process the top border of the image. + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: halfTileHeight, + useFastPath: false, + configuration)); + + // Process the bottom border of the image. + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: source.Height - halfTileHeight, + yEnd: source.Height, + useFastPath: false, + configuration)); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + } + + /// + /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. + /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and + /// adding a new row at the bottom. + /// + /// The source image. + /// The memory allocator. + /// The target pixels. + /// Informations about the sliding window dimensions. + /// The y start position. + /// The y end position. + /// if set to true the borders of the image will not be checked. + /// The configuration. + /// Action Delegate. + private Action ProcessSlidingWindow( + ImageFrame source, + MemoryAllocator memoryAllocator, + Buffer2D targetPixels, + SlidingWindowInfos swInfos, + int yStart, + int yEnd, + bool useFastPath, + Configuration configuration) + { + return x => + { + using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner histogramBufferCopy = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner pixelRowBuffer = memoryAllocator.Allocate(swInfos.TileWidth, AllocationOptions.Clean)) + { + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + Span histogramCopy = histogramBufferCopy.GetSpan(); + ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); + + ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); + + Span pixelRow = pixelRowBuffer.GetSpan(); + ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); + + // Build the initial histogram of grayscale values. + for (int dy = yStart - swInfos.HalfTileHeight; dy < yStart + swInfos.HalfTileHeight; dy++) + { + if (useFastPath) + { + this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); + } + else + { + this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); + } + + this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); + } + + for (int y = yStart; y < yEnd; y++) + { + if (this.ClipHistogramEnabled) + { + // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. + histogram.CopyTo(histogramCopy); + this.ClipHistogram(histogramCopy, this.ClipLimitPercentage, swInfos.PixeInTile); + } + + // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. + int cdfMin = this.ClipHistogramEnabled + ? this.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) + : this.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = swInfos.PixeInTile - cdfMin; + + // Map the current pixel to the new equalized value. + int luminance = GetLuminance(source[x, y], this.LuminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; + targetPixels[x, y].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, source[x, y].ToVector4().W)); + + // Remove top most row from the histogram, mirroring rows which exceeds the borders. + if (useFastPath) + { + this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); + } + else + { + this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); + } + + this.RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); + + // Add new bottom row to the histogram, mirroring rows which exceeds the borders. + if (useFastPath) + { + this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); + } + else + { + this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); + } + + this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); + } + } + }; + } + + /// + /// Get the a pixel row at a given position with a length of the tile width. Mirrors pixels which exceeds the edges. + /// + /// The source image. + /// Pre-allocated pixel row span of the size of a the tile width. + /// The x position. + /// The y position. + /// The width in pixels of a tile. + /// The configuration. + private void CopyPixelRow( + ImageFrame source, + Span rowPixels, + int x, + int y, + int tileWidth, + Configuration configuration) + { + if (y < 0) + { + y = ImageMaths.FastAbs(y); + } + else if (y >= source.Height) + { + int diff = y - source.Height; + y = source.Height - diff - 1; + } + + // Special cases for the left and the right border where GetPixelRowSpan can not be used. + if (x < 0) + { + rowPixels.Clear(); + int idx = 0; + for (int dx = x; dx < x + tileWidth; dx++) + { + rowPixels[idx] = source[ImageMaths.FastAbs(dx), y].ToVector4(); + idx++; + } + + return; + } + else if (x + tileWidth > source.Width) + { + rowPixels.Clear(); + int idx = 0; + for (int dx = x; dx < x + tileWidth; dx++) + { + if (dx >= source.Width) + { + int diff = dx - source.Width; + rowPixels[idx] = source[dx - diff - 1, y].ToVector4(); + } + else + { + rowPixels[idx] = source[dx, y].ToVector4(); + } + + idx++; + } + + return; + } + + this.CopyPixelRowFast(source, rowPixels, x, y, tileWidth, configuration); + } + + /// + /// Get the a pixel row at a given position with a length of the tile width. + /// + /// The source image. + /// Pre-allocated pixel row span of the size of a the tile width. + /// The x position. + /// The y position. + /// The width in pixels of a tile. + /// The configuration. + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyPixelRowFast( + ImageFrame source, + Span rowPixels, + int x, + int y, + int tileWidth, + Configuration configuration) + => PixelOperations.Instance.ToVector4(configuration, source.GetPixelRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); + + /// + /// Adds a column of grey values to the histogram. + /// + /// The reference to the span of grey values to add. + /// The reference to the histogram span. + /// The number of different luminance levels. + /// The grey values span length. + [MethodImpl(InliningOptions.ShortMethod)] + private void AddPixelsToHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) + { + for (int idx = 0; idx < length; idx++) + { + int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; + } + } + + /// + /// Removes a column of grey values from the histogram. + /// + /// The reference to the span of grey values to remove. + /// The reference to the histogram span. + /// The number of different luminance levels. + /// The grey values span length. + [MethodImpl(InliningOptions.ShortMethod)] + private void RemovePixelsFromHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) + { + for (int idx = 0; idx < length; idx++) + { + int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)--; + } + } + + private class SlidingWindowInfos + { + public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixeInTile) + { + this.TileWidth = tileWidth; + this.TileHeight = tileHeight; + this.HalfTileWidth = halfTileWidth; + this.HalfTileHeight = halfTileHeight; + this.PixeInTile = pixeInTile; + } + + public int TileWidth { get; private set; } + + public int TileHeight { get; private set; } + + public int PixeInTile { get; private set; } + + public int HalfTileWidth { get; private set; } + + public int HalfTileHeight { get; private set; } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs new file mode 100644 index 000000000..9af2c8352 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// 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 in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + } + + /// + public override IImageProcessor CreatePixelSpecificProcessor() + { + return new GlobalHistogramEqualizationProcessor( + this.LuminanceLevels, + this.ClipHistogram, + this.ClipLimitPercentage); + } + } +} \ No newline at end of file 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 000000000..b3a1603e6 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -0,0 +1,107 @@ +// 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.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Applies a global histogram equalization to the image. + /// + /// The pixel format. + internal class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// Indicating whether to clip the histogram bins at a specific value. + /// Histogram clip limit in percent of the total pixels. Histogram bins which exceed this limit, will be capped at this value. + public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + int numberOfPixels = source.Width * source.Height; + Span pixels = source.GetPixelSpan(); + var workingRect = new Rectangle(0, 0, source.Width, source.Height); + + using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + { + // Build the histogram of the grayscale levels. + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + ref int histogramBase = ref MemoryMarshal.GetReference(histogramBuffer.GetSpan()); + for (int y = rows.Min; y < rows.Max; y++) + { + ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + + for (int x = 0; x < workingRect.Width; x++) + { + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.LuminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; + } + } + }); + + Span histogram = histogramBuffer.GetSpan(); + if (this.ClipHistogramEnabled) + { + this.ClipHistogram(histogram, this.ClipLimitPercentage, numberOfPixels); + } + + // Calculate the cumulative distribution function, which will map each input pixel to a new value. + int cdfMin = this.CalculateCdf( + ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), + ref MemoryMarshal.GetReference(histogram), + histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; + + // Apply the cdf to each pixel of the image + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); + for (int y = rows.Min; y < rows.Max; y++) + { + ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + + for (int x = 0; x < workingRect.Width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.LuminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs new file mode 100644 index 000000000..641587c39 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Enumerates the different types of defined histogram equalization methods. + /// + public enum HistogramEqualizationMethod : int + { + /// + /// 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 000000000..1d9d5c986 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Data container providing the different options for the histogram equalization. + /// + public class HistogramEqualizationOptions + { + /// + /// 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. Defaults to false. + /// + public bool ClipHistogram { get; set; } = false; + + /// + /// Gets or sets the histogram clip limit in percent of the total pixels in a tile. Histogram bins which exceed this limit, will be capped at this value. + /// Defaults to 0.035f. + /// + public float ClipLimitPercentage { get; set; } = 0.035f; + + /// + /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. Defaults to 10. + /// + public int NumberOfTiles { get; set; } = 10; + } +} diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index 580adc7fe..b1d12f847 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. // 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. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) { - Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); - this.LuminanceLevels = luminanceLevels; + this.ClipHistogram = clipHistogram; + this.ClipLimitPercentage = clipLimitPercentage; } /// @@ -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 in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// + public float ClipLimitPercentage { get; } - pixels[i].FromVector4(new Vector4(luminanceEqualized)); - } - } - } + /// + public abstract IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, 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.ClipLimitPercentage); 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.ClipLimitPercentage, + options.NumberOfTiles); + break; - return cdfMin; - } + case HistogramEqualizationMethod.AdaptiveSlidingWindow: + processor = new AdaptiveHistogramEqualizationSlidingWindowProcessor( + options.LuminanceLevels, + options.ClipHistogram, + options.ClipLimitPercentage, + 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.ClipLimitPercentage); + break; + } - return luminance; + return processor; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs new file mode 100644 index 000000000..8dbc903f2 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -0,0 +1,139 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +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 : struct, IPixel + { + private readonly float luminanceLevelsFloat; + + /// + /// Initializes a new instance of the class. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicates, if histogram bins should be clipped. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) + { + Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); + Guard.MustBeGreaterThan(clipLimitPercentage, 0F, nameof(clipLimitPercentage)); + + this.LuminanceLevels = luminanceLevels; + this.luminanceLevelsFloat = luminanceLevels; + this.ClipHistogramEnabled = clipHistogram; + this.ClipLimitPercentage = clipLimitPercentage; + } + + /// + /// 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 in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// + public float ClipLimitPercentage { get; } + + /// + /// Calculates the cumulative distribution function. + /// + /// The reference to the array holding the cdf. + /// The reference to the histogram of the input image. + /// Index of the maximum of the histogram. + /// The first none zero value of the cdf. + public int CalculateCdf(ref int cdfBase, ref int histogramBase, int maxIdx) + { + int histSum = 0; + int cdfMin = 0; + bool cdfMinFound = false; + + for (int i = 0; i <= maxIdx; i++) + { + histSum += Unsafe.Add(ref histogramBase, i); + if (!cdfMinFound && histSum != 0) + { + cdfMin = histSum; + cdfMinFound = true; + } + + // 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 in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The numbers of pixels inside the tile. + public void ClipHistogram(Span histogram, float clipLimitPercentage, int pixelCount) + { + int clipLimit = (int)MathF.Round(pixelCount * clipLimitPercentage); + int sumOverClip = 0; + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + for (int i = 0; i < histogram.Length; i++) + { + ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); + if (histogramLevel > clipLimit) + { + sumOverClip += histogramLevel - clipLimit; + histogramLevel = clipLimit; + } + } + + int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; + if (addToEachBin > 0) + { + for (int i = 0; i < histogram.Length; i++) + { + Unsafe.Add(ref histogramBase, i) += addToEachBin; + } + } + } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The pixel to get the luminance from + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) + { + var vector = sourcePixel.ToVector4(); + return GetLuminance(ref vector, luminanceLevels); + } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The vector to get the luminance from + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(ref Vector4 vector, int luminanceLevels) + => (int)MathF.Round(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs index 25787ff92..62848172a 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. // 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 to set the background color to. /// The options defining blending algorithm and amount. - public BackgroundColorProcessor(TPixel color, GraphicsOptions options) + public BackgroundColorProcessor(Color color, GraphicsOptions options) { this.Color = color; this.GraphicsOptions = options; @@ -39,69 +29,13 @@ 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) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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()); - } - }); - } + return new BackgroundColorProcessor(this); } } } \ 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 000000000..c4af59fec --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -0,0 +1,101 @@ +// 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.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Overlays +{ + /// + /// Sets the background color of the image. + /// + /// The pixel format. + internal class BackgroundColorProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly BackgroundColorProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + public BackgroundColorProcessor(BackgroundColorProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + TPixel color = this.definition.Color.ToPixel(); + GraphicsOptions graphicsOptions = this.definition.GraphicsOptions; + + 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(color); + amountSpan.Fill(graphicsOptions.BlendPercentage); + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); + + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destination = + source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); + + // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one + blender.Blend( + source.Configuration, + destination, + colors.GetSpan(), + destination, + amount.GetSpan()); + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 21f6be69f..255be5ed5 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -1,158 +1,78 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Buffers; -using System.Numerics; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { /// - /// 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. + /// Initializes a new instance of the class. /// /// The color or the glow. - public GlowProcessor(TPixel color) + public GlowProcessor(Color color) : this(color, 0) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The color or the glow. - /// The radius of the glow. - public GlowProcessor(TPixel color, ValueSize radius) - : this(color, radius, GraphicsOptions.Default) + /// The options effecting blending and composition. + public GlowProcessor(Color color, GraphicsOptions options) + : this(color, 0, options) { } /// - /// 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) + /// The radius of the glow. + internal GlowProcessor(Color color, ValueSize radius) + : this(color, radius, GraphicsOptions.Default) { } /// - /// Initializes a new instance of the class. + /// 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(Color color, ValueSize radius, GraphicsOptions options) { this.GlowColor = color; this.Radius = radius; - this.blender = PixelOperations.Instance.GetPixelBlender(options); this.GraphicsOptions = options; } /// - /// Gets the options effecting blending and composition + /// 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) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - // 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); - } - }); - } + return new GlowProcessor(this); } } } \ 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 000000000..3201fcbfe --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -0,0 +1,112 @@ +// 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.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Overlays +{ + /// + /// An that applies a radial glow effect an . + /// + /// The pixel format. + internal class GlowProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly PixelBlender blender; + + private readonly GlowProcessor definition; + + public GlowProcessor(GlowProcessor definition) + { + this.definition = definition; + this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); + } + + /// + 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.definition.GlowColor.ToPixel(); + Vector2 center = Rectangle.Center(sourceRectangle); + + float finalRadius = this.definition.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); + + float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; + + 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] = + (blendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); + } + + Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend( + source.Configuration, + destination, + destination, + rowColors.GetSpan(), + amountsSpan); + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index a8fa1d65c..66bf9f1af 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -1,63 +1,48 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Buffers; -using System.Numerics; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { /// - /// 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. + /// Initializes a new instance of the class. /// /// The color of the vignette. - public VignetteProcessor(TPixel color) + public VignetteProcessor(Color 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) + public VignetteProcessor(Color color, GraphicsOptions options) { 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 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(Color color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) { this.VignetteColor = color; this.RadiusX = radiusX; this.RadiusY = radiusY; - this.blender = PixelOperations.Instance.GetPixelBlender(options); this.GraphicsOptions = options; } @@ -67,94 +52,25 @@ 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; } + internal ValueSize RadiusY { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - TPixel vignetteColor = this.VignetteColor; - Vector2 centre = Rectangle.Center(sourceRectangle); - - Size sourceSize = source.Size(); - float finalRadiusX = this.RadiusX.Calculate(sourceSize); - float finalRadiusY = this.RadiusY.Calculate(sourceSize); - float rX = finalRadiusX > 0 ? MathF.Min(finalRadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; - float rY = finalRadiusY > 0 ? MathF.Min(finalRadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; - float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - - int width = maxX - minX; - int offsetX = minX - startX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - - using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width)) - { - rowColors.GetSpan().Fill(vignetteColor); - - ParallelHelper.IterateRowsWithTempBuffer( - workingRect, - configuration, - (rows, amounts) => - { - Span amountsSpan = amounts.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - startY; - - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = - (this.GraphicsOptions.BlendPercentage * (.9F * (distance / maxDistance))).Clamp( - 0, - 1); - } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend( - source.Configuration, - destination, - destination, - rowColors.GetSpan(), - amountsSpan); - } - }); - } + return new VignetteProcessor(this); } } } \ 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 000000000..78c3cec5e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -0,0 +1,114 @@ +// 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.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Overlays +{ + /// + /// An that applies a radial vignette effect to an . + /// + /// The pixel format. + internal class VignetteProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly PixelBlender blender; + + private readonly VignetteProcessor definition; + + public VignetteProcessor(VignetteProcessor definition) + { + this.definition = definition; + this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); + } + + /// + 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.definition.VignetteColor.ToPixel(); + Vector2 centre = Rectangle.Center(sourceRectangle); + + Size sourceSize = source.Size(); + float finalRadiusX = this.definition.RadiusX.Calculate(sourceSize); + float finalRadiusY = this.definition.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); + float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; + + 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] = (blendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); + } + + Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend( + source.Configuration, + destination, + destination, + rowColors.GetSpan(), + amountsSpan); + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs similarity index 79% rename from src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs index 3b9b046a0..e6ffecc84 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The base class for all implementations /// /// The pixel format. - public abstract class FrameQuantizerBase : IFrameQuantizer + public abstract class FrameQuantizer : IFrameQuantizer where TPixel : struct, IPixel { /// @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private Vector4[] paletteVector; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The quantizer /// @@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// only call the method. /// If two passes are required, the code will also call . /// - protected FrameQuantizerBase(IQuantizer quantizer, bool singlePass) + protected FrameQuantizer(IQuantizer quantizer, bool singlePass) { Guard.NotNull(quantizer, nameof(quantizer)); @@ -53,14 +54,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.singlePass = singlePass; } - /// - public bool Dither { get; } + /// + /// Initializes a new instance of the class. + /// + /// The diffuser + /// + /// 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 FrameQuantizer(IErrorDiffuser diffuser, bool singlePass) + { + this.Diffuser = diffuser; + this.Dither = this.Diffuser != null; + this.singlePass = singlePass; + } /// public IErrorDiffuser Diffuser { get; } + /// + public bool Dither { get; } + /// - public virtual QuantizedFrame QuantizeFrame(ImageFrame image) + public virtual void Dispose() + { + } + + /// + public IQuantizedFrame QuantizeFrame(ImageFrame image) { Guard.NotNull(image, nameof(image)); @@ -77,22 +102,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } // Collect the palette. Required before the second pass runs. - TPixel[] palette = this.GetPalette(); + ReadOnlyMemory palette = this.GetPalette(); this.paletteVector = new Vector4[palette.Length]; - PixelOperations.Instance.ToScaledVector4(image.Configuration, palette, this.paletteVector); + PixelOperations.Instance.ToVector4( + image.Configuration, + palette.Span, + (Span)this.paletteVector, + PixelConversionModifiers.Scale); + var quantizedFrame = new QuantizedFrame(image.MemoryAllocator, width, height, palette); + Span pixelSpan = quantizedFrame.GetWritablePixelSpan(); 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); + this.SecondPass(clone, pixelSpan, palette.Span, width, height); } } else { - this.SecondPass(image, quantizedFrame.GetPixelSpan(), palette, width, height); + this.SecondPass(image, pixelSpan, palette.Span, width, height); } return quantizedFrame; @@ -109,19 +140,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - /// Execute a second pass through the image to assign the pixels to a palette entry. + /// Returns the closest color from the palette to the given color by calculating the + /// Euclidean distance in the Rgba colorspace. /// - /// 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); + /// 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); + } /// /// Retrieve the palette for the quantized image. @@ -129,7 +163,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// - protected abstract TPixel[] GetPalette(); + protected abstract ReadOnlyMemory GetPalette(); /// /// Returns the index of the first instance of the transparent color in the palette. @@ -155,22 +189,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - /// Returns the closest color from the palette to the given color by calculating the - /// Euclidean distance in the Rgba colorspace. + /// Execute a second pass through the image to assign the pixels to a palette entry. /// - /// 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); - } + /// 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); [MethodImpl(MethodImplOptions.NoInlining)] private byte GetClosestPixelSlow(ref TPixel pixel) diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 50fdb5b58..54dabab0a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -10,7 +11,7 @@ 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 + public interface IFrameQuantizer : IDisposable where TPixel : struct, IPixel { /// @@ -30,6 +31,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// A representing a quantized version of the image pixels. /// - QuantizedFrame QuantizeFrame(ImageFrame image); + IQuantizedFrame QuantizeFrame(ImageFrame image); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs new file mode 100644 index 000000000..42016459b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs @@ -0,0 +1,38 @@ +// 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.Quantization +{ + /// + /// Defines an abstraction to represent a quantized image frame where the pixels indexed by a color palette. + /// + /// The pixel format. + public interface IQuantizedFrame : IDisposable + where TPixel : struct, IPixel + { + /// + /// Gets the width of this . + /// + int Width { get; } + + /// + /// Gets the height of this . + /// + int Height { get; } + + /// + /// Gets the color palette of this . + /// + ReadOnlyMemory Palette { get; } + + /// + /// Gets the pixels of this . + /// + /// The The pixel span. + ReadOnlySpan GetPixelSpan(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index dd56375f6..85a4d2029 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class OctreeFrameQuantizer : FrameQuantizerBase + internal sealed class OctreeFrameQuantizer : FrameQuantizer where TPixel : struct, IPixel { /// @@ -136,10 +136,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } - internal TPixel[] AotGetPalette() => this.GetPalette(); + internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); /// - protected override TPixel[] GetPalette() => this.octree.Palletize(this.colors); + protected override ReadOnlyMemory GetPalette() => this.octree.Palletize(this.colors); /// /// Process the pixel in the second pass of the algorithm. diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index d49023886..f5fa8c95d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image + /// Whether to apply dithering to the output image. public OctreeQuantizer(bool dither) : this(GetDiffuser(dither), QuantizerConstants.MaxColors) { @@ -44,7 +44,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// 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. + /// Whether to apply dithering to the output image. + public OctreeQuantizer(bool dither, int maxColors) + : this(GetDiffuser(dither), 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) { @@ -53,8 +63,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// 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 + /// The error diffusion algorithm, if any, to apply to the output image. + /// The maximum number of colors to hold in the color palette. public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors) { this.Diffuser = diffuser; diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index f8a19f8c4..265c343e6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -14,21 +15,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class PaletteFrameQuantizer : FrameQuantizerBase + internal sealed class PaletteFrameQuantizer : FrameQuantizer where TPixel : struct, IPixel { /// /// The reduced image palette. /// - private readonly TPixel[] palette; + private readonly ReadOnlyMemory palette; /// /// Initializes a new instance of the class. /// - /// The palette quantizer. + /// The palette quantizer. /// An array of all colors in the palette. - public PaletteFrameQuantizer(IQuantizer quantizer, TPixel[] colors) - : base(quantizer, true) => this.palette = colors; + public PaletteFrameQuantizer(IErrorDiffuser diffuser, ReadOnlyMemory colors) + : base(diffuser, true) => this.palette = colors; /// protected override void SecondPass( @@ -85,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override TPixel[] GetPalette() => this.palette; + protected override ReadOnlyMemory GetPalette() => this.palette; /// /// Process the pixel in the second pass of the algorithm diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 6b2be3d03..ba434a4b2 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -14,61 +15,65 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// By default the quantizer uses dithering. /// /// - public abstract class PaletteQuantizer : IQuantizer + public class PaletteQuantizer : IQuantizer { /// /// Initializes a new instance of the class. /// - protected PaletteQuantizer() - : this(true) + /// The palette. + public PaletteQuantizer(ReadOnlyMemory palette) + : this(palette, true) { } /// /// Initializes a new instance of the class. /// + /// The palette. /// Whether to apply dithering to the output image - protected PaletteQuantizer(bool dither) - : this(GetDiffuser(dither)) + public PaletteQuantizer(ReadOnlyMemory palette, bool dither) + : this(palette, GetDiffuser(dither)) { } /// /// Initializes a new instance of the class. /// + /// The palette. /// The error diffusion algorithm, if any, to apply to the output image - protected PaletteQuantizer(IErrorDiffuser diffuser) => this.Diffuser = diffuser; + public PaletteQuantizer(ReadOnlyMemory palette, IErrorDiffuser diffuser) + { + this.Palette = palette; + this.Diffuser = diffuser; + } /// public IErrorDiffuser Diffuser { get; } - /// - public abstract IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel; - - /// - public abstract IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel; - /// - /// Creates the generic frame quantizer. + /// Gets the palette. /// - /// 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) + public ReadOnlyMemory Palette { get; } + + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) where TPixel : struct, IPixel { - int max = Math.Min(QuantizerConstants.MaxColors, Math.Min(maxColors, palette.Length)); + TPixel[] palette = new TPixel[this.Palette.Length]; + Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan()); + return new PaletteFrameQuantizer(this.Diffuser, palette); + } - if (max != palette.Length) - { - return new PaletteFrameQuantizer(this, palette.AsSpan(0, max).ToArray()); - } + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + where TPixel : struct, IPixel + { + maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + int max = Math.Min(maxColors, this.Palette.Length); - return new PaletteFrameQuantizer(this, palette); + TPixel[] palette = new TPixel[max]; + Color.ToPixel(configuration, this.Palette.Span.Slice(0, max), palette.AsSpan()); + return new PaletteFrameQuantizer(this.Diffuser, palette); } private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs deleted file mode 100644 index a350adfc0..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs +++ /dev/null @@ -1,110 +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.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// A generic palette quantizer. - /// - /// The pixel format. - public class PaletteQuantizer : IQuantizer - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The color palette to use. - public PaletteQuantizer(TPixel[] palette) - : this(palette, true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The color palette to use. - /// Whether to apply dithering to the output image - public PaletteQuantizer(TPixel[] palette, bool dither) - : this(palette, GetDiffuser(dither)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The color palette to use. - /// The error diffusion algorithm, if any, to apply to the output image - public PaletteQuantizer(TPixel[] palette, IErrorDiffuser diffuser) - { - Guard.MustBeBetweenOrEqualTo(palette.Length, QuantizerConstants.MinColors, QuantizerConstants.MaxColors, nameof(palette)); - this.Palette = palette; - this.Diffuser = diffuser; - } - - /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the palette. - /// - public TPixel[] Palette { get; } - - /// - /// Creates the generic frame quantizer. - /// - /// The to configure internal operations. - /// The . - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - => ((IQuantizer)this).CreateFrameQuantizer(configuration); - - /// - /// 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); - - /// - IFrameQuantizer IQuantizer.CreateFrameQuantizer(Configuration configuration) - { - 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."); - } - - 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); - } - - 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 8da89bf94..0aee0b483 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs @@ -1,59 +1,34 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// 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; } /// - /// Gets the quantizer + /// Gets the quantizer. /// public IQuantizer Quantizer { get; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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])]; - } - } - } + return new QuantizeProcessor(this.Quantizer); } } } \ 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 000000000..5cb45e8d7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// 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 : struct, IPixel + { + private readonly IQuantizer quantizer; + + /// + /// Initializes a new instance of the class. + /// + /// The quantizer used to reduce the color palette. + public QuantizeProcessor(IQuantizer quantizer) + { + Guard.NotNull(quantizer, nameof(quantizer)); + this.quantizer = quantizer; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration)) + using (IQuantizedFrame quantized = frameQuantizer.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(); + + ReadOnlySpan paletteSpan = quantized.Palette.Span; + + int yy = y * source.Width; + + for (int x = 0; x < source.Width; x++) + { + int i = x + yy; + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs new file mode 100644 index 000000000..fa3d36e10 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Contains extension methods for . + /// + public static class QuantizedFrameExtensions + { + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The . + /// The row. + /// The pixel type. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public static ReadOnlySpan GetRowSpan(this IQuantizedFrame frame, int rowIndex) + where TPixel : struct, IPixel + => frame.GetPixelSpan().Slice(rowIndex * frame.Width, frame.Width); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index 38862ef44..cbea82c1f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -8,14 +8,13 @@ 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 + public class QuantizedFrame : IQuantizedFrame where TPixel : struct, IPixel { private IMemoryOwner pixels; @@ -27,7 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The image width. /// The image height. /// The color palette. - public QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, TPixel[] palette) + internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlyMemory palette) { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); @@ -51,23 +50,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Gets the color palette of this . /// - public TPixel[] Palette { get; private set; } + public ReadOnlyMemory 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 ReadOnlySpan GetPixelSpan() => this.pixels.GetSpan(); /// public void Dispose() @@ -76,5 +66,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.pixels = null; this.Palette = null; } + + /// + /// Get the non-readonly span of pixel data so can fill it. + /// + internal Span GetWritablePixelSpan() => this.pixels.GetSpan(); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index 93630a916..3f6dfde28 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -15,6 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// public WebSafePaletteQuantizer() + : this(true) { } @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Whether to apply dithering to the output image public WebSafePaletteQuantizer(bool dither) - : base(dither) + : base(Color.WebSafePalette, dither) { } @@ -32,16 +33,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The error diffusion algorithm, if any, to apply to the output image public WebSafePaletteQuantizer(IErrorDiffuser diffuser) - : base(diffuser) + : base(Color.WebSafePalette, diffuser) { } - - /// - public override IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - => this.CreateFrameQuantizer(configuration, NamedColors.WebSafePalette.Length); - - /// - public override IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - => this.CreateFrameQuantizer(configuration, NamedColors.WebSafePalette, maxColors); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index 2ff9f5090..d659ecabf 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// public WernerPaletteQuantizer() + : this(true) { } @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Whether to apply dithering to the output image public WernerPaletteQuantizer(bool dither) - : base(dither) + : base(Color.WernerPalette, dither) { } @@ -33,16 +34,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The error diffusion algorithm, if any, to apply to the output image public WernerPaletteQuantizer(IErrorDiffuser diffuser) - : base(diffuser) + : base(Color.WernerPalette, diffuser) { } - - /// - public override IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - => this.CreateFrameQuantizer(configuration, NamedColors.WernerPalette.Length); - - /// - public override IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - => this.CreateFrameQuantizer(configuration, NamedColors.WernerPalette, maxColors); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 44df226cf..2b1a24aee 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -11,6 +11,8 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; +// TODO: Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? +// (T, R, G, B, A, M2) could be grouped together! Investigate a ColorMoment struct. namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// @@ -33,23 +35,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class WuFrameQuantizer : FrameQuantizerBase + internal sealed class WuFrameQuantizer : FrameQuantizer 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 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. + /// The index bits. 6 in original code. /// private const int IndexBits = 5; /// - /// The index alpha bits. Keep separate for now to allow easy adjustment. + /// The index alpha bits. 3 in original code. /// private const int IndexAlphaBits = 5; @@ -64,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; /// - /// The table length. Now 1185921. + /// The table length. Now 1185921. originally 2471625. /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; @@ -96,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Moment of c^2*P(c). /// - private IMemoryOwner m2; + private IMemoryOwner m2; /// /// Color space tag. @@ -121,62 +120,67 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// + /// The . /// 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) + public WuFrameQuantizer(MemoryAllocator memoryAllocator, WuQuantizer quantizer) + : this(memoryAllocator, quantizer, quantizer.MaxColors) { } /// /// Initializes a new instance of the class. /// + /// The . /// 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 WuFrameQuantizer(MemoryAllocator memoryAllocator, WuQuantizer quantizer, int maxColors) + : base(quantizer, false) + { + Guard.NotNull(memoryAllocator, nameof(memoryAllocator)); + + this.vwt = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmr = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmg = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmb = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vma = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.m2 = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.tag = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + + this.colors = maxColors; + } /// - public override QuantizedFrame QuantizeFrame(ImageFrame image) + public override void Dispose() { - 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(); - } + this.vwt?.Dispose(); + this.vmr?.Dispose(); + this.vmg?.Dispose(); + this.vmb?.Dispose(); + this.vma?.Dispose(); + this.m2?.Dispose(); + this.tag?.Dispose(); + + this.vwt = null; + this.vmr = null; + this.vmg = null; + this.vmb = null; + this.vma = null; + this.m2 = null; + this.tag = null; } - internal TPixel[] AotGetPalette() => this.GetPalette(); + internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); /// - protected override TPixel[] GetPalette() + protected override ReadOnlyMemory GetPalette() { if (this.palette is null) { @@ -275,9 +279,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [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; + 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; } /// @@ -288,26 +297,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// 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)]; + return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; } /// - /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (depending on direction). + /// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction). /// /// The cube. /// The direction. @@ -319,47 +328,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { // 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)]; + return -moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Green case 2: - return -moment[GetPaletteIndex(cube.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)]; + return -moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Blue case 1: - return -moment[GetPaletteIndex(cube.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)]; + return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Alpha case 0: - return -moment[GetPaletteIndex(cube.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)]; + return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; default: throw new ArgumentOutOfRangeException(nameof(direction)); @@ -367,7 +376,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction). + /// Computes remainder of Volume(cube, moment), substituting position for RMax, GMax, BMax, or AMax (depending on direction). /// /// The cube. /// The direction. @@ -380,47 +389,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { // 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)]; + return moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)]; // Green case 2: - return moment[GetPaletteIndex(cube.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)]; + return moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)]; // Blue case 1: - return moment[GetPaletteIndex(cube.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)]; + return moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)]; // Alpha case 0: - return moment[GetPaletteIndex(cube.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)]; + return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)] + - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)]; default: throw new ArgumentOutOfRangeException(nameof(direction)); @@ -440,7 +449,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Span vmgSpan = this.vmg.GetSpan(); Span vmbSpan = this.vmb.GetSpan(); Span vmaSpan = this.vma.GetSpan(); - Span m2Span = this.m2.GetSpan(); + Span m2Span = this.m2.GetSpan(); // Build up the 3-D color histogram // Loop through each row @@ -489,34 +498,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Span vmgSpan = this.vmg.GetSpan(); Span vmbSpan = this.vmb.GetSpan(); Span vmaSpan = this.vma.GetSpan(); - Span m2Span = this.m2.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 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)) + 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 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(); + Span area2Span = area2.GetSpan(); for (int r = 1; r < IndexCount; r++) { @@ -543,7 +552,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization long lineG = 0; long lineB = 0; long lineA = 0; - float line2 = 0; + double line2 = 0; for (int a = 1; a < IndexAlphaCount; a++) { @@ -592,35 +601,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The cube. /// The . - private float Variance(ref Box cube) + private double Variance(ref Box cube) { float dr = Volume(ref cube, this.vmr.GetSpan()); float dg = Volume(ref cube, this.vmg.GetSpan()); float db = Volume(ref cube, this.vmb.GetSpan()); float da = Volume(ref cube, this.vma.GetSpan()); - Span m2Span = this.m2.GetSpan(); - - 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)]; + Span m2Span = this.m2.GetSpan(); + + double moment = + m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; var vector = new Vector4(dr, dg, db, da); - return xx - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.GetSpan())); + return moment - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.GetSpan())); } /// @@ -714,10 +723,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization 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); + float maxr = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxg = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxb = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxa = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW); int dir; @@ -743,48 +752,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization dir = 0; } - set2.R1 = set1.R1; - set2.G1 = set1.G1; - set2.B1 = set1.B1; - set2.A1 = set1.A1; + set2.RMax = set1.RMax; + set2.GMax = set1.GMax; + set2.BMax = set1.BMax; + set2.AMax = set1.AMax; switch (dir) { // Red case 3: - set2.R0 = set1.R1 = cutr; - set2.G0 = set1.G0; - set2.B0 = set1.B0; - set2.A0 = set1.A0; + set2.RMin = set1.RMax = cutr; + set2.GMin = set1.GMin; + set2.BMin = set1.BMin; + set2.AMin = set1.AMin; break; // Green case 2: - set2.G0 = set1.G1 = cutg; - set2.R0 = set1.R0; - set2.B0 = set1.B0; - set2.A0 = set1.A0; + set2.GMin = set1.GMax = cutg; + set2.RMin = set1.RMin; + set2.BMin = set1.BMin; + set2.AMin = set1.AMin; break; // Blue case 1: - set2.B0 = set1.B1 = cutb; - set2.R0 = set1.R0; - set2.G0 = set1.G0; - set2.A0 = set1.A0; + set2.BMin = set1.BMax = cutb; + set2.RMin = set1.RMin; + set2.GMin = set1.GMin; + set2.AMin = set1.AMin; break; // Alpha case 0: - set2.A0 = set1.A1 = cuta; - set2.R0 = set1.R0; - set2.G0 = set1.G0; - set2.B0 = set1.B0; + set2.AMin = set1.AMax = cuta; + set2.RMin = set1.RMin; + set2.GMin = set1.GMin; + set2.BMin = set1.BMin; 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); + 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; } @@ -798,13 +807,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { Span tagSpan = this.tag.GetSpan(); - for (int r = cube.R0 + 1; r <= cube.R1; r++) + for (int r = cube.RMin + 1; r <= cube.RMax; r++) { - for (int g = cube.G0 + 1; g <= cube.G1; g++) + for (int g = cube.GMin + 1; g <= cube.GMax; g++) { - for (int b = cube.B0 + 1; b <= cube.B1; b++) + for (int b = cube.BMin + 1; b <= cube.BMax; b++) { - for (int a = cube.A0 + 1; a <= cube.A1; a++) + for (int a = cube.AMin + 1; a <= cube.AMax; a++) { tagSpan[GetPaletteIndex(r, g, b, a)] = label; } @@ -819,12 +828,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private void BuildCube() { this.colorCube = new Box[this.colors]; - float[] vv = new float[this.colors]; + double[] vv = new double[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; + cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; + cube.RMax = cube.GMax = cube.BMax = IndexCount - 1; + cube.AMax = IndexAlphaCount - 1; int next = 0; @@ -839,13 +848,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } else { - vv[next] = 0F; + vv[next] = 0D; i--; } next = 0; - float temp = vv[0]; + double temp = vv[0]; for (int k = 1; k <= i; k++) { if (vv[k] > temp) @@ -855,7 +864,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } - if (temp <= 0F) + if (temp <= 0D) { this.colors = i + 1; break; @@ -897,52 +906,83 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Represents a box color cube. /// - private struct Box + private struct Box : IEquatable { /// /// Gets or sets the min red value, exclusive. /// - public int R0; + public int RMin; /// /// Gets or sets the max red value, inclusive. /// - public int R1; + public int RMax; /// /// Gets or sets the min green value, exclusive. /// - public int G0; + public int GMin; /// /// Gets or sets the max green value, inclusive. /// - public int G1; + public int GMax; /// /// Gets or sets the min blue value, exclusive. /// - public int B0; + public int BMin; /// /// Gets or sets the max blue value, inclusive. /// - public int B1; + public int BMax; /// /// Gets or sets the min alpha value, exclusive. /// - public int A0; + public int AMin; /// /// Gets or sets the max alpha value, inclusive. /// - public int A1; + public int AMax; /// /// Gets or sets the volume. /// public int Volume; + + /// + public override bool Equals(object obj) => obj is Box box && this.Equals(box); + + /// + public bool Equals(Box other) => + this.RMin == other.RMin + && this.RMax == other.RMax + && this.GMin == other.GMin + && this.GMax == other.GMax + && this.BMin == other.BMin + && this.BMax == other.BMax + && this.AMin == other.AMin + && this.AMax == other.AMax + && this.Volume == other.Volume; + + /// + public override int GetHashCode() + { + HashCode hash = default; + hash.Add(this.RMin); + hash.Add(this.RMax); + hash.Add(this.GMin); + hash.Add(this.GMax); + hash.Add(this.BMin); + hash.Add(this.BMax); + hash.Add(this.AMin); + hash.Add(this.AMax); + hash.Add(this.Volume); + return hash.ToHashCode(); + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index eb8b0fec9..b80cedeb3 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -72,14 +72,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) where TPixel : struct, IPixel - => new WuFrameQuantizer(this); + { + Guard.NotNull(configuration, nameof(configuration)); + return new WuFrameQuantizer(configuration.MemoryAllocator, this); + } /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) where TPixel : struct, IPixel { + Guard.NotNull(configuration, nameof(configuration)); maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - return new WuFrameQuantizer(this, maxColors); + return new WuFrameQuantizer(configuration.MemoryAllocator, this, maxColors); } private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 5a3b5943b..713f04265 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -1,26 +1,20 @@ // 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. + /// Defines an affine transformation applicable on an . /// - /// The pixel format. - internal class AffineTransformProcessor : TransformProcessorBase - where TPixel : struct, IPixel + public class AffineTransformProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The transform matrix. /// The sampler to perform the transform operation. @@ -48,95 +42,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public Size TargetDimensions { get; } - /// - protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + /// + public virtual IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - // 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(); - } + return new AffineTransformProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs new file mode 100644 index 000000000..529a49bf7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -0,0 +1,136 @@ +// 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 : TransformProcessor + where TPixel : struct, IPixel + { + public AffineTransformProcessor(AffineTransformProcessor definition) + { + this.Definition = definition; + } + + protected AffineTransformProcessor Definition { get; } + + private Size TargetDimensions => this.Definition.TargetDimensions; + + private Matrix3x2 TransformMatrix => this.Definition.TransformMatrix; + + /// + 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, ImageFrame>( + 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); + + var sampler = this.Definition.Sampler; + + if (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(), sampler); + try + { + ParallelHelper.IterateRowsWithTempBuffer( + targetBounds, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); + ref float ySpanRef = ref kernel.GetYStartReference(y); + ref float xSpanRef = ref kernel.GetXStartReference(y); + + for (int x = 0; x < width; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), matrix); + kernel.Convolve( + point, + x, + ref ySpanRef, + ref xSpanRef, + source.PixelBuffer, + vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive( + configuration, + vectorSpan, + targetRowSpan); + } + }); + } + finally + { + kernel.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs index a610ae5bb..9bbbba843 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs @@ -1,103 +1,21 @@ // 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 + public sealed class AutoOrientProcessor : IImageProcessor { - /// - protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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; + return new AutoOrientProcessor(); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs new file mode 100644 index 000000000..8b3ec8690 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs @@ -0,0 +1,106 @@ +// 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.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 3b1d7e94d..76f223e03 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -1,32 +1,28 @@ // 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.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 : IImageProcessor { /// - /// 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 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public Rectangle CropRectangle { get; } - /// - protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - // 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); - } - }); + return new CropProcessor(this); } } } \ 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 000000000..25f8d7849 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -0,0 +1,80 @@ +// 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.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. + /// + /// The pixel format. + internal class CropProcessor : TransformProcessor + where TPixel : struct, IPixel + { + private readonly CropProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public CropProcessor(CropProcessor definition) + { + this.definition = definition; + } + + private Rectangle CropRectangle => this.definition.CropRectangle; + + /// + 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, ImageFrame>( + 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); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs index 6de717afd..dee5e7fb3 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. // 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,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public float Threshold { get; } - /// - protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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(); + return new EntropyCropProcessor(this); } } } \ 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 000000000..4bfeb2519 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods to allow the cropping of an image to preserve areas of highest entropy. + /// + /// The pixel format. + internal class EntropyCropProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private readonly EntropyCropProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public EntropyCropProcessor(EntropyCropProcessor definition) + { + this.definition = definition; + } + + /// + 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.definition.Threshold).Apply(temp, sourceRectangle, configuration); + + // Search for the first white pixels + rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); + } + + new CropProcessor(rectangle, source.Size()).Apply(source, sourceRectangle); + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + // All processing happens at the image level within BeforeImageApply(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index c6f5e9d7b..f604d8399 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -1,27 +1,17 @@ // 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. + /// Defines a flipping around the center point of the image. /// - /// The pixel format. - internal class FlipProcessor : ImageProcessor - where TPixel : struct, IPixel + public sealed class FlipProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The used to perform flipping. public FlipProcessor(FlipMode flipMode) @@ -34,63 +24,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public FlipMode FlipMode { get; } - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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(); - } - }); + return new FlipProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs new file mode 100644 index 000000000..024786209 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs @@ -0,0 +1,87 @@ +// 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.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +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 + { + private readonly FlipProcessor definition; + + public FlipProcessor(FlipProcessor definition) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + switch (this.definition.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/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index 0b5627e19..3a86b3fe4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -1,26 +1,20 @@ // 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. + /// Defines a projective transformation applicable to an . /// - /// The pixel format. - internal class ProjectiveTransformProcessor : TransformProcessorBase - where TPixel : struct, IPixel + public sealed class ProjectiveTransformProcessor : IImageProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The transform matrix. /// The sampler to perform the transform operation. @@ -39,103 +33,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public IResampler Sampler { get; } /// - /// Gets the matrix used to supply the projective transform + /// Gets the matrix used to supply the projective transform. /// public Matrix4x4 TransformMatrix { get; } /// - /// Gets the target dimensions to constrain the transformed image to + /// Gets the target dimensions to constrain the transformed image to. /// public Size TargetDimensions { get; } - /// - protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - // 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(); - } + return new ProjectiveTransformProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs new file mode 100644 index 000000000..80b03000b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -0,0 +1,143 @@ +// 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 : TransformProcessor + where TPixel : struct, IPixel + { + private readonly ProjectiveTransformProcessor definition; + + public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition) + { + this.definition = definition; + } + + private Size TargetDimensions => this.definition.TargetDimensions; + + /// + 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, ImageFrame>( + 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) + { + Matrix4x4 transformMatrix = this.definition.TransformMatrix; + + // Handle tranforms that result in output identical to the original. + if (transformMatrix.Equals(default) || 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(transformMatrix, out Matrix4x4 matrix); + + IResampler sampler = this.definition.Sampler; + + if (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(), sampler); + try + { + ParallelHelper.IterateRowsWithTempBuffer( + targetBounds, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); + ref float ySpanRef = ref kernel.GetYStartReference(y); + ref float xSpanRef = ref kernel.GetXStartReference(y); + + for (int x = 0; x < width; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); + kernel.Convolve( + point, + x, + ref ySpanRef, + ref xSpanRef, + source.PixelBuffer, + vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive( + configuration, + vectorSpan, + targetRowSpan); + } + }); + } + finally + { + kernel.Dispose(); + } + } + } +} diff --git a/src/ImageSharp/Processing/ResizeHelper.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs similarity index 87% rename from src/ImageSharp/Processing/ResizeHelper.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs index 3ae632162..ae7b112fc 100644 --- a/src/ImageSharp/Processing/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs @@ -1,11 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Linq; +using System.Numerics; + using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// Provides methods to help calculate the target rectangle when resizing using the @@ -13,6 +15,16 @@ namespace SixLabors.ImageSharp.Processing /// 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. /// @@ -21,9 +33,13 @@ namespace SixLabors.ImageSharp.Processing /// The target width /// The target height /// - /// The . + /// The tuple representing the location and the bounds /// - public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options, int width, int height) + public static (Size, Rectangle) CalculateTargetLocationAndBounds( + Size sourceSize, + ResizeOptions options, + int width, + int height) { switch (options.Mode) { @@ -44,7 +60,90 @@ namespace SixLabors.ImageSharp.Processing } } - private static (Size, Rectangle) CalculateCropRectangle(Size source, ResizeOptions options, int width, int height) + private static (Size, Rectangle) CalculateBoxPadRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + if (width <= 0 || height <= 0) + { + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); + } + + int 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) CalculateCropRectangle( + Size source, + ResizeOptions options, + int width, + int height) { if (width <= 0 || height <= 0) { @@ -147,152 +246,15 @@ namespace SixLabors.ImageSharp.Processing 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); + return (new Size(width, height), + new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } - private static (Size, Rectangle) CalculateMaxRectangle(Size source, ResizeOptions options, int width, int height) + private static (Size, Rectangle) CalculateMaxRectangle( + Size source, + ResizeOptions options, + int width, + int height) { int destinationWidth = width; int destinationHeight = height; @@ -320,7 +282,11 @@ namespace SixLabors.ImageSharp.Processing return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); } - private static (Size, Rectangle) CalculateMinRectangle(Size source, ResizeOptions options, int width, int height) + private static (Size, Rectangle) CalculateMinRectangle( + Size source, + ResizeOptions options, + int width, + int height) { int sourceWidth = source.Width; int sourceHeight = source.Height; @@ -330,7 +296,7 @@ namespace SixLabors.ImageSharp.Processing // Don't upscale if (width > sourceWidth || height > sourceHeight) { - return (new Size(sourceWidth, sourceWidth), new Rectangle(0, 0, sourceWidth, sourceHeight)); + return (new Size(sourceWidth, sourceHeight), new Rectangle(0, 0, sourceWidth, sourceHeight)); } // Find the shortest distance to go. @@ -372,5 +338,78 @@ namespace SixLabors.ImageSharp.Processing // Replace the size to match the rectangle. return (new Size(width, height), new Rectangle(0, 0, 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)); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index f349634ac..dce4e70d6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -19,27 +19,27 @@ 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 { get; } /// - /// Gets the the length of the kernel + /// Gets the the length of the kernel. /// public int Length { 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 +54,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 +69,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,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// - /// Copy the contents of altering + /// Copy the contents of altering /// to the value . /// internal ResizeKernel AlterLeftValue(int left) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 2ab574df2..9abbb66e3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -54,11 +54,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.radius = radius; this.sourceLength = sourceLength; this.DestinationLength = destinationLength; - int maxWidth = (radius * 2) + 1; - this.data = memoryAllocator.Allocate2D(maxWidth, bufferHeight, AllocationOptions.Clean); + this.MaxDiameter = (radius * 2) + 1; + this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); this.pinHandle = this.data.Memory.Pin(); this.kernels = new ResizeKernel[destinationLength]; - this.tempValues = new double[maxWidth]; + this.tempValues = new double[this.MaxDiameter]; } /// @@ -66,6 +66,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 /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index 189e21de7..8762d6b26 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -1,40 +1,62 @@ -// Copyright (c) Six Labors and contributors. +// 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 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 : IImageProcessor { - // 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. + /// + /// The . + /// The width. + /// The height. + /// The size of the source image. + /// The target rectangle to resize into. + /// A value indicating whether to apply RGBA companding. + public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle targetRectangle, bool compand) + { + Guard.NotNull(sampler, nameof(sampler)); + + // Ensure size is populated across both dimensions. + // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. + // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. + const int min = 1; + if (width == 0 && height > 0) + { + width = (int)MathF.Max(min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height)); + targetRectangle.Width = width; + } + + if (height == 0 && width > 0) + { + height = (int)MathF.Max(min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); + targetRectangle.Height = height; + } + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Sampler = sampler; + this.Width = width; + this.Height = height; + this.TargetRectangle = targetRectangle; + this.Compand = compand; + } /// - /// 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)); @@ -66,12 +88,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.Sampler = options.Sampler; this.Width = size.Width; this.Height = size.Height; - this.ResizeRectangle = rectangle; + this.TargetRectangle = rectangle; this.Compand = options.Compand; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The sampler to perform the resize operation. /// The target width. @@ -82,47 +104,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { } - /// - /// 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. /// @@ -141,189 +122,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Gets the resize rectangle. /// - public Rectangle ResizeRectangle { get; } + public Rectangle TargetRectangle { get; } /// /// Gets a value indicating whether to compress or expand individual pixel color values on processing. /// public bool Compand { get; } - /// - protected override Image CreateDestination(Image source, Rectangle sourceRectangle) - { - // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.Width, this.Height, x.MetaData.DeepClone())); - - // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); - } - - /// - protected override void BeforeImageApply(Image source, Image destination, Rectangle sourceRectangle) - { - if (!(this.Sampler is NearestNeighborResampler)) - { - // Since all image frame dimensions have to be the same we can calculate this for all frames. - MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); - this.horizontalKernelMap = ResizeKernelMap.Calculate( - this.Sampler, - this.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) + /// + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel { - 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; + return new ResizeProcessor(this); } } } \ 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 000000000..fb03057a4 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -0,0 +1,191 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Implements resizing of images using various resamplers. + /// + /// + /// The original code has been adapted from . + /// + /// The pixel format. + internal class ResizeProcessor : TransformProcessor + where TPixel : struct, IPixel + { + // The following fields are not immutable but are optionally created on demand. + private ResizeKernelMap horizontalKernelMap; + private ResizeKernelMap verticalKernelMap; + + private readonly ResizeProcessor parameterSource; + + public ResizeProcessor(ResizeProcessor parameterSource) + { + this.parameterSource = parameterSource; + } + + /// + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler => this.parameterSource.Sampler; + + /// + /// Gets the target width. + /// + public int Width => this.parameterSource.Width; + + /// + /// Gets the target height. + /// + public int Height => this.parameterSource.Height; + + /// + /// Gets the resize rectangle. + /// + public Rectangle TargetRectangle => this.parameterSource.TargetRectangle; + + /// + /// Gets a value indicating whether to compress or expand individual pixel color values on processing. + /// + public bool Compand => this.parameterSource.Compand; + + /// + 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, ImageFrame>( + x => new ImageFrame( + source.GetConfiguration(), + this.Width, + this.Height, + x.Metadata.DeepClone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); + } + + /// + protected override void BeforeImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + if (!(this.Sampler is NearestNeighborResampler)) + { + // Since all image frame dimensions have to be the same we can calculate this for all frames. + MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); + this.horizontalKernelMap = ResizeKernelMap.Calculate( + this.Sampler, + this.TargetRectangle.Width, + sourceRectangle.Width, + memoryAllocator); + + this.verticalKernelMap = ResizeKernelMap.Calculate( + this.Sampler, + this.TargetRectangle.Height, + sourceRectangle.Height, + memoryAllocator); + } + } + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + // Handle resize dimensions identical to the original + if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.TargetRectangle) + { + // The cloned will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + int width = this.Width; + int height = this.Height; + int sourceX = sourceRectangle.X; + int sourceY = sourceRectangle.Y; + int startY = this.TargetRectangle.Y; + int startX = this.TargetRectangle.X; + + var targetWorkingRect = Rectangle.Intersect( + this.TargetRectangle, + new Rectangle(0, 0, width, height)); + + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)this.TargetRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)this.TargetRectangle.Height; + + ParallelHelper.IterateRows( + targetWorkingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + // Y coordinates of source points + Span sourceRow = + source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); + Span targetRow = destination.GetPixelRowSpan(y); + + for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; + } + } + }); + + return; + } + + int sourceHeight = source.Height; + + PixelConversionModifiers conversionModifiers = + PixelConversionModifiers.Premultiply.ApplyCompanding(this.Compand); + + BufferArea sourceArea = source.PixelBuffer.GetArea(sourceRectangle); + + // To reintroduce parallel processing, we to launch multiple workers + // for different row intervals of the image. + using (var worker = new ResizeWorker( + configuration, + sourceArea, + conversionModifiers, + this.horizontalKernelMap, + this.verticalKernelMap, + width, + targetWorkingRect, + this.TargetRectangle.Location)) + { + worker.Initialize(); + + var workingInterval = new RowInterval(targetWorkingRect.Top, targetWorkingRect.Bottom); + worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); + } + } + + protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + base.AfterImageApply(source, destination, sourceRectangle); + + // TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable! + this.horizontalKernelMap?.Dispose(); + this.horizontalKernelMap = null; + this.verticalKernelMap?.Dispose(); + this.verticalKernelMap = null; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs new file mode 100644 index 000000000..00a8cfbf3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -0,0 +1,193 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Implements the resize algorithm using a sliding window of size + /// maximized by . + /// The height of the window is a multiple of the vertical kernel's maximum diameter. + /// When sliding the window, the contents of the bottom window band are copied to the new top band. + /// For more details, and visual explanation, see "ResizeWorker.pptx". + /// + internal class ResizeWorker : IDisposable + where TPixel : struct, IPixel + { + private readonly Buffer2D transposedFirstPassBuffer; + + private readonly Configuration configuration; + + private readonly PixelConversionModifiers conversionModifiers; + + private readonly ResizeKernelMap horizontalKernelMap; + + private readonly BufferArea 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, + BufferArea source, + PixelConversionModifiers conversionModifiers, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + int destWidth, + Rectangle targetWorkingRect, + Point targetOrigin) + { + this.configuration = configuration; + this.source = source; + this.sourceRectangle = source.Rectangle; + this.conversionModifiers = conversionModifiers; + this.horizontalKernelMap = horizontalKernelMap; + this.verticalKernelMap = verticalKernelMap; + this.destWidth = destWidth; + this.targetWorkingRect = targetWorkingRect; + this.targetOrigin = targetOrigin; + + this.windowBandHeight = verticalKernelMap.MaxDiameter; + + int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( + this.windowBandHeight, + destWidth, + configuration.WorkingBufferSizeHintInBytes); + + this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); + + this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( + this.workerHeight, + destWidth, + AllocationOptions.Clean); + + this.tempRowBuffer = configuration.MemoryAllocator.Allocate(this.sourceRectangle.Width); + this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(destWidth); + + this.currentWindow = new RowInterval(0, this.workerHeight); + } + + public void Dispose() + { + this.transposedFirstPassBuffer.Dispose(); + this.tempRowBuffer.Dispose(); + this.tempColumnBuffer.Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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(); + + 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); + + if (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 this.transposedFirstPassBuffer.Span[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(); + for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) + { + Span sourceRow = this.source.GetRowSpan(y); + + PixelOperations.Instance.ToVector4( + this.configuration, + sourceRow, + tempRowSpan, + this.conversionModifiers); + + // Span firstPassSpan = this.transposedFirstPassBuffer.Span.Slice(y - this.currentWindow.Min); + ref Vector4 firstPassBaseRef = ref this.transposedFirstPassBuffer.Span[y - this.currentWindow.Min]; + + for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++) + { + ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X); + + // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.pptx b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.pptx new file mode 100644 index 000000000..248959170 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 index 69ecf5921..ef0671d20 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -1,26 +1,19 @@ // 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. + /// Defines a rotation applicable to an . /// - /// The pixel format. - internal class RotateProcessor : AffineTransformProcessor - where TPixel : struct, IPixel + public sealed class RotateProcessor : AffineTransformProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The angle of rotation in degrees. /// The source image size @@ -30,16 +23,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// - /// Initializes a new instance of the class. + /// 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) + TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), + sampler, + sourceSize) => this.Degrees = degrees; // Helper constructor @@ -53,190 +46,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public float Degrees { get; } - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + /// + public override IImageProcessor CreatePixelSpecificProcessor() { - 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]; - } - } - } - }); + return new RotateProcessor(this); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs new file mode 100644 index 000000000..252cb77ab --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -0,0 +1,225 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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 + { + public RotateProcessor(RotateProcessor definition) + : base(definition) + { + this.Degrees = definition.Degrees; + } + + private float Degrees { get; } + + /// + 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); + } + + /// + 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); + } + + /// + /// 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 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 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 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 index c7b1d7410..cb73bb66c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -9,14 +9,12 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides methods that allow the skewing of images. + /// Defines a skew transformation applicable to an . /// - /// The pixel format. - internal class SkewProcessor : AffineTransformProcessor - where TPixel : struct, IPixel + public sealed class SkewProcessor : AffineTransformProcessor { /// - /// Initializes a new instance of the class. + /// 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. @@ -27,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// - /// Initializes a new instance of the class. + /// 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. diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs similarity index 80% rename from src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs rename to src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs index 4973b90f4..ef508549a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs @@ -11,11 +11,11 @@ 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 + internal abstract class TransformProcessor : CloningImageProcessor where TPixel : struct, IPixel { /// protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) - => TransformProcessorHelpers.UpdateDimensionalMetData(destination); + => TransformProcessorHelpers.UpdateDimensionalMetadata(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 f5536d046..00c1227a6 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. // 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,10 +16,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. /// The image to update - public static void UpdateDimensionalMetData(Image image) + public static void UpdateDimensionalMetadata(Image image) where TPixel : struct, IPixel { - ExifProfile profile = image.MetaData.ExifProfile; + ExifProfile profile = image.Metadata.ExifProfile; if (profile is null) { return; diff --git a/src/ImageSharp/Processing/QuantizeExtensions.cs b/src/ImageSharp/Processing/QuantizeExtensions.cs deleted file mode 100644 index 5bd2f49bd..000000000 --- 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/ResizeOptions.cs b/src/ImageSharp/Processing/ResizeOptions.cs index 0d5bfe38b..ee0dde6b2 100644 --- a/src/ImageSharp/Processing/ResizeOptions.cs +++ b/src/ImageSharp/Processing/ResizeOptions.cs @@ -1,8 +1,8 @@ // 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.Processing.Processors.Transforms; using SixLabors.Primitives; @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets or sets the center coordinates. /// - public IEnumerable CenterCoordinates { get; set; } = Enumerable.Empty(); + public IEnumerable CenterCoordinates { get; set; } = Array.Empty(); /// /// Gets or sets the target size. diff --git a/src/ImageSharp/Processing/RotateExtensions.cs b/src/ImageSharp/Processing/RotateExtensions.cs deleted file mode 100644 index 398a634d1..000000000 --- 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/VignetteExtensions.cs b/src/ImageSharp/Processing/VignetteExtensions.cs deleted file mode 100644 index 18dd8064c..000000000 --- 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 7b8f933b0..2862b4585 100644 --- a/src/ImageSharp/Properties/AssemblyInfo.cs +++ b/src/ImageSharp/Properties/AssemblyInfo.cs @@ -1,6 +1,11 @@ // Copyright (c) Six Labors and contributors. // 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/Shared/AssemblyInfo.Common.cs b/src/Shared/AssemblyInfo.Common.cs deleted file mode 100644 index 82e1dac6c..000000000 --- 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 new file mode 160000 index 000000000..8b085c0ec --- /dev/null +++ b/standards @@ -0,0 +1 @@ +Subproject commit 8b085c0ec4fb64797b9965741f7138f8f66a6b44 diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 000000000..97bd9b6e7 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,30 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.props + tests + + + + CS0618;$(NoWarn) + + + + + + + + + + + diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets new file mode 100644 index 000000000..f8a4936e2 --- /dev/null +++ b/tests/Directory.Build.targets @@ -0,0 +1,19 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.targets + + + + + diff --git a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs index 6db03a448..87ed8fa42 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 and contributors. +// 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 30799aabf..1ab5ed309 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs @@ -54,4 +54,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs index ff378c75c..cc946e05a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System.IO; using System.Runtime.CompilerServices; diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs index 39f09b6b6..a19d8fa91 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs @@ -11,7 +11,6 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] public class DecodePng : BenchmarkBase { diff --git a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs index a51ce8ebc..f0d7a54d0 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs @@ -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); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs index ee77a2b6b..7c3da90db 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// // This file contains small, cheap and "unit test" benchmarks to test MultiImageBenchmarkBase. // Need this because there are no real test cases for the common benchmark utility stuff. diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs index 65176af5b..3d9b54dff 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs @@ -364,22 +364,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; diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 5958b9f79..5a4a9ab17 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -47,5 +47,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg decoder.Dispose(); } } + + // RESULTS (2019 April 23): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + // Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + // .NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |---------------------------- |----- |-------- |--------------------- |---------:|---------:|----------:|------:|--------:|---------:|------:|------:|----------:| + // | 'System.Drawing FULL' | Clr | Clr | Jpg/b(...)f.jpg [28] | 18.69 ms | 8.273 ms | 0.4535 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.89 KB | + // | JpegDecoderCore.ParseStream | Clr | Clr | Jpg/b(...)f.jpg [28] | 15.76 ms | 4.266 ms | 0.2339 ms | 0.84 | 0.03 | - | - | - | 11.83 KB | + // | | | | | | | | | | | | | | + // | 'System.Drawing FULL' | Core | Core | Jpg/b(...)f.jpg [28] | 17.68 ms | 2.711 ms | 0.1486 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB | + // | JpegDecoderCore.ParseStream | Core | Core | Jpg/b(...)f.jpg [28] | 14.27 ms | 3.671 ms | 0.2012 ms | 0.81 | 0.00 | - | - | - | 11.76 KB | } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index fe112042e..62742f729 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -5,6 +5,7 @@ using System.Drawing; using System.IO; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.Formats.Jpeg; @@ -26,8 +27,7 @@ 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 @@ -35,8 +35,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public ShortClr() { this.Add( - //Job.Clr.WithLaunchCount(1).WithWarmupCount(2).WithTargetCount(3), - Job.Core.WithLaunchCount(1).WithWarmupCount(2).WithTargetCount(3) + // Job.Clr.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), + Job.Core.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3) ); } } @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private byte[] jpegBytes; private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - + [Params( TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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); } @@ -115,5 +115,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // | | | | | | | | | | | // 'Decode Jpeg - System.Drawing' | Jpg/issues/issue750-exif-tranform.jpg | 95.192 ms | 3.1762 ms | 0.1795 ms | 1.00 | 0.00 | 1750.0000 | - | - | 5492.63 KB | // 'Decode Jpeg - ImageSharp' | Jpg/issues/issue750-exif-tranform.jpg | 230.158 ms | 48.8128 ms | 2.7580 ms | 2.42 | 0.02 | 312.5000 | 312.5000 | 312.5000 | 58834.66 KB | + + // RESULTS (2019 April 23): + // + //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + //.NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + //| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + //|------------------------------- |--------------------- |-----------:|-----------:|-----------:|------:|--------:|----------:|------:|------:|------------:| + //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 6.957 ms | 9.618 ms | 0.5272 ms | 1.00 | 0.00 | 93.7500 | - | - | 205.83 KB | + //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 18.348 ms | 8.876 ms | 0.4865 ms | 2.65 | 0.23 | - | - | - | 14.49 KB | + //| | | | | | | | | | | | + //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 18.687 ms | 11.632 ms | 0.6376 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB | + //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 41.990 ms | 25.514 ms | 1.3985 ms | 2.25 | 0.10 | - | - | - | 15.48 KB | + //| | | | | | | | | | | | + //| 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 477.265 ms | 732.126 ms | 40.1303 ms | 1.00 | 0.00 | 3000.0000 | - | - | 7403.76 KB | + //| 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 348.545 ms | 91.480 ms | 5.0143 ms | 0.73 | 0.06 | - | - | - | 35177.21 KB | } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs index d4cbe81e1..b0834eb52 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs @@ -19,8 +19,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private MemoryStream stream2; private MemoryStream stream3; private MemoryStream stream4; - DoubleBufferedStreamReader reader1; - DoubleBufferedStreamReader reader2; + private DoubleBufferedStreamReader reader1; + private DoubleBufferedStreamReader reader2; [GlobalSetup] public void CreateStreams() @@ -102,6 +102,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg return r; } + [Benchmark] + public int SimpleReadByte() + { + byte[] b = this.buffer; + int r = 0; + for (int i = 0; i < b.Length; i++) + { + r += b[i]; + } + + return r; + } + private static byte[] CreateTestBytes() { byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3]; @@ -111,4 +124,29 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg return buffer; } } + + // RESULTS (2019 April 24): + // + //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + //.NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + //IterationCount=3 LaunchCount=1 WarmupCount=3 + // + //| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + //|----------------------------- |----- |-------- |---------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| + //| StandardStreamReadByte | Clr | Clr | 96.71 us | 5.9950 us | 0.3286 us | 1.00 | 0.00 | - | - | - | - | + //| StandardStreamRead | Clr | Clr | 77.73 us | 5.2284 us | 0.2866 us | 0.80 | 0.00 | - | - | - | - | + //| DoubleBufferedStreamReadByte | Clr | Clr | 23.17 us | 26.2354 us | 1.4381 us | 0.24 | 0.01 | - | - | - | - | + //| DoubleBufferedStreamRead | Clr | Clr | 33.35 us | 3.4071 us | 0.1868 us | 0.34 | 0.00 | - | - | - | - | + //| SimpleReadByte | Clr | Clr | 10.85 us | 0.4927 us | 0.0270 us | 0.11 | 0.00 | - | - | - | - | + //| | | | | | | | | | | | | + //| StandardStreamReadByte | Core | Core | 75.35 us | 12.9789 us | 0.7114 us | 1.00 | 0.00 | - | - | - | - | + //| StandardStreamRead | Core | Core | 55.36 us | 1.4432 us | 0.0791 us | 0.73 | 0.01 | - | - | - | - | + //| DoubleBufferedStreamReadByte | Core | Core | 21.47 us | 29.7076 us | 1.6284 us | 0.28 | 0.02 | - | - | - | - | + //| DoubleBufferedStreamRead | Core | Core | 29.67 us | 2.5988 us | 0.1424 us | 0.39 | 0.00 | - | - | - | - | + //| SimpleReadByte | Core | Core | 10.84 us | 0.7567 us | 0.0415 us | 0.14 | 0.00 | - | - | - | - | } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index d934a1d6d..c617d25c0 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -1,18 +1,16 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using SixLabors.ImageSharp.PixelFormats; +using BenchmarkDotNet.Attributes; + namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { using System.Drawing; using System.Drawing.Imaging; using System.IO; - using BenchmarkDotNet.Attributes; - using CoreImage = SixLabors.ImageSharp.Image; public class EncodeJpeg : BenchmarkBase @@ -45,19 +43,19 @@ 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); } } } -} +} \ 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 05edd2791..8417b32f2 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output); + JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output, 255F, 128F); } [Benchmark] @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrSimd.ConvertCore(values, this.output); + JpegColorConverter.FromYCbCrSimd.ConvertCore(values, this.output, 255F, 128F); } [Benchmark] @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output); + JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output, 255F, 128F); } private static Buffer2D[] CreateRandomValues( diff --git a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs index 446c03859..84e2d0616 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 and contributors. // Licensed under the Apache License, Version 2.0. -// using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; @@ -18,7 +16,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs using System.Numerics; using BenchmarkDotNet.Attributes; - + using BenchmarkDotNet.Diagnosers; using SixLabors.ImageSharp.Tests; using CoreImage = ImageSharp.Image; @@ -30,7 +28,7 @@ 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 @@ -38,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public ShortClr() { this.Add( - Job.Core.WithLaunchCount(1).WithWarmupCount(1).WithTargetCount(2) + Job.Core.WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2) ); } } @@ -47,7 +45,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected Dictionary FileNamesToBytes = new Dictionary(); protected Dictionary> FileNamesToImageSharpImages = new Dictionary>(); - protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); + protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); /// /// The values of this enum separate input files into categories @@ -152,7 +150,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { foreach (KeyValuePair kv in this.FileNames2Bytes) { - using (MemoryStream memoryStream = new MemoryStream(kv.Value)) + using (var memoryStream = new MemoryStream(kv.Value)) { try { @@ -179,7 +177,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs 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); @@ -223,7 +221,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected void ForEachImageSharpImage(Func, MemoryStream, object> operation) { - using (MemoryStream workStream = new MemoryStream()) + using (var workStream = new MemoryStream()) { this.ForEachImageSharpImage( @@ -256,7 +254,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected void ForEachSystemDrawingImage(Func operation) { - using (MemoryStream workStream = new MemoryStream()) + using (var workStream = new MemoryStream()) { this.ForEachSystemDrawingImage( diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs index 2b9573ed7..8b2d08e66 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs @@ -61,13 +61,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()); } } diff --git a/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs b/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs index 02017cbb7..602e1137f 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 and contributors. // 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 cc3472e22..855f5b9b4 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs @@ -1,13 +1,13 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +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); diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs index d10999518..07870b3a8 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs @@ -1,13 +1,13 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +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 +18,6 @@ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs index da7b9c3dd..4d9ba8928 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs @@ -1,13 +1,13 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +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 +18,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 2a5754ab0..f20ffdcab 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs @@ -1,13 +1,13 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +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); diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs index 07ae17d75..b4e5ab380 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Benchmarks OnStackInputCache.Byte 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; @@ -162,7 +162,7 @@ namespace SixLabors.ImageSharp.Benchmarks OnStackInputCache.Byte 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; @@ -194,15 +194,15 @@ 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); for (int i = 0; i < this.inputSourceRGB.Length; i++) { @@ -240,23 +240,23 @@ 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++) { diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs index 92008f6e2..060a28550 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -1,13 +1,13 @@ -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces -{ - using BenchmarkDotNet.Attributes; +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 +18,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/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index b46757942..0543cbc50 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -1,20 +1,17 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +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); } public class ShortClr : Config @@ -22,9 +19,9 @@ 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.Clr.WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Core.WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3) + ); } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs index edbbceb62..8999afaed 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System.Drawing; using System.Drawing.Drawing2D; @@ -19,14 +17,14 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Baseline = true, Description = "System.Drawing Draw Beziers")] public void DrawPathSystemDrawing() { - using (Bitmap destination = new Bitmap(800, 800)) + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) { + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; - using (Graphics graphics = Graphics.FromImage(destination)) + using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) { - 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), @@ -35,9 +33,9 @@ namespace SixLabors.ImageSharp.Benchmarks }); } - using (MemoryStream ms = new MemoryStream()) + using (var stream = new MemoryStream()) { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); } } } @@ -45,7 +43,7 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Beziers")] public void DrawLinesCore() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { image.Mutate(x => x.DrawBeziers( Rgba32.HotPink, @@ -57,9 +55,9 @@ namespace SixLabors.ImageSharp.Benchmarks new Vector2(300, 500) })); - using (MemoryStream ms = new MemoryStream()) + using (var stream = new MemoryStream()) { - image.SaveAsBmp(ms); + image.SaveAsBmp(stream); } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs index 894683599..408e87f9e 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs @@ -1,32 +1,31 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors 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 System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +namespace SixLabors.ImageSharp.Benchmarks +{ public class DrawLines : BenchmarkBase { [Benchmark(Baseline = true, Description = "System.Drawing Draw Lines")] public void DrawPathSystemDrawing() { using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) { - using (var graphics = Graphics.FromImage(destination)) + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) { - 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), @@ -34,9 +33,9 @@ namespace SixLabors.ImageSharp.Benchmarks }); } - using (var ms = new MemoryStream()) + using (var stream = new MemoryStream()) { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); } } } @@ -55,9 +54,9 @@ namespace SixLabors.ImageSharp.Benchmarks new Vector2(200, 400) })); - using (var ms = new MemoryStream()) + using (var stream = new MemoryStream()) { - image.SaveAsBmp(ms); + image.SaveAsBmp(stream); } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs index 5fbd9f112..2b4cff8cb 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System.Drawing; using System.Drawing.Drawing2D; @@ -19,14 +17,13 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Baseline = true, Description = "System.Drawing Draw Polygon")] public void DrawPolygonSystemDrawing() { - using (Bitmap destination = new Bitmap(800, 800)) + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) { - - using (Graphics graphics = Graphics.FromImage(destination)) + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) { - 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), @@ -34,9 +31,9 @@ namespace SixLabors.ImageSharp.Benchmarks }); } - using (MemoryStream ms = new MemoryStream()) + using (var stream = new MemoryStream()) { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); } } } @@ -44,7 +41,7 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Polygon")] public void DrawPolygonCore() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { image.Mutate(x => x.DrawPolygon( Rgba32.HotPink, @@ -55,7 +52,7 @@ namespace SixLabors.ImageSharp.Benchmarks new Vector2(200, 400) })); - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { image.SaveAsBmp(ms); } diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs index 624b54278..0982db334 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System.Drawing; using System.Drawing.Drawing2D; @@ -14,11 +12,9 @@ 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"; @@ -28,42 +24,45 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Baseline = true, Description = "System.Drawing Draw Text")] public void DrawTextSystemDrawing() { - using (Bitmap destination = new Bitmap(800, 800)) + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) { - - using (Graphics graphics = Graphics.FromImage(destination)) + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + using (var font = new Font("Arial", 12, GraphicsUnit.Point)) { - 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)) + using (var image = new Image(800, 800)) { var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); - image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)))); + 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)) + using (var image = new Image(800, 800)) { var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))); } - IImageProcessingContext DrawTextOldVersion(IImageProcessingContext source, TextGraphicsOptions options, string text, SixLabors.Fonts.Font font, IBrush brush, IPen pen, SixLabors.Primitives.PointF location) - where TPixel : struct, IPixel + IImageProcessingContext DrawTextOldVersion( + IImageProcessingContext source, + TextGraphicsOptions options, + string text, + SixLabors.Fonts.Font font, + IBrush brush, + IPen pen, + SixLabors.Primitives.PointF location) { float dpiX = 72; float dpiY = 72; diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs index ba6d055e3..c5c1ba5ac 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System.Drawing; using System.Drawing.Drawing2D; @@ -13,30 +11,26 @@ 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 (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) { - - using (Graphics graphics = Graphics.FromImage(destination)) + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) + using (var font = new Font("Arial", 12, GraphicsUnit.Point)) + using (var gp = new GraphicsPath()) { - 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); } @@ -46,24 +40,38 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Text Outline - Cached Glyphs")] public void DrawTextCore() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); - image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10)))); + 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)) + using (var image = new Image(800, 800)) { var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); - image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10))); + 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 + IImageProcessingContext DrawTextOldVersion( + IImageProcessingContext source, + TextGraphicsOptions options, + string text, + SixLabors.Fonts.Font font, + IBrush brush, + IPen pen, + SixLabors.Primitives.PointF location) { var style = new SixLabors.Fonts.RendererOptions(font, options.DpiX, options.DpiY, location) { diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs index 8aadb85bf..396cc18d1 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System.Drawing; using System.Drawing.Drawing2D; @@ -21,31 +19,30 @@ namespace SixLabors.ImageSharp.Benchmarks public FillPolygon() { - this.shape = new Polygon(new LinearLineSegment(new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400))); + 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 (var destination = new Bitmap(800, 800)) + + using (var graphics = Graphics.FromImage(destination)) { + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.FillPolygon(System.Drawing.Brushes.HotPink, + new[] { + new Point(10, 10), + new Point(550, 50), + new Point(200, 400) + }); - using (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()) + using (var stream = new MemoryStream()) { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); } } } @@ -53,7 +50,7 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill Polygon")] public void DrawSolidPolygonCore() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { image.Mutate(x => x.FillPolygon( Rgba32.HotPink, @@ -63,9 +60,9 @@ namespace SixLabors.ImageSharp.Benchmarks new Vector2(200, 400) })); - using (MemoryStream ms = new MemoryStream()) + using (var stream = new MemoryStream()) { - image.SaveAsBmp(ms); + image.SaveAsBmp(stream); } } } @@ -73,15 +70,15 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill Polygon - cached shape")] public void DrawSolidPolygonCoreCahced() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { image.Mutate(x => x.Fill( Rgba32.HotPink, this.shape)); - using (MemoryStream ms = new MemoryStream()) + using (var stream = new MemoryStream()) { - image.SaveAsBmp(ms); + image.SaveAsBmp(stream); } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs index 643e4ac9a..d45b791ae 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System.Drawing; using System.Drawing.Drawing2D; @@ -15,22 +13,18 @@ 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 (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) { - - 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)); - } + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.FillRectangle(System.Drawing.Brushes.HotPink, new Rectangle(10, 10, 190, 140)); + return destination.Size; } } @@ -38,7 +32,7 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill Rectangle")] public CoreSize FillRactangleCore() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { image.Mutate(x => x.Fill(Rgba32.HotPink, new CoreRectangle(10, 10, 190, 140))); @@ -49,7 +43,7 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill Rectangle - As Polygon")] public CoreSize FillPolygonCore() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { image.Mutate(x => x.FillPolygon( Rgba32.HotPink, diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs index 5f8f2ff06..411f8210a 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System.Drawing; using System.Drawing.Drawing2D; @@ -21,17 +19,19 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Baseline = true, Description = "System.Drawing Fill with Pattern")] public void DrawPatternPolygonSystemDrawing() { - using (Bitmap destination = new Bitmap(800, 800)) + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) { - using (Graphics graphics = Graphics.FromImage(destination)) + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + using (var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink)) { - 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()) + + using (var stream = new MemoryStream()) { - destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); } } } @@ -39,13 +39,13 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill with Pattern")] public void DrawPatternPolygon3Core() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { image.Mutate(x => x.Fill(CoreBrushes.BackwardDiagonal(Rgba32.HotPink))); - using (MemoryStream ms = new MemoryStream()) + using (var stream = new MemoryStream()) { - image.SaveAsBmp(ms); + image.SaveAsBmp(stream); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs index 60d89847f..1f8961fcd 100644 --- a/tests/ImageSharp.Benchmarks/General/Array2D.cs +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -1,16 +1,14 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // 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.Primitives; +namespace SixLabors.ImageSharp.Benchmarks.General +{ /** * Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | -------------------------------------------- |------ |---------:|---------:|---------:|-------:|---------:| diff --git a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs deleted file mode 100644 index ac6b3f93c..000000000 --- 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 45a8519a9..c49c383eb 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs @@ -1,14 +1,12 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // 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)] @@ -58,4 +56,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General } } } -} +} \ 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 6ce82ba11..a8686fc18 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 and contributors. // Licensed under the Apache License, Version 2.0. -// using System; using System.Runtime.CompilerServices; diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs new file mode 100644 index 000000000..2c18b2972 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs @@ -0,0 +1,22 @@ +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 000000000..32f1d10c7 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs @@ -0,0 +1,231 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General +{ + /// + /// 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 unsafe void UnsafeCopyBlockReferences() + { + Unsafe.CopyBlock(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); + } + + [Benchmark(Description = "Unsafe.CopyBlock(ptr)")] + public unsafe void UnsafeCopyBlockPointers() + { + void* pinnedDestination = this.destHandle.Pointer; + void* pinnedSource = this.sourceHandle.Pointer; + Unsafe.CopyBlock(pinnedDestination, pinnedSource, (uint)this.Count); + } + + [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ref)")] + public unsafe void UnsafeCopyBlockUnalignedReferences() + { + Unsafe.CopyBlockUnaligned(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); + } + + [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ptr)")] + public unsafe void UnsafeCopyBlockUnalignedPointers() + { + void* pinnedDestination = this.destHandle.Pointer; + void* pinnedSource = this.sourceHandle.Pointer; + Unsafe.CopyBlockUnaligned(pinnedDestination, pinnedSource, (uint)this.Count); + } + + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC + // .NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // | Method | Job | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |------------------------------- |----- |-------- |------ |-----------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| + // | Array.Copy() | Clr | Clr | 10 | 23.636 ns | 2.5299 ns | 0.1387 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 10 | 11.420 ns | 2.3341 ns | 0.1279 ns | 0.48 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 10 | 2.861 ns | 0.5059 ns | 0.0277 ns | 0.12 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 10 | 14.870 ns | 2.4541 ns | 0.1345 ns | 0.63 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 10 | 31.906 ns | 1.2213 ns | 0.0669 ns | 1.35 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 10 | 3.513 ns | 0.7392 ns | 0.0405 ns | 0.15 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10 | 3.053 ns | 0.2010 ns | 0.0110 ns | 0.13 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10 | 3.497 ns | 0.4911 ns | 0.0269 ns | 0.15 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10 | 3.109 ns | 0.5958 ns | 0.0327 ns | 0.13 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 10 | 19.709 ns | 2.1867 ns | 0.1199 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 10 | 7.377 ns | 1.1582 ns | 0.0635 ns | 0.37 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 10 | 2.581 ns | 1.1607 ns | 0.0636 ns | 0.13 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 10 | 15.197 ns | 2.8446 ns | 0.1559 ns | 0.77 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 10 | 25.394 ns | 0.9782 ns | 0.0536 ns | 1.29 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 10 | 2.254 ns | 0.1590 ns | 0.0087 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 10 | 1.878 ns | 0.1035 ns | 0.0057 ns | 0.10 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10 | 2.263 ns | 0.1383 ns | 0.0076 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10 | 1.877 ns | 0.0602 ns | 0.0033 ns | 0.10 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 50 | 35.068 ns | 5.9137 ns | 0.3242 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 50 | 23.299 ns | 2.3797 ns | 0.1304 ns | 0.66 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 50 | 3.598 ns | 4.8536 ns | 0.2660 ns | 0.10 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 50 | 27.720 ns | 4.6602 ns | 0.2554 ns | 0.79 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 50 | 35.673 ns | 16.2972 ns | 0.8933 ns | 1.02 | 0.03 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 50 | 5.534 ns | 2.8119 ns | 0.1541 ns | 0.16 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 50 | 4.511 ns | 0.9555 ns | 0.0524 ns | 0.13 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 50 | 5.613 ns | 1.6679 ns | 0.0914 ns | 0.16 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 50 | 4.884 ns | 7.3153 ns | 0.4010 ns | 0.14 | 0.01 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 50 | 20.232 ns | 1.5720 ns | 0.0862 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 50 | 8.142 ns | 0.7860 ns | 0.0431 ns | 0.40 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 50 | 2.962 ns | 0.0611 ns | 0.0033 ns | 0.15 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 50 | 16.802 ns | 2.9686 ns | 0.1627 ns | 0.83 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 50 | 26.571 ns | 0.9228 ns | 0.0506 ns | 1.31 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 50 | 2.219 ns | 0.7191 ns | 0.0394 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 50 | 1.751 ns | 0.1884 ns | 0.0103 ns | 0.09 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 50 | 2.177 ns | 0.4489 ns | 0.0246 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 50 | 1.806 ns | 0.1063 ns | 0.0058 ns | 0.09 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 100 | 39.158 ns | 4.3068 ns | 0.2361 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 100 | 27.623 ns | 0.4611 ns | 0.0253 ns | 0.71 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 100 | 5.018 ns | 0.5689 ns | 0.0312 ns | 0.13 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 100 | 33.527 ns | 1.9019 ns | 0.1042 ns | 0.86 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 100 | 35.604 ns | 2.7039 ns | 0.1482 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 100 | 7.853 ns | 0.4925 ns | 0.0270 ns | 0.20 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 100 | 7.406 ns | 1.9733 ns | 0.1082 ns | 0.19 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 100 | 7.822 ns | 0.6837 ns | 0.0375 ns | 0.20 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 100 | 7.392 ns | 1.2832 ns | 0.0703 ns | 0.19 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 100 | 22.909 ns | 2.9754 ns | 0.1631 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 100 | 10.687 ns | 1.1262 ns | 0.0617 ns | 0.47 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 100 | 4.063 ns | 0.1607 ns | 0.0088 ns | 0.18 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 100 | 18.067 ns | 4.0557 ns | 0.2223 ns | 0.79 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 100 | 28.352 ns | 1.2762 ns | 0.0700 ns | 1.24 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 100 | 4.130 ns | 0.2013 ns | 0.0110 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 100 | 4.096 ns | 0.2460 ns | 0.0135 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 100 | 4.160 ns | 0.3174 ns | 0.0174 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 100 | 3.480 ns | 1.1683 ns | 0.0640 ns | 0.15 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 1000 | 49.059 ns | 2.0729 ns | 0.1136 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 1000 | 38.270 ns | 23.6970 ns | 1.2989 ns | 0.78 | 0.03 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 1000 | 27.599 ns | 6.8328 ns | 0.3745 ns | 0.56 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 1000 | 42.752 ns | 5.1357 ns | 0.2815 ns | 0.87 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 1000 | 69.983 ns | 2.1860 ns | 0.1198 ns | 1.43 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 1000 | 44.822 ns | 0.1625 ns | 0.0089 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 1000 | 45.072 ns | 1.4053 ns | 0.0770 ns | 0.92 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 1000 | 45.306 ns | 5.2646 ns | 0.2886 ns | 0.92 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 1000 | 44.813 ns | 0.9117 ns | 0.0500 ns | 0.91 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 1000 | 51.907 ns | 3.1827 ns | 0.1745 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 1000 | 40.700 ns | 3.1488 ns | 0.1726 ns | 0.78 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 1000 | 23.711 ns | 1.3004 ns | 0.0713 ns | 0.46 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 1000 | 42.586 ns | 2.5390 ns | 0.1392 ns | 0.82 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 1000 | 44.109 ns | 4.5604 ns | 0.2500 ns | 0.85 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 1000 | 33.926 ns | 5.1633 ns | 0.2830 ns | 0.65 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 1000 | 33.267 ns | 0.2708 ns | 0.0148 ns | 0.64 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 1000 | 34.018 ns | 2.3238 ns | 0.1274 ns | 0.66 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 1000 | 33.667 ns | 2.1983 ns | 0.1205 ns | 0.65 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 10000 | 153.429 ns | 6.1735 ns | 0.3384 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 10000 | 201.373 ns | 4.3670 ns | 0.2394 ns | 1.31 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 10000 | 211.768 ns | 71.3510 ns | 3.9110 ns | 1.38 | 0.02 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 10000 | 215.299 ns | 17.2677 ns | 0.9465 ns | 1.40 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 10000 | 486.325 ns | 32.4445 ns | 1.7784 ns | 3.17 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 10000 | 452.314 ns | 33.0593 ns | 1.8121 ns | 2.95 | 0.02 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10000 | 455.600 ns | 56.7534 ns | 3.1108 ns | 2.97 | 0.02 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10000 | 452.279 ns | 8.6457 ns | 0.4739 ns | 2.95 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10000 | 453.146 ns | 12.3776 ns | 0.6785 ns | 2.95 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 10000 | 204.508 ns | 3.1652 ns | 0.1735 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 10000 | 193.345 ns | 1.3742 ns | 0.0753 ns | 0.95 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 10000 | 196.978 ns | 18.3279 ns | 1.0046 ns | 0.96 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 10000 | 206.878 ns | 6.9938 ns | 0.3834 ns | 1.01 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 10000 | 215.733 ns | 15.4824 ns | 0.8486 ns | 1.05 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 10000 | 186.894 ns | 8.7617 ns | 0.4803 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 10000 | 186.662 ns | 10.6059 ns | 0.5813 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10000 | 187.489 ns | 13.1527 ns | 0.7209 ns | 0.92 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10000 | 186.586 ns | 4.6274 ns | 0.2536 ns | 0.91 | 0.00 | - | - | - | - | + } +} \ 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 d205e1e63..ea8b34c24 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs @@ -1,7 +1,4 @@ -// ReSharper disable InconsistentNaming - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs index 2bc3ee971..68a16b791 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs @@ -3,8 +3,6 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToVector4 diff --git a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs index ae11806f3..3597207ee 100644 --- a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs +++ b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs @@ -1,10 +1,10 @@ -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; - using System.Numerics; +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! diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs index d189411b5..3afb796a7 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -1,9 +1,9 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class BitwiseOrUInt32 { private uint[] input; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs index 637846747..be9534f7d 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -1,9 +1,9 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class DivFloat { private float[] input; @@ -41,11 +41,11 @@ 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); } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs index 49ada2f36..bfc8d3de3 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -1,9 +1,9 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class DivUInt32 { private uint[] input; @@ -32,6 +32,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,11 +42,12 @@ 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); } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs index b38429557..df09aa569 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs @@ -1,10 +1,9 @@ -namespace ImageSharp.Benchmarks.General.Vectorization -{ - using System; - using System.Numerics; +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace ImageSharp.Benchmarks.General.Vectorization +{ public class DivFloat : SIMDBenchmarkBase.Divide { protected override float GetTestValue() => 42; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs index 8c5f56818..418209cbc 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -1,9 +1,9 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class MulFloat { private float[] input; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs index 913d4ce3c..7253dbd6a 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -1,9 +1,9 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; +using System.Numerics; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ public class MulUInt32 { private uint[] input; @@ -41,7 +41,7 @@ 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) { diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs index d1b70f21b..7a679c000 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs @@ -1,8 +1,8 @@ +using System.Numerics; +using BenchmarkDotNet.Attributes; + namespace ImageSharp.Benchmarks.General.Vectorization { - using System.Numerics; - using BenchmarkDotNet.Attributes; - public class MulUInt32 : SIMDBenchmarkBase.Multiply { protected override uint GetTestValue() => 42u; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs index f3853a8b1..67a8a9f39 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -1,10 +1,10 @@ -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; - using System.Runtime.InteropServices; +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; @@ -40,7 +40,7 @@ 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]; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs index 76987dbd2..8fc9d9977 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs @@ -1,10 +1,10 @@ -namespace ImageSharp.Benchmarks.General.Vectorization -{ - using System.Numerics; - using System.Runtime.CompilerServices; +using System.Numerics; +using System.Runtime.CompilerServices; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace ImageSharp.Benchmarks.General.Vectorization +{ public abstract class SIMDBenchmarkBase where T : struct { @@ -19,7 +19,6 @@ namespace ImageSharp.Benchmarks.General.Vectorization protected virtual T GetTestValue() => default(T); protected virtual Vector GetTestVector() => new Vector(this.GetTestValue()); - [Params(32)] public int InputSize { get; set; } diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index f941203db..14ad5635c 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -1,28 +1,30 @@ - + + + - netcoreapp2.1;net461 + ImageSharp.Benchmarks Exe - True SixLabors.ImageSharp.Benchmarks - ImageSharp.Benchmarks - 7.3 - - - win7-x64 - false + 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 cdd56fa07..ce4e16c44 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -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); } } diff --git a/tests/ImageSharp.Benchmarks/Program.cs b/tests/ImageSharp.Benchmarks/Program.cs index 4dd63067a..5caf238fb 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 and contributors. // 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 240a277cf..4fe7a365f 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs @@ -1,46 +1,40 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks -{ - using System.Drawing; - using System.Drawing.Drawing2D; +using System.Drawing; +using System.Drawing.Drawing2D; - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Processing; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Processing; - using CoreSize = SixLabors.Primitives.Size; +using CoreSize = SixLabors.Primitives.Size; +namespace SixLabors.ImageSharp.Benchmarks +{ public class Crop : BenchmarkBase { [Benchmark(Baseline = true, Description = "System.Drawing Crop")] public Size 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); - } - - return destination.Size; - } + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); + + return destination.Size; } } [Benchmark(Description = "ImageSharp Crop")] public CoreSize CropResizeCore() { - using (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); diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index 006d1b639..b36b28ef3 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 and contributors. // Licensed under the Apache License, Version 2.0. -// using SixLabors.ImageSharp.PixelFormats; @@ -53,4 +51,4 @@ namespace SixLabors.ImageSharp.Benchmarks this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Sobel)); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs new file mode 100644 index 000000000..47bd42a3c --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs @@ -0,0 +1,19 @@ +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks.Samplers +{ + [Config(typeof(Config.ShortClr))] + public class GaussianBlur + { + [Benchmark] + public void Blur() + { + using (var image = new Image(Configuration.Default, 400, 400, Rgba32.White)) + { + image.Mutate(c => c.GaussianBlur()); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs index 729971548..263ce6a7e 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Numerics; -using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -21,21 +20,21 @@ namespace SixLabors.ImageSharp.Benchmarks public class Glow : BenchmarkBase { - private GlowProcessor bulk; + 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, }; + this.bulk = new GlowProcessor(Color.Beige, 800 * .5f, GraphicsOptions.Default); + this.parallel = new GlowProcessorParallel(Color.Beige) { Radius = 800 * .5f, }; } [Benchmark(Description = "ImageSharp Glow - Bulk")] public CoreSize GlowBulk() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { this.bulk.Apply(image, image.Bounds()); return new CoreSize(image.Width, image.Height); @@ -45,7 +44,7 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Glow - Parallel")] public CoreSize GLowSimple() { - using (Image image = new Image(800, 800)) + using (var image = new Image(800, 800)) { this.parallel.Apply(image, image.Bounds()); return new CoreSize(image.Width, image.Height); @@ -128,7 +127,7 @@ namespace SixLabors.ImageSharp.Benchmarks int offsetX = x - startX; float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TPixel packed = default(TPixel); + TPixel packed = default; packed.FromVector4( PremultipliedLerp( sourceColor, @@ -166,9 +165,9 @@ namespace SixLabors.ImageSharp.Benchmarks // 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); + var inverseW = new Vector3(1 - source.W); + var xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); + var xyzS = new Vector3(source.X, source.Y, source.Z); return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs index 148b25328..172e24372 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. // 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 + public abstract class ResizeBenchmarkBase + where TPixel : struct, 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; } + + protected int SourceSize { get; private set; } + + protected int DestSize { get; private set; } - [Params(400)] - public int DestSize { get; 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,155 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")] public int ImageSharp_P1() => this.RunImageSharpResize(1); - [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] - public int ImageSharp_P4() => this.RunImageSharpResize(4); + // 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); + //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] + //public int ImageSharp_P8() => this.RunImageSharpResize(8); protected int RunImageSharpResize(int maxDegreeOfParallelism) { this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; - using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) + 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 : ResizeBenchmarkBase + + public class Resize_Bicubic_Rgba32 : 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 - ResizeWorker: // - // BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134 + // 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=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=2742189 Hz, Resolution=364.6722 ns, Timer=TSC + // .NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceToDest | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032-400 | 120.11 ms | 1.435 ms | 0.0786 ms | 1.00 | 0.00 | - | - | - | 1638 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032-400 | 75.32 ms | 34.143 ms | 1.8715 ms | 0.63 | 0.02 | - | - | - | 16384 B | + // | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032-400 | 120.33 ms | 6.669 ms | 0.3656 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032-400 | 88.56 ms | 1.864 ms | 0.1022 ms | 0.74 | 0.00 | - | - | - | 15568 B | + } + + /// + /// Is it worth to set a larger working buffer limit for resize? + /// Conclusion: It doesn't really have an effect. + /// + public class Resize_Bicubic_Rgba32_CompareWorkBufferSizes : Resize_Bicubic_Rgba32 + { + [Params(128, 512, 1024, 8 * 1024)] + public int WorkingBufferSizeHintInKilobytes { get; set; } + + [Params("3032-400", "4000-300")] + public override string SourceToDest { get; set; } + + public override void Setup() + { + this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024; + base.Setup(); + } + } + + public class Resize_Bicubic_Bgra32 : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + } + + // RESULTS (2019 April): // - // 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 | + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 119.01 ms | 18.513 ms | 1.0147 ms | 1.00 | - | - | - | 1638 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 104.71 ms | 16.078 ms | 0.8813 ms | 0.88 | - | - | - | 45056 B | + // | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 121.58 ms | 50.084 ms | 2.7453 ms | 1.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B | + } + + public class Resize_Bicubic_Rgb24 : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + } + // RESULTS (2019 April): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 121.37 ms | 48.580 ms | 2.6628 ms | 1.00 | 0.00 | - | - | - | 2048 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 99.36 ms | 11.356 ms | 0.6224 ms | 0.82 | 0.02 | - | - | - | 45056 B | + // | | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 118.06 ms | 15.667 ms | 0.8587 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B | } - public class Resize_BicubicCompand : 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 - // - // 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 | + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |---------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 120.7 ms | 68.985 ms | 3.7813 ms | 1.00 | 0.00 | - | - | - | 1638 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 132.2 ms | 15.976 ms | 0.8757 ms | 1.10 | 0.04 | - | - | - | 16384 B | + // | | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 118.3 ms | 6.899 ms | 0.3781 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 122.4 ms | 15.069 ms | 0.8260 ms | 1.03 | 0.01 | - | - | - | 15712 B | } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs index f898576af..69ff1549b 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs index 84819750a..559e49704 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index cb286cc28..fc94668e1 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -1,30 +1,28 @@ - + + + - 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 + Exe + false SixLabors.ImageSharp.Sandbox46 - 7.3 + win7-x64 + net472 + - - + + - - - + + + - - - + + - \ No newline at end of file + + diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 02d4f80c5..afe7eb04f 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -33,11 +33,11 @@ namespace SixLabors.ImageSharp.Sandbox46 /// public static void Main(string[] args) { - RunJpegColorProfilingTests(); + // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); - // RunResizeProfilingTest(); + RunResizeProfilingTest(); Console.ReadLine(); } @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Sandbox46 private static void RunResizeProfilingTest() { var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); - test.ResizeBicubic(2000, 2000); + test.ResizeBicubic(4000, 4000); } private static void RunToVector4ProfilingTest() diff --git a/tests/ImageSharp.Sandbox46/Properties/AssemblyInfo.cs b/tests/ImageSharp.Sandbox46/Properties/AssemblyInfo.cs deleted file mode 100644 index a10fc12fe..000000000 --- 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.Tests/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs index 7adbefb34..d3de04354 100644 --- a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs +++ b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; using Xunit; @@ -10,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests { public abstract class BaseImageOperationsExtensionTest { - protected readonly IImageProcessingContext operations; + protected readonly IImageProcessingContext operations; private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; protected readonly Rectangle rect; protected readonly GraphicsOptions options; @@ -33,7 +34,12 @@ namespace SixLabors.ImageSharp.Tests FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; - return Assert.IsType(operation.Processor); + if (operation.NonGenericProcessor != null) + { + return Assert.IsType(operation.NonGenericProcessor); + } + + return Assert.IsType(operation.GenericProcessor); } public T Verify(Rectangle rect, int index = 0) @@ -43,7 +49,13 @@ namespace SixLabors.ImageSharp.Tests FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; Assert.Equal(rect, operation.Rectangle); - return Assert.IsType(operation.Processor); + + if (operation.NonGenericProcessor != null) + { + return Assert.IsType(operation.NonGenericProcessor); + } + + return Assert.IsType(operation.GenericProcessor); } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs new file mode 100644 index 000000000..22c6d55ad --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// 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() + { + Rgba64 source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + Color color = source; + + // Assert: + Rgba64 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + Rgba32 source = new Rgba32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Rgba32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + Argb32 source = new Argb32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Argb32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + Bgra32 source = new Bgra32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Bgra32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + Rgb24 source = new Rgb24(1, 22, 231); + + // Act: + Color color = source; + + // Assert: + Rgb24 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + Bgr24 source = new Bgr24(1, 22, 231); + + // Act: + Color color = source; + + // Assert: + Bgr24 data = color.ToPixel(); + Assert.Equal(source, data); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs new file mode 100644 index 000000000..d44472b28 --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -0,0 +1,93 @@ +// // Copyright (c) Six Labors and contributors. +// // 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() + { + Rgba64 source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + Color color = new Color(source); + + // Assert: + Rgba64 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + Rgba32 source = new Rgba32(1, 22, 33, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Rgba32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + Argb32 source = new Argb32(1, 22, 33, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Argb32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + Bgra32 source = new Bgra32(1, 22, 33, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Bgra32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + Rgb24 source = new Rgb24(1, 22, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Rgb24 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + Bgr24 source = new Bgr24(1, 22, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Bgr24 data = color; + Assert.Equal(source, data); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs new file mode 100644 index 000000000..9d21cd80c --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// 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() + { + Rgba64 source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + Color color = new Color(source); + + // Assert: + Rgba64 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + Rgba32 source = new Rgba32(1, 22, 33, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Rgba32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + Argb32 source = new Argb32(1, 22, 33, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Argb32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + Bgra32 source = new Bgra32(1, 22, 33, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Bgra32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + Rgb24 source = new Rgb24(1, 22, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Rgb24 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + Bgr24 source = new Bgr24(1, 22, 231); + + // Act: + Color color = new Color(source); + + // Assert: + Bgr24 data = color.ToPixel(); + Assert.Equal(source, data); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs new file mode 100644 index 000000000..eb5cc70b2 --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Reflection; + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ColorTests + { + [Fact] + public void WithAlpha() + { + Color c1 = Color.FromRgba(111, 222, 55, 255); + Color c2 = c1.WithAlpha(0.5f); + + Rgba32 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"; + Color color = Color.FromHex(expected); + string actual = color.ToHex(); + + Assert.Equal(expected, actual); + } + + [Fact] + public void WebSafePalette_IsCorrect() + { + Rgba32[] actualPalette = Color.WebSafePalette.ToArray().Select(c => (Rgba32)c).ToArray(); + Assert.Equal(ReferencePalette.WebSafeColors, actualPalette); + } + + [Fact] + public void WernerPalette_IsCorrect() + { + Rgba32[] actualPalette = Color.WernerPalette.ToArray().Select(c => (Rgba32)c).ToArray(); + Assert.Equal(ReferencePalette.WernerColors, actualPalette); + } + + public class FromHex + { + [Fact] + public void ShortHex() + { + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24) Color.FromHex("#fff")); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24) Color.FromHex("fff")); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32) Color.FromHex("000f")); + } + + [Fact] + public void LeadingPoundIsOptional() + { + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24) Color.FromHex("#008080")); + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24) Color.FromHex("008080")); + } + + [Fact] + public void ThrowsOnEmpty() + { + Assert.Throws(() => Color.FromHex("")); + } + + [Fact] + public void ThrowsOnNull() + { + Assert.Throws(() => Color.FromHex(null)); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Color/ReferencePalette.cs b/tests/ImageSharp.Tests/Color/ReferencePalette.cs new file mode 100644 index 000000000..3c6e382c5 --- /dev/null +++ b/tests/ImageSharp.Tests/Color/ReferencePalette.cs @@ -0,0 +1,277 @@ +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +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 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/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs index 91cacfe3f..e1386e1a0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs @@ -2,7 +2,6 @@ // 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 57da2ff17..7bf84dd0a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion IEqualityComparer, IEqualityComparer, IEqualityComparer, - IEqualityComparer + IEqualityComparer { private readonly float Epsilon; @@ -222,14 +222,14 @@ 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) { diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 208387e6d..6f68d0428 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -39,13 +39,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,7 +54,7 @@ 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); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests public void ConfigurationCannotAddDuplicates() { const int count = 4; - Configuration config = Configuration.Default; + Configuration config = this.DefaultConfiguration; Assert.Equal(count, config.ImageFormats.Count()); @@ -105,9 +105,16 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void DefaultConfigurationHasCorrectFormatCount() { - Configuration config = Configuration.Default; + Configuration config = Configuration.CreateDefaultInstance(); Assert.Equal(4, config.ImageFormats.Count()); } + + [Fact] + public void WorkingBufferSizeHint_DefaultIsCorrect() + { + Configuration config = this.DefaultConfiguration; + Assert.True(config.WorkingBufferSizeHintInBytes > 1024); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs deleted file mode 100644 index 69b2098dc..000000000 --- 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/DrawBezierTests.cs b/tests/ImageSharp.Tests/Drawing/DrawBezierTests.cs new file mode 100644 index 000000000..4ccc7fd35 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawBezierTests.cs @@ -0,0 +1,48 @@ +// 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 Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class DrawBezierTests + { + public static readonly TheoryData DrawPathData = new TheoryData + { + { "White", 255, 1.5f }, + { "Red", 255, 3 }, + { "HotPink", 255, 5 }, + { "HotPink", 150, 5 }, + { "White", 255, 15 }, + }; + + [Theory] + [WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)] + public void DrawBeziers(TestImageProvider provider, string colorName, byte alpha, float thickness) + where TPixel : struct, IPixel + { + var points = new SixLabors.Primitives.PointF[] + { + new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) + }; + Rgba32 rgba = TestUtils.GetColorByName(colorName); + rgba.A = alpha; + Color color = rgba; + + FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; + + provider.RunValidatingProcessorTest( x => x.DrawBeziers(color, 5f, points), + testDetails, + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/DrawComplexPolygonTests.cs new file mode 100644 index 000000000..f28f3345f --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawComplexPolygonTests.cs @@ -0,0 +1,70 @@ +// 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 +{ + [GroupOutput("Drawing")] + public class DrawComplexPolygonTests + { + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, true, false, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, true, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, true)] + public void DrawComplexPolygon(TestImageProvider provider, bool overlap, bool transparent, bool dashed) + where TPixel :struct, IPixel + { + 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), + overlap ? new Vector2(130, 40) : new Vector2(93, 85), + new Vector2(65, 137))); + IPath clipped = simplePath.Clip(hole1); + + Rgba32 colorRgba = Rgba32.White; + if (transparent) + { + colorRgba.A = 150; + } + + Color color = colorRgba; + + string testDetails = ""; + if (overlap) + { + testDetails += "_Overlap"; + } + + if (transparent) + { + testDetails += "_Transparent"; + } + + if (dashed) + { + testDetails += "_Dashed"; + } + + Pen pen = dashed ? Pens.Dash(color, 5f) : Pens.Solid(color, 5f); + + provider.RunValidatingProcessorTest( + x => x.Draw(pen, clipped), + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs deleted file mode 100644 index 374454afb..000000000 --- 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 000000000..b2d499c7f --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -0,0 +1,202 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +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.Primitives; +using SixLabors.Shapes; + +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 : 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)); + 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 : struct, IPixel + { + using (Image image = provider.GetImage()) + using (var blend = Image.Load(TestFile.Create(brushImage).Bytes)) + { + Size size = new Size(image.Width * 3 / 4, image.Height * 3 / 4); + Point 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}"; + + PngEncoder 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 : struct, 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)) + { + overlay.Mutate(c => c.Fill(Rgba32.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 : 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)); + 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, Rgba32.Black)) + { + ImageProcessingException ex = Assert.Throws(Test); + + Assert.Contains("does not overlap", ex.ToString()); + + void Test() + { + background.Mutate(context => context.DrawImage(overlay, new Point(x, y), GraphicsOptions.Default)); + } + } + } + + + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs b/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs new file mode 100644 index 000000000..12eb150b1 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs @@ -0,0 +1,100 @@ +// 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 Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class DrawLinesTests + { + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)] + public void DrawLines_Simple(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : struct, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = new Pen(color, thickness); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] + public void DrawLines_Dash(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : struct, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = Pens.Dash(color, thickness); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "LightGreen", 1f, 5, false)] + public void DrawLines_Dot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : struct, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = Pens.Dot(color, thickness); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, false)] + public void DrawLines_DashDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : struct, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = Pens.DashDot(color, thickness); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Black", 1f, 5, false)] + public void DrawLines_DashDotDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : struct, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = Pens.DashDotDot(color, thickness); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + + private static void DrawLinesImpl( + TestImageProvider provider, + string colorName, + float alpha, + float thickness, + bool antialias, + Pen pen) + where TPixel : struct, IPixel + { + SixLabors.Primitives.PointF[] simplePath = { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; + + GraphicsOptions options = new GraphicsOptions(antialias); + + string aa = antialias ? "" : "_NoAntialias"; + FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; + + provider.RunValidatingProcessorTest( + c => c.DrawLines(options, pen, simplePath), + outputDetails, + appendSourceFileOrDescription: false); + } + + } +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index 0d791fbd2..5e9743c31 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -1,110 +1,79 @@ // 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.Primitives; using SixLabors.Shapes; using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { - public class DrawPathTests : FileTestBase + [GroupOutput("Drawing")] + public class DrawPathTests { - [Fact] - public void ImageShouldBeOverlayedByPath() + public static readonly TheoryData DrawPathData = new TheoryData + { + { "White", 255, 1.5f }, + { "Red", 255, 3 }, + { "HotPink", 255, 5 }, + { "HotPink", 150, 5 }, + { "White", 255, 15 }, + }; + + [Theory] + [WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)] + public void DrawPath(TestImageProvider provider, string colorName, byte alpha, float thickness) + where TPixel : struct, IPixel { - 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(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"); + var path = new Path(linerSegemnt, bazierSegment); - //shift background color towards forground color by the opacity amount - var mergedColor = new Rgba32( - Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + Rgba32 rgba = TestUtils.GetColorByName(colorName); + rgba.A = alpha; + Color color = rgba; - Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); - Assert.Equal(mergedColor, sourcePixels[11, 11]); + FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; - Assert.Equal(mergedColor, sourcePixels[199, 149]); - - Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); - } + provider.RunValidatingProcessorTest( + x => x.Draw(color, thickness, path), + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } - [Fact] - public void PathExtendingOffEdgeOfImageShouldNotBeCropped() + [Theory] + [WithSolidFilledImages(256, 256, "Black", PixelTypes.Rgba32)] + public void PathExtendingOffEdgeOfImageShouldNotBeCropped(TestImageProvider provider) + where TPixel : struct, IPixel { - - 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]); - } + Color color = Color.White; + Pen pen = Pens.Solid(color, 5f); + + provider.RunValidatingProcessorTest( + x => + { + for (int i = 0; i < 300; i += 20) + { + PointF[] points = new PointF[] { new Vector2(100, 2), new Vector2(-10, i) }; + x.DrawLines(pen, points); + } + }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs new file mode 100644 index 000000000..b7c54fdbd --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs @@ -0,0 +1,43 @@ +// 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.Primitives; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class DrawPolygonTests + { + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)] + public void DrawPolygon(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : struct, IPixel + { + SixLabors.Primitives.PointF[] simplePath = + { + new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) + }; + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + + GraphicsOptions options = new GraphicsOptions(antialias); + + string aa = antialias ? "" : "_NoAntialias"; + FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; + + provider.RunValidatingProcessorTest( + c => c.DrawPolygon(options, color, thickness, simplePath), + outputDetails, + appendSourceFileOrDescription: false); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/FillComplexPolygonTests.cs new file mode 100644 index 000000000..d6c85cd1d --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillComplexPolygonTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Shapes; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class FillComplexPolygonTests + { + [Theory] + [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, false)] + [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, true, false)] + [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, true)] + public void ComplexPolygon_SolidFill(TestImageProvider provider, bool overlap, bool transparent) + where TPixel :struct, IPixel + { + 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), + overlap ? new Vector2(130, 40) : new Vector2(93, 85), + new Vector2(65, 137))); + IPath clipped = simplePath.Clip(hole1); + + Rgba32 colorRgba = Rgba32.HotPink; + if (transparent) + { + colorRgba.A = 150; + } + + Color color = colorRgba; + + string testDetails = ""; + if (overlap) + { + testDetails += "_Overlap"; + } + + if (transparent) + { + testDetails += "_Transparent"; + } + + provider.RunValidatingProcessorTest( + x => x.Fill(color, clipped), + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index fa4d4a709..c61f770c9 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -23,18 +23,18 @@ namespace SixLabors.ImageSharp.Tests.Drawing TestImageProvider provider) where TPixel : struct, IPixel { - TPixel red = NamedColors.Red; + Color red = Color.Red; using (Image image = provider.GetImage()) { var unicolorLinearGradientBrush = - new EllipticGradientBrush( + 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)); + new ColorStop(0, red), + new ColorStop(1, red)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); @@ -58,22 +58,22 @@ namespace SixLabors.ImageSharp.Tests.Drawing float ratio) where TPixel : struct, IPixel { - TPixel yellow = NamedColors.Yellow; - TPixel red = NamedColors.Red; - TPixel black = NamedColors.Black; + Color yellow = Color.Yellow; + Color red = Color.Red; + Color black = Color.Black; provider.VerifyOperation( TolerantComparer, image => { - var unicolorLinearGradientBrush = new EllipticGradientBrush( + 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)); + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); }, @@ -114,9 +114,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing TolerantComparer, image => { - TPixel yellow = NamedColors.Yellow; - TPixel red = NamedColors.Red; - TPixel black = NamedColors.Black; + Color yellow = Color.Yellow; + Color red = Color.Red; + Color black = Color.Black; var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2); @@ -128,14 +128,14 @@ namespace SixLabors.ImageSharp.Tests.Drawing int axisX = center.X + (int)-(offsetY * sin); int axisY = center.Y + (int)(offsetY * cos); - var unicolorLinearGradientBrush = new EllipticGradientBrush( + 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)); + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); }, diff --git a/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs new file mode 100644 index 000000000..d33d7b70d --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; +using SixLabors.Shapes; + +using Xunit; + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class FillImageBrushTests + { + [Fact] + public void DoesNotDisposeImage() + { + using (var src = new Image(5, 5)) + { + var brush = new ImageBrush(src); + using (var dest = new Image(10, 10)) + { + dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); + dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); + } + } + } + + [Theory] + [WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void UseBrushOfDifferentPixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; + using (Image background = provider.GetImage()) + using (Image overlay = provider.PixelType == PixelTypes.Rgba32 + ? (Image)Image.Load(data) + : Image.Load(data)) + { + var brush = new ImageBrush(overlay); + background.Mutate(c => c.Fill(brush)); + + background.DebugSave(provider, appendSourceFileOrDescription : false); + background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 556ec9c9c..361e7e70d 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -29,14 +29,14 @@ namespace SixLabors.ImageSharp.Tests.Drawing { using (Image image = provider.GetImage()) { - TPixel red = NamedColors.Red; + Color red = Color.Red; - var unicolorLinearGradientBrush = new LinearGradientBrush( + 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)); + new ColorStop(0, red), + new ColorStop(1, red)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); @@ -58,12 +58,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing TolerantComparer, image => { - var unicolorLinearGradientBrush = new LinearGradientBrush( + 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)); + new ColorStop(0, Color.Blue), + new ColorStop(1, Color.Yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); }, @@ -79,15 +79,15 @@ namespace SixLabors.ImageSharp.Tests.Drawing TolerantComparer, image => { - TPixel red = NamedColors.Red; - TPixel yellow = NamedColors.Yellow; + Color red = Color.Red; + Color yellow = Color.Yellow; - var unicolorLinearGradientBrush = new LinearGradientBrush( + 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)); + new ColorStop(0, red), + new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); }, @@ -109,15 +109,15 @@ namespace SixLabors.ImageSharp.Tests.Drawing TolerantComparer, image => { - TPixel red = NamedColors.Red; - TPixel yellow = NamedColors.Yellow; + Color red = Color.Red; + Color yellow = Color.Yellow; - var unicolorLinearGradientBrush = new LinearGradientBrush( + 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)); + new ColorStop(0, red), + new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); }, @@ -140,26 +140,26 @@ namespace SixLabors.ImageSharp.Tests.Drawing // ensure the input data is valid Assert.True(pattern.Length > 0); - TPixel black = NamedColors.Black; - TPixel white = NamedColors.White; + Color black = Color.Black; + Color white = Color.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) + 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) + 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)) + .Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? black : white), 1)) .ToArray(); using (Image image = provider.GetImage()) { var unicolorLinearGradientBrush = - new LinearGradientBrush( + new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(image.Width, 0), GradientRepetitionMode.None, @@ -176,7 +176,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing // 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))); + color => Assert.True( + color.Equals(black.ToPixel()) || color.Equals(white.ToPixel()))); image.CompareToReferenceOutput( TolerantComparer, @@ -196,15 +197,15 @@ namespace SixLabors.ImageSharp.Tests.Drawing provider.VerifyOperation( image => { - TPixel red = NamedColors.Red; - TPixel yellow = NamedColors.Yellow; + Color red = Color.Red; + Color yellow = Color.Yellow; - var unicolorLinearGradientBrush = new LinearGradientBrush( + 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)); + new ColorStop(0, red), + new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); @@ -254,16 +255,16 @@ namespace SixLabors.ImageSharp.Tests.Drawing int endX = image.Height - startX - 1; int endY = image.Width - startY - 1; - TPixel red = NamedColors.Red; - TPixel yellow = NamedColors.Yellow; + Color red = Color.Red; + Color yellow = Color.Yellow; var unicolorLinearGradientBrush = - new LinearGradientBrush( + new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); + new ColorStop(0, red), + new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.DebugSave( @@ -275,10 +276,6 @@ namespace SixLabors.ImageSharp.Tests.Drawing 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) @@ -314,22 +311,67 @@ namespace SixLabors.ImageSharp.Tests.Drawing int[] stopColorCodes) where TPixel : struct, IPixel { - TPixel[] colors = + Color[] colors = + { + Color.Navy, Color.LightGreen, Color.Yellow, + Color.Red + }; + + var coloringVariant = new StringBuilder(); + var colorStops = new ColorStop[stopPositions.Length]; + + for (int i = 0; i < stopPositions.Length; i++) + { + Color color = colors[stopColorCodes[i % colors.Length]]; + float position = stopPositions[i]; + colorStops[i] = new ColorStop(position, color); + Rgba32 rgba = color; + coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); + } + + FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; + + provider.VerifyOperation( + image => + { + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(startX, startY), + new SixLabors.Primitives.Point(endX, endY), + GradientRepetitionMode.None, + colorStops); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + variant, + false, + false); + } + + [Theory] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0, 199, 199, new[] { 0f, .25f, .5f, .75f, 1f }, new[] { 0, 1, 2, 3, 4 })] + public void MultiplePointGradients( + TestImageProvider provider, + int startX, int startY, + int endX, int endY, + float[] stopPositions, + int[] stopColorCodes) + where TPixel : struct, IPixel + { + Color[] colors = { - NamedColors.Navy, NamedColors.LightGreen, NamedColors.Yellow, - NamedColors.Red + Color.Black, Color.Blue, Color.Red, + Color.White, Color.Lime }; var coloringVariant = new StringBuilder(); - var colorStops = new ColorStop[stopPositions.Length]; + var colorStops = new ColorStop[stopPositions.Length]; for (int i = 0; i < stopPositions.Length; i++) { - TPixel color = colors[stopColorCodes[i % colors.Length]]; + Color color = colors[stopColorCodes[i % colors.Length]]; float position = stopPositions[i]; - colorStops[i] = new ColorStop(position, color); - Rgba32 rgba = default; - color.ToRgba32(ref rgba); + colorStops[i] = new ColorStop(position, color); + Rgba32 rgba = color; coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); } @@ -338,7 +380,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing provider.VerifyOperation( image => { - var unicolorLinearGradientBrush = new LinearGradientBrush( + var unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), GradientRepetitionMode.None, diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternBrushTests.cs similarity index 98% rename from tests/ImageSharp.Tests/Drawing/FillPatternTests.cs rename to tests/ImageSharp.Tests/Drawing/FillPatternBrushTests.cs index b310c7afc..647f28510 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternBrushTests.cs @@ -12,11 +12,11 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { - public class FillPatternBrushTests : FileTestBase + public class FillPatternBrushTests { - private void Test(string name, Rgba32 background, IBrush brush, Rgba32[,] expectedPattern) + private void Test(string name, Rgba32 background, IBrush brush, Rgba32[,] expectedPattern) { - string path = TestEnvironment.CreateOutputDirectory("Fill", "PatternBrush"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FillPatternBrushTests"); using (var image = new Image(20, 20)) { image.Mutate(x => x.Fill(background).Fill(brush)); @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } - image.Mutate(x => x.Resize(80, 80)); + image.Mutate(x => x.Resize(80, 80, KnownResamplers.NearestNeighbor)); image.Save($"{path}/{name}x4.png"); } } diff --git a/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs new file mode 100644 index 000000000..5393e9954 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs @@ -0,0 +1,155 @@ +// 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 +{ + [GroupOutput("Drawing")] + public class FillPolygonTests + { + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, true)] + public void FillPolygon_Solid(TestImageProvider provider, string colorName, float alpha, bool antialias) + where TPixel : struct, IPixel + { + SixLabors.Primitives.PointF[] simplePath = + { + new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) + }; + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + + GraphicsOptions options = new GraphicsOptions(antialias); + + string aa = antialias ? "" : "_NoAntialias"; + FormattableString outputDetails = $"{colorName}_A{alpha}{aa}"; + + provider.RunValidatingProcessorTest( + c => c.FillPolygon(options, color, simplePath), + outputDetails, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)] + public void FillPolygon_Concave(TestImageProvider provider) + where TPixel : struct, IPixel + { + var points = 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) + }; + + Color color = Color.LightGreen; + + provider.RunValidatingProcessorTest( + c => c.FillPolygon(color, points), + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] + public void FillPolygon_Pattern(TestImageProvider provider) + where TPixel : struct, IPixel + { + SixLabors.Primitives.PointF[] simplePath = + { + new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) + }; + Color color = Color.Yellow; + + var brush = Brushes.Horizontal(color); + + provider.RunValidatingProcessorTest( + c => c.FillPolygon(brush, simplePath), + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)] + public void FillPolygon_ImageBrush(TestImageProvider provider, string brushImageName) + where TPixel : struct, IPixel + { + SixLabors.Primitives.PointF[] simplePath = + { + new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200) + }; + + using (Image brushImage = Image.Load(TestFile.Create(brushImageName).Bytes)) + { + var brush = new ImageBrush(brushImage); + + provider.RunValidatingProcessorTest( + c => c.FillPolygon(brush, simplePath), + System.IO.Path.GetFileNameWithoutExtension(brushImageName), + appendSourceFileOrDescription: false); + } + } + + [Theory] + [WithBasicTestPatternImages(250, 250, PixelTypes.Rgba32)] + public void Fill_RectangularPolygon(TestImageProvider provider) + where TPixel : struct, IPixel + { + var polygon = new SixLabors.Shapes.RectangularPolygon(10, 10, 190, 140); + Color color = Color.White; + + provider.RunValidatingProcessorTest( + c => c.Fill(color, polygon), + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 50, 0f)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, 20f)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, -180f)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 5, 70, 0f)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 7, 80, -180f)] + public void Fill_RegularPolygon(TestImageProvider provider, int vertices, float radius, float angleDeg) + where TPixel : struct, IPixel + { + float angle = GeometryUtilities.DegreeToRadian(angleDeg); + var polygon = new RegularPolygon(100, 100, vertices, radius, angle); + Color color = Color.Yellow; + + FormattableString testOutput = $"V({vertices})_R({radius})_Ang({angleDeg})"; + provider.RunValidatingProcessorTest( + c => c.Fill(color, polygon), + testOutput, + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)] + public void Fill_EllipsePolygon(TestImageProvider provider) + where TPixel : struct, IPixel + { + var polygon = new EllipsePolygon(100, 100, 80, 120); + Color color = Color.Azure; + + provider.RunValidatingProcessorTest( + c => c.Fill(color, polygon), + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs index 7461347de..818340dd2 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -21,15 +21,15 @@ namespace SixLabors.ImageSharp.Tests.Drawing { using (Image image = provider.GetImage()) { - TPixel red = NamedColors.Red; + Color red = Color.Red; var unicolorRadialGradientBrush = - new RadialGradientBrush( + new RadialGradientBrush( new SixLabors.Primitives.Point(0, 0), 100, GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); + new ColorStop(0, red), + new ColorStop(1, red)); image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); @@ -56,12 +56,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing TolerantComparer, image => { - var brush = new RadialGradientBrush( + 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)); + new ColorStop(0, Color.Red), + new ColorStop(1, Color.Yellow)); image.Mutate(x => x.Fill(brush)); }, diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs index dc7da3543..bb78054a4 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs @@ -8,6 +8,7 @@ using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; using Xunit; using SixLabors.ImageSharp.Processing.Processors.Drawing; @@ -30,14 +31,14 @@ namespace SixLabors.ImageSharp.Tests.Drawing { var bounds = new Rectangle(0, 0, 1, 1); - var brush = new Mock>(); + 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 processor = new FillRegionProcessor(brush.Object, region, options); var img = new Image(1, 1); processor.Apply(img, bounds); @@ -48,9 +49,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void FillOffCanvas() { var bounds = new Rectangle(-100, -10, 10, 10); - var brush = new Mock>(); + var brush = new Mock(); var options = new GraphicsOptions(true); - var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options); + var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options); var img = new Image(10, 10); processor.Apply(img, bounds); } @@ -61,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (var img = new Image(10, 10)) { - img.Mutate(x => x.DrawLines(new Pen(Rgba32.Black, 10), new SixLabors.Primitives.PointF[] { + img.Mutate(x => x.DrawLines(new Pen(Rgba32.Black, 10), new SixLabors.Primitives.PointF[] { new Vector2(-10, 5), new Vector2(20, 5), })); diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs index 639b3fe81..358552bf8 100644 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { using (Image image = provider.GetImage()) { - TPixel color = NamedColors.HotPink; + Color color = Color.HotPink; image.Mutate(c => c.Fill(color)); image.DebugSave(provider, appendPixelTypeToFileName: false); @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { using (Image image = provider.GetImage()) { - TPixel color = NamedColors.HotPink; + Color color = Color.HotPink; image.Mutate(c => c.Fill(color)); image.DebugSave(provider, appendSourceFileOrDescription: false); @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { using (Image image = provider.GetImage()) { - TPixel color = TestUtils.GetPixelOfNamedColor(newColorName); + Color color = TestUtils.GetColorByName(newColorName); image.Mutate(c => c.Fill(color)); image.DebugSave( @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; var region = new RectangleF(x0, y0, w, h); - TPixel color = TestUtils.GetPixelOfNamedColor("Blue"); + Color color = TestUtils.GetColorByName("Blue"); provider.RunValidatingProcessorTest(c => c.Fill(color, region), testDetails, ImageComparer.Exact); } @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; var region = new RectangleF(x0, y0, w, h); - TPixel color = TestUtils.GetPixelOfNamedColor("Blue"); + Color color = TestUtils.GetColorByName("Blue"); provider.RunValidatingProcessorTestOnWrappedMemoryImage( c => c.Fill(color, region), @@ -150,11 +150,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing float blendPercentage) where TPixel : struct, IPixel { - var vec = TestUtils.GetPixelOfNamedColor(newColorName).ToVector4(); - vec.W = alpha; - - TPixel fillColor = default; - fillColor.FromVector4(vec); + Color fillColor = TestUtils.GetColorByName(newColorName).WithAlpha(alpha); using (Image image = provider.GetImage()) { @@ -169,11 +165,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing { var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16)); - image.Mutate(c => c.Fill(options, new SolidBrush(fillColor), region)); + image.Mutate(c => c.Fill(options, new SolidBrush(fillColor), region)); } else { - image.Mutate(c => c.Fill(options, new SolidBrush(fillColor))); + image.Mutate(c => c.Fill(options, new SolidBrush(fillColor))); } var testOutputDetails = new @@ -194,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing PixelBlender blender = PixelOperations.Instance.GetPixelBlender( blenderMode, PixelAlphaCompositionMode.SrcOver); - TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage); + TPixel expectedPixel = blender.Blend(bgColor, fillColor.ToPixel(), blendPercentage); image.ComparePixelBufferTo(expectedPixel); } diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs deleted file mode 100644 index d827975c7..000000000 --- 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 43dec547e..000000000 --- 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 index 326517a4e..5ab789435 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs @@ -14,8 +14,8 @@ 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); + Color color = Color.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), @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths for (int i = 0; i < 2; i++) { - FillRegionProcessor processor = this.Verify>(i); + FillRegionProcessor processor = this.Verify(i); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths for (int i = 0; i < 2; i++) { - FillRegionProcessor processor = this.Verify>(i); + FillRegionProcessor processor = this.Verify(i); Assert.Equal(this.noneDefault, processor.Options); @@ -81,14 +81,14 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths for (int i = 0; i < 2; i++) { - FillRegionProcessor processor = this.Verify>(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); + SolidBrush brush = Assert.IsType(processor.Brush); Assert.Equal(this.color, brush.Color); } } @@ -100,14 +100,14 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths for (int i = 0; i < 2; i++) { - FillRegionProcessor processor = this.Verify>(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); + 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 index e72fbbdf2..d65dcd8c6 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs @@ -14,8 +14,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public class FillPath : BaseImageOperationsExtensionTest { GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + Color color = Color.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), @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsBrushAndPath() { this.operations.Fill(this.brush, this.path); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsBrushPathOptions() { this.operations.Fill(this.noneDefault, this.brush, this.path); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(this.noneDefault, processor.Options); @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsColorAndPath() { this.operations.Fill(this.color, this.path); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths Polygon polygon = Assert.IsType(region.Shape); LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); - SolidBrush brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType(processor.Brush); Assert.Equal(this.color, brush.Color); } @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsColorPathAndOptions() { this.operations.Fill(this.noneDefault, this.color, this.path); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(this.noneDefault, processor.Options); @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths Polygon polygon = Assert.IsType(region.Shape); LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); - SolidBrush brush = Assert.IsType>(processor.Brush); + 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 index ec7a5a20c..1f8e2d423 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs @@ -14,8 +14,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public class FillPathCollection : BaseImageOperationsExtensionTest { GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + Color color = Color.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), @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths for (int i = 0; i < 2; i++) { - FillRegionProcessor processor = this.Verify>(i); + FillRegionProcessor processor = this.Verify(i); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths for (int i = 0; i < 2; i++) { - FillRegionProcessor processor = this.Verify>(i); + FillRegionProcessor processor = this.Verify(i); Assert.Equal(this.noneDefault, processor.Options); @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths for (int i = 0; i < 2; i++) { - FillRegionProcessor processor = this.Verify>(i); + FillRegionProcessor processor = this.Verify(i); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths Polygon polygon = Assert.IsType(region.Shape); LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); - SolidBrush brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType(processor.Brush); Assert.Equal(this.color, brush.Color); } } @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths for (int i = 0; i < 2; i++) { - FillRegionProcessor processor = this.Verify>(i); + FillRegionProcessor processor = this.Verify(i); Assert.Equal(this.noneDefault, processor.Options); @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths Polygon polygon = Assert.IsType(region.Shape); LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); - SolidBrush brush = Assert.IsType>(processor.Brush); + 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 index d8927a468..b270a03cf 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs @@ -14,8 +14,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public class FillPolygon : BaseImageOperationsExtensionTest { GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); SixLabors.Primitives.PointF[] path = { new Vector2(10,10), new Vector2(20,10), @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths { this.operations.FillPolygon(this.brush, this.path); - FillRegionProcessor processor = this.Verify>(); + FillRegionProcessor processor = this.Verify(); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsBrushPathAndOptions() { this.operations.FillPolygon(this.noneDefault, this.brush, this.path); - FillRegionProcessor processor = this.Verify>(); + FillRegionProcessor processor = this.Verify(); Assert.Equal(this.noneDefault, processor.Options); @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsColorAndPath() { this.operations.FillPolygon(this.color, this.path); - FillRegionProcessor processor = this.Verify>(); + FillRegionProcessor processor = this.Verify(); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths Polygon polygon = Assert.IsType(region.Shape); LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); - SolidBrush brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType(processor.Brush); Assert.Equal(this.color, brush.Color); } @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsColorPathAndOptions() { this.operations.FillPolygon(this.noneDefault, this.color, this.path); - FillRegionProcessor processor = this.Verify>(); + FillRegionProcessor processor = this.Verify(); Assert.Equal(this.noneDefault, processor.Options); @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths Polygon polygon = Assert.IsType(region.Shape); LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); - SolidBrush brush = Assert.IsType>(processor.Brush); + 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 index 8f648e425..6cb052aa8 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs @@ -12,15 +12,15 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public class FillRectangle : BaseImageOperationsExtensionTest { GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + Color color = Color.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>(); + FillRegionProcessor processor = this.Verify(); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsBrushRectangleAndOptions() { this.operations.Fill(this.noneDefault, this.brush, this.rectangle); - FillRegionProcessor processor = this.Verify>(); + FillRegionProcessor processor = this.Verify(); Assert.Equal(this.noneDefault, processor.Options); @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsColorAndRectangle() { this.operations.Fill(this.color, this.rectangle); - FillRegionProcessor processor = this.Verify>(); + FillRegionProcessor processor = this.Verify(); Assert.Equal(GraphicsOptions.Default, processor.Options); @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths Assert.Equal(rect.Size.Width, this.rectangle.Width); Assert.Equal(rect.Size.Height, this.rectangle.Height); - SolidBrush brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType(processor.Brush); Assert.Equal(this.color, brush.Color); } @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths public void CorrectlySetsColorRectangleAndOptions() { this.operations.Fill(this.noneDefault, this.color, this.rectangle); - FillRegionProcessor processor = this.Verify>(); + FillRegionProcessor processor = this.Verify(); Assert.Equal(this.noneDefault, processor.Options); @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths Assert.Equal(rect.Size.Width, this.rectangle.Width); Assert.Equal(rect.Size.Height, this.rectangle.Height); - SolidBrush brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType(processor.Brush); Assert.Equal(this.color, brush.Color); } } diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs deleted file mode 100644 index 6ea9c647f..000000000 --- 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 2dcd8b3d3..000000000 --- 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/RecolorImageTests.cs b/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs new file mode 100644 index 000000000..fae0bf72b --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs @@ -0,0 +1,52 @@ +// 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.Primitives; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class RecolorImageTests + { + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, "Yellow", "Pink", 0.2f)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.6f)] + public void Recolor(TestImageProvider provider, string sourceColorName, string targetColorName, float threshold) + where TPixel : struct, IPixel + { + Color sourceColor = TestUtils.GetColorByName(sourceColorName); + Color targetColor = TestUtils.GetColorByName(targetColorName); + var brush = new RecolorBrush(sourceColor, targetColor, threshold); + + FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; + provider.RunValidatingProcessorTest(x => x.Fill(brush), testInfo); + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] + public void Recolor_InBox(TestImageProvider provider, string sourceColorName, string targetColorName, float threshold) + where TPixel : struct, IPixel + { + Color sourceColor = TestUtils.GetColorByName(sourceColorName); + Color targetColor = TestUtils.GetColorByName(targetColorName); + var brush = new RecolorBrush(sourceColor, targetColor, threshold); + + FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; + provider.RunValidatingProcessorTest(x => + { + Size size = x.GetCurrentSize(); + var rectangle = new Rectangle(0, size.Height / 2 - size.Height / 4, size.Width, size.Height / 2); + x.Fill(brush, rectangle); + }, testInfo); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 23acc1a44..fd8713ccc 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -24,15 +24,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing new Vector2(300, 400) }; - TPixel blue = NamedColors.Blue; - TPixel hotPink = NamedColors.HotPink; + Color blue = Color.Blue; + Color hotPink = Color.HotPink; using (Image image = provider.GetImage()) { - - image.Mutate(x => x - .BackgroundColor(blue) - .Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); + image.Mutate(x => x.BackgroundColor(blue)); + image.Mutate(x => x.Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); image.DebugSave(provider); image.CompareToReferenceOutput(provider); } @@ -55,9 +53,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (var image = provider.GetImage() as Image) { - image.Mutate(x => x - .BackgroundColor(Rgba32.Blue) - .Fill(color, new Polygon(new CubicBezierLineSegment(simplePath)))); + image.Mutate(x => x.BackgroundColor(Rgba32.Blue)); + + image.Mutate(x => x.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 2c9628e84..000000000 --- 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 index 94e12f858..c34d50297 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs @@ -43,11 +43,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing int scaleY = img.Height / 100; img.Mutate( x => x.Fill( - NamedColors.DarkBlue, + Color.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) ) .Fill(new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode=composition }, - NamedColors.HotPink, + Color.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) ); @@ -69,17 +69,17 @@ namespace SixLabors.ImageSharp.Tests.Drawing int scaleY = img.Height / 100; img.Mutate( x => x.Fill( - NamedColors.DarkBlue, + Color.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, + Color.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, + Color.Transparent, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) ); @@ -101,22 +101,20 @@ namespace SixLabors.ImageSharp.Tests.Drawing int scaleY = (img.Height / 100); img.Mutate( x => x.Fill( - NamedColors.DarkBlue, + Color.DarkBlue, new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); img.Mutate( x => x.Fill( new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, - NamedColors.HotPink, + Color.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); + + var transparentRed = Color.Red.WithAlpha(0.5f); img.Mutate( x => x.Fill( new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, - pixel, + transparentRed, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) ); @@ -139,12 +137,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing dstImg.Mutate( x => x.Fill( - NamedColors.DarkBlue, + Color.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); srcImg.Mutate( x => x.Fill( - NamedColors.Black, + Color.Black, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); dstImg.Mutate( diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs deleted file mode 100644 index d7fb0a3d3..000000000 --- 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 index 76f40e0ac..8520a2f3f 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs @@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text { public class DrawText : BaseImageOperationsExtensionTest { - Rgba32 color = Rgba32.HotPink; + Rgba32 color = Color.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + SolidBrush brush = Brushes.Solid(Color.HotPink); IPath path = new SixLabors.Shapes.Path( new LinearLineSegment( @@ -39,57 +39,57 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text new TextGraphicsOptions(true), "123", this.Font, - Brushes.Solid(Rgba32.Red), + Brushes.Solid(Color.Red), null, Vector2.Zero); - this.Verify>(0); + this.Verify(0); } [Fact] public void FillsForEachACharachterWhenBrushSetAndNotPenDefaultOptions() { - this.operations.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), null, Vector2.Zero); + this.operations.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero); - this.Verify>(0); + this.Verify(0); } [Fact] public void FillsForEachACharachterWhenBrushSet() { - this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Brushes.Solid(Rgba32.Red), Vector2.Zero); + this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); - this.Verify>(0); + this.Verify(0); } [Fact] public void FillsForEachACharachterWhenBrushSetDefaultOptions() { - this.operations.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), Vector2.Zero); + this.operations.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); - this.Verify>(0); + this.Verify(0); } [Fact] public void FillsForEachACharachterWhenColorSet() { - this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Rgba32.Red, Vector2.Zero); + this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Color.Red, Vector2.Zero); - var processor = this.Verify>(0); + var processor = this.Verify(0); - SolidBrush brush = Assert.IsType>(processor.Brush); - Assert.Equal(Rgba32.Red, brush.Color); + SolidBrush brush = Assert.IsType(processor.Brush); + Assert.Equal(Color.Red, brush.Color); } [Fact] public void FillsForEachACharachterWhenColorSetDefaultOptions() { - this.operations.DrawText("123", this.Font, Rgba32.Red, Vector2.Zero); + this.operations.DrawText("123", this.Font, Color.Red, Vector2.Zero); - var processor = this.Verify>(0); + var processor = this.Verify(0); - SolidBrush brush = Assert.IsType>(processor.Brush); - Assert.Equal(Rgba32.Red, brush.Color); + SolidBrush brush = Assert.IsType(processor.Brush); + Assert.Equal(Color.Red, brush.Color); } [Fact] @@ -100,39 +100,39 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text "123", this.Font, null, - Pens.Dash(Rgba32.Red, 1), + Pens.Dash(Color.Red, 1), Vector2.Zero); - var processor = this.Verify>(0); + var processor = this.Verify(0); } [Fact] public void DrawForEachACharachterWhenPenSetAndNotBrushDefaultOptions() { - this.operations.DrawText("123", this.Font, null, Pens.Dash(Rgba32.Red, 1), Vector2.Zero); + this.operations.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero); - var processor = this.Verify>(0); + 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); + this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); - var processor = this.Verify>(0); + var processor = this.Verify(0); } [Fact] public void DrawForEachACharachterWhenPenSetDefaultOptions() { - this.operations.DrawText("123", this.Font, Pens.Dash(Rgba32.Red, 1), Vector2.Zero); + this.operations.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); - var processor = this.Verify>(0); + 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); + var penBrush = Assert.IsType(processor.Pen.StrokeFill); + Assert.Equal(Color.Red, penBrush.Color); Assert.Equal(1, processor.Pen.StrokeWidth); Assert.Equal(PointF.Empty, processor.Location); } @@ -144,19 +144,19 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text new TextGraphicsOptions(true), "123", this.Font, - Brushes.Solid(Rgba32.Red), - Pens.Dash(Rgba32.Red, 1), + Brushes.Solid(Color.Red), + Pens.Dash(Color.Red, 1), Vector2.Zero); - var processor = this.Verify>(0); + 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); + var brush = Assert.IsType(processor.Brush); + Assert.Equal(Color.Red, brush.Color); Assert.Equal(PointF.Empty, processor.Location); - var penBrush = Assert.IsType>(processor.Pen.StrokeFill); - Assert.Equal(Rgba32.Red, penBrush.Color); + var penBrush = Assert.IsType(processor.Pen.StrokeFill); + Assert.Equal(Color.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 index 20ccb25a5..ebd9cf644 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text where TPixel : struct, IPixel { Font font = CreateFont("OpenSans-Regular.ttf", 36); - TPixel color = NamedColors.Black; + Color color = Color.Black; float padding = 5; var text = "A short piece of text"; @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text where TPixel : struct, IPixel { Font font = CreateFont(fontName, fontSize); - TPixel color = NamedColors.Black; + Color color = Color.Black; provider.VerifyOperation( TextDrawingComparer, @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text HorizontalAlignment = HorizontalAlignment.Left, }; - TPixel color = NamedColors.Black; + Color color = Color.Black; // Based on the reported 0.0270% difference with AccuracyMultiple = 8 // We should avoid quality regressions leading to higher difference! @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text where TPixel : struct, IPixel { Font font = CreateFont(fontName, fontSize); - TPixel color = NamedColors.Black; + Color color = Color.Black; provider.VerifyOperation( OutlinedTextDrawingComparer, @@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text where TPixel : struct, IPixel { Font font = CreateFont(fontName, fontSize); - TPixel color = NamedColors.Black; + Color color = Color.Black; provider.VerifyOperation( OutlinedTextDrawingComparer, @@ -217,7 +217,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text var comparer = ImageComparer.TolerantPercentage(0.2f); provider.RunValidatingProcessorTest( - x => x.DrawText(textOptions, text, font, NamedColors.Black, new PointF(10, 50)), + x => x.DrawText(textOptions, text, font, Color.Black, new PointF(10, 50)), details, comparer, appendPixelTypeToFileName: false, diff --git a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs index ff4014e61..c3ae0cddf 100644 --- a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs +++ b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests public MemoryAllocator MemoryAllocator => this.Source.GetConfiguration().MemoryAllocator; - public Image Apply() + public Image GetResultImage() { return this.Source; } @@ -67,29 +67,31 @@ namespace SixLabors.ImageSharp.Tests return this.Source.Size(); } - public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { - this.Applied.Add(new AppliedOperation - { - Processor = processor, - Rectangle = rectangle - }); + this.Applied.Add(new AppliedOperation() + { + Rectangle = rectangle, + NonGenericProcessor = processor + }); return this; } - public IImageProcessingContext ApplyProcessor(IImageProcessor processor) + public IImageProcessingContext ApplyProcessor(IImageProcessor processor) { - this.Applied.Add(new AppliedOperation - { - Processor = processor - }); + this.Applied.Add(new AppliedOperation() + { + NonGenericProcessor = processor + }); return this; } public struct AppliedOperation { public Rectangle? Rectangle { get; set; } - public IImageProcessor Processor { get; set; } + public IImageProcessor GenericProcessor { get; set; } + + public IImageProcessor NonGenericProcessor { get; set; } } } } diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index efd6c3b40..4f8475738 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // 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 diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 251475567..dadce92d1 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -1,41 +1,100 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; +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; public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Bmp.RLE, 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) + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + } + + [Theory] + [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(Bit16Inverted, PixelTypes.Rgba32)] + [WithFile(Bit8Inverted, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(new BmpDecoder())) { - image.DebugSave(provider, "bmp"); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(Bit1, PixelTypes.Rgba32)] + [WithFile(Bit1Pal1, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); + } + } + [Theory] + [WithFile(Bit4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new 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); @@ -44,25 +103,330 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [WithFile(F, CommonNonDefaultPixelTypes)] - public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + [WithFile(Bit8, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(Bit16, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(Rgba32v4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(new BmpDecoder())) { - image.DebugSave(provider, "bmp"); + 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 : struct, 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 : struct, 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 : struct, 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 : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(RLE8, PixelTypes.Rgba32)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(RLE24, PixelTypes.Rgba32)] + [WithFile(RLE24Cut, PixelTypes.Rgba32)] + [WithFile(RLE24Delta, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + 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 : struct, IPixel + { + using (Image image = provider.GetImage(new 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 + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(Rgba321010102, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + + // Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel + // seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3, + // which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set. + // The total difference without the alpha channel is still: 0.0204% + // Exporting the image as PNG with GIMP yields to the same result as the imagesharp implementation. + image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder()); + } + } + [Theory] [WithFile(WinBmpv2, PixelTypes.Rgba32)] + [WithFile(CoreHeader, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(new BmpDecoder())) { - image.DebugSave(provider, "png"); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(WinBmpv3, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new 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 : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + } + + [Theory] + [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] + public void BmpDecoder_ThrowsImageFormatException_OnInvalidPaletteSize(TestImageProvider provider) + where TPixel : struct, IPixel + { + Assert.Throws( () => { using (Image image = provider.GetImage(new BmpDecoder())) { } }); + } + + [Theory] + [WithFile(Rgb24jpeg, PixelTypes.Rgba32)] + [WithFile(Rgb24png, PixelTypes.Rgba32)] + public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider) + where TPixel : struct, IPixel + { + Assert.Throws(() => { using (Image image = provider.GetImage(new BmpDecoder())) { } }); + } + + [Theory] + [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(Rgba32bf56AdobeV3, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [Theory] + [WithFile(WinBmpv4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(WinBmpv5, PixelTypes.Rgba32)] + [WithFile(V5Header, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(Pal8Offset, PixelTypes.Rgba32)] + public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(F, CommonNonDefaultPixelTypes)] + public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); image.CompareToOriginal(provider); } } @@ -74,23 +438,53 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = provider.GetImage(new BmpDecoder())) { - image.DebugSave(provider, "png"); + image.DebugSave(provider); image.CompareToOriginal(provider); } } [Theory] + [InlineData(Bit32Rgb, 32)] + [InlineData(Bit32Rgba, 32)] [InlineData(Car, 24)] [InlineData(F, 24)] [InlineData(NegHeight, 24)] + [InlineData(Bit16, 16)] + [InlineData(Bit16Inverted, 16)] [InlineData(Bit8, 8)] [InlineData(Bit8Inverted, 8)] - 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)) { - Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); + 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)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); } } @@ -104,7 +498,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); @@ -119,12 +513,49 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = provider.GetImage(new BmpDecoder())) { - image.DebugSave(provider, "png"); + image.DebugSave(provider); + + // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. + // image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(Os2v2, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + + // TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it, + // but i think incorrectly. I have loaded the image with GIMP and exported as PNG. + // The results are the same as the image sharp implementation. + // image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + + [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 : struct, IPixel + { + using (Image image = provider.GetImage(new 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); } } } -} \ 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 b9f855cf1..7412f70a1 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -1,17 +1,26 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // 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 +32,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.RLE, 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 +55,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 +64,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 +80,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 +89,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.GetFormatMetadata(BmpFormat.Instance); Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); } @@ -102,19 +111,157 @@ namespace SixLabors.ImageSharp.Tests public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); - private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel 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 : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + public void Encode_32Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + // WinBmpv3 is a 24 bits per pixel image + [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + public void Encode_24Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, 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 : struct, 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 : struct, 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 : struct, 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 : struct, 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 : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] + public void Encode_8BitGray_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: false); + + [Theory] + [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] + public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: true); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) where TPixel : struct, 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 WuQuantizer(256) + }; + 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 : struct, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + return; + } + + using (Image image = provider.GetImage()) + { + var encoder = new BmpEncoder + { + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new OctreeQuantizer(256) + }; + 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 : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + private static void TestBmpEncoderCore( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel, + bool supportTransparency = true, + ImageComparer customComparer = null) + where TPixel : struct, 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/BmpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs index 991768a11..da17dfb98 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs @@ -1,22 +1,50 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // 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(); + 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.GetFormatMetadata(BmpFormat.Instance); + Assert.NotNull(bitmapMetaData); + Assert.Equal(expectedInfoHeaderType, bitmapMetaData.InfoHeaderType); + } + } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 158a085d5..b2f9788ae 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -27,8 +27,8 @@ namespace SixLabors.ImageSharp.Tests { 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,7 +40,7 @@ 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"; File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); @@ -55,10 +55,9 @@ namespace SixLabors.ImageSharp.Tests foreach (TestFile file in Files) { - using (Image image = file.CreateImage()) + using (Image image = file.CreateRgba32Image()) { - var encoder = new PngEncoder { Quantizer = new WuQuantizer(KnownDiffusers.JarvisJudiceNinke, 256), ColorType = PngColorType.Palette }; - image.Save($"{path}/{file.FileName}.png", encoder); + image.Save($"{path}/{file.FileName}"); } } } @@ -103,7 +102,7 @@ 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")) { diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 5bfbb058b..784f7ce70 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -7,7 +7,7 @@ using System.IO; using System.Text; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -40,7 +40,7 @@ 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.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var decoder = new GifDecoder(); 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); @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { var decoder = new GifDecoder(); IImageInfo image = decoder.Identify(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); @@ -165,11 +165,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image image = testFile.CreateImage(options)) + using (Image image = testFile.CreateRgba32Image(options)) { - Assert.Equal(1, image.MetaData.Properties.Count); - Assert.Equal("Comments", image.MetaData.Properties[0].Name); - Assert.Equal("ImageSharp", image.MetaData.Properties[0].Value); + Assert.Equal(1, image.Metadata.Properties.Count); + Assert.Equal("Comments", image.Metadata.Properties[0].Name); + Assert.Equal("ImageSharp", image.Metadata.Properties[0].Value); } } @@ -183,9 +183,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image image = testFile.CreateImage(options)) + using (Image image = testFile.CreateRgba32Image(options)) { - Assert.Equal(0, image.MetaData.Properties.Count); + Assert.Equal(0, image.Metadata.Properties.Count); } } @@ -199,10 +199,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image image = testFile.CreateImage(options)) + using (Image image = testFile.CreateRgba32Image(options)) { - Assert.Equal(1, image.MetaData.Properties.Count); - Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value); + Assert.Equal(1, image.Metadata.Properties.Count); + Assert.Equal("浉条卥慨灲", image.Metadata.Properties[0].Value); } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index b74a7a85f..eab30944e 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -3,7 +3,7 @@ using System.IO; 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; @@ -20,7 +20,7 @@ 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.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; @@ -45,7 +45,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 +58,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 +67,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 +83,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,9 +92,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); + Assert.Equal(1, output.Metadata.Properties.Count); + Assert.Equal("Comments", output.Metadata.Properties[0].Name); + Assert.Equal("ImageSharp", output.Metadata.Properties[0].Value); } } } @@ -107,9 +107,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { - input.MetaData.Properties.Clear(); + input.Metadata.Properties.Clear(); using (var memStream = new MemoryStream()) { input.SaveAsGif(memStream, options); @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif memStream.Position = 0; using (var output = Image.Load(memStream)) { - Assert.Equal(0, output.MetaData.Properties.Count); + Assert.Equal(0, output.Metadata.Properties.Count); } } } @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var input = new Image(1, 1)) { string comments = new string('c', 256); - input.MetaData.Properties.Add(new ImageProperty("Comments", comments)); + input.Metadata.Properties.Add(new ImageProperty("Comments", comments)); using (var memStream = new MemoryStream()) { @@ -138,9 +138,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(255, output.MetaData.Properties[0].Value.Length); + Assert.Equal(1, output.Metadata.Properties.Count); + Assert.Equal("Comments", output.Metadata.Properties[0].Name); + Assert.Equal(255, output.Metadata.Properties[0].Value.Length); } } } @@ -180,9 +180,9 @@ 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.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetFormatMetadata(GifFormat.Instance); GifColorTableMode colorMode = metaData.ColorTableMode; var encoder = new GifEncoder() { @@ -194,9 +194,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif outStream.Position = 0; outStream.Position = 0; - var clone = Image.Load(outStream); + var clone = Image.Load(outStream); - GifMetaData cloneMetaData = clone.MetaData.GetFormatMetaData(GifFormat.Instance); + GifMetadata cloneMetaData = clone.Metadata.GetFormatMetadata(GifFormat.Instance); Assert.Equal(metaData.ColorTableMode, cloneMetaData.ColorTableMode); // Gifiddle and Cyotek GifInfo say this image has 64 colors. @@ -204,8 +204,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif 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.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata cifm = clone.Frames[i].Metadata.GetFormatMetadata(GifFormat.Instance); 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 index a39fc47b4..d82bfbf22 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs @@ -11,14 +11,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Fact] public void CloneIsDeep() { - var meta = new GifFrameMetaData() + var meta = new GifFrameMetadata() { FrameDelay = 1, DisposalMethod = GifDisposalMethod.RestoreToBackground, ColorTableLength = 2 }; - var clone = (GifFrameMetaData)meta.DeepClone(); + var clone = (GifFrameMetadata)meta.DeepClone(); clone.FrameDelay = 2; clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs index 29db32b4a..8510a3461 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs @@ -11,14 +11,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Fact] public void CloneIsDeep() { - var meta = new GifMetaData() + var meta = new GifMetadata() { RepeatCount = 1, ColorTableMode = GifColorTableMode.Global, GlobalColorTableLength = 2 }; - var clone = (GifMetaData)meta.DeepClone(); + var clone = (GifMetadata)meta.DeepClone(); clone.RepeatCount = 2; clone.ColorTableMode = GifColorTableMode.Local; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 81c76390c..7e7218c9d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.PrintLinearData(input); Block8x8F dest = block; - dest.NormalizeColorsInplace(); + dest.NormalizeColorsInplace(255); float[] array = new float[64]; dest.CopyTo(array); @@ -253,11 +253,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); Block8x8F expected = source; - expected.NormalizeColorsInplace(); + expected.NormalizeColorsInplace(255); expected.RoundInplace(); Block8x8F actual = source; - actual.NormalizeColorsAndRoundInplaceAvx2(); + actual.NormalizeColorsAndRoundInplaceAvx2(255); this.Output.WriteLine(expected.ToString()); this.Output.WriteLine(actual.ToString()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs index b2dc3534d..aebf80d08 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs @@ -2,7 +2,7 @@ // 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 8e30eb9e5..caaad73c9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) { ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrBasic(), + new JpegColorConverter.FromYCbCrBasic(8), 3, inputBufferLength, resultBufferLength, @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg JpegColorConverter.ComponentValues values = CreateRandomValues(3, size, seed); var result = new Vector4[size]; - JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result); + JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result, 255, 128); for (int i = 0; i < size; i++) { @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FromYCbCrSimd(int inputBufferLength, int resultBufferLength, int seed) { ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrSimd(), + new JpegColorConverter.FromYCbCrSimd(8), 3, inputBufferLength, resultBufferLength, @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg //JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrSimdAvx2(), + new JpegColorConverter.FromYCbCrSimdAvx2(8), 3, inputBufferLength, resultBufferLength, @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); var result = new Vector4[count]; - JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd() : new JpegColorConverter.FromYCbCrBasic(); + JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd(8) : new JpegColorConverter.FromYCbCrBasic(8); // Warm up: converter.ConvertToRgba(values, result); @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk, 8); JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); var result = new Vector4[resultBufferLength]; @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed) { - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale, 8); JpegColorConverter.ComponentValues values = CreateRandomValues(1, inputBufferLength, seed); var result = new Vector4[resultBufferLength]; @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed) { - var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB, 8); JpegColorConverter.ComponentValues values = CreateRandomValues(3, inputBufferLength, seed); var result = new Vector4[resultBufferLength]; @@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck, 8); JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); var result = new Vector4[resultBufferLength]; @@ -308,7 +308,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int seed) { ValidateRgbToYCbCrConversion( - JpegColorConverter.GetConverter(colorSpace), + JpegColorConverter.GetConverter(colorSpace,8), componentCount, inputBufferLength, resultBufferLength, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 73167a4b7..31a5a0eeb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -35,12 +34,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [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)); - } + [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] + public void UnrecoverableImagesShouldThrowCorrectError(TestImageProvider provider) + where TPixel : struct, 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 40de25b30..4a3ef9b95 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -15,12 +15,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Testorig420, - // BUG: The following image has a high difference compared to the expected output: + // 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, @@ -28,7 +30,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.ExifResizeOutOfRange696, TestImages.Jpeg.Issues.InvalidAPP0721, TestImages.Jpeg.Issues.ExifGetString750Load, - TestImages.Jpeg.Issues.ExifGetString750Transform + TestImages.Jpeg.Issues.ExifGetString750Transform, + + // 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, + + // High depth images + TestImages.Jpeg.Baseline.Testorig12bit, }; public static string[] ProgressiveTestJpegs = @@ -50,6 +61,32 @@ 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.IndexOutOfRangeException824C, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824D, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824E, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824F, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824G, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824H, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825A, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825C, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825D, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826A, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826B, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826C, + TestImages.Jpeg.Issues.Fuzz.AccessViolationException827, + TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839 + }; + private static readonly Dictionary CustomToleranceValues = new Dictionary { @@ -70,4 +107,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 index 4810985f1..308cf28b3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs @@ -3,8 +3,8 @@ using System.IO; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg; - using SixLabors.ImageSharp.MetaData; + using SixLabors.ImageSharp.Metadata; public partial class JpegDecoderTests { @@ -52,8 +52,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public static readonly TheoryData QualityFiles = new TheoryData { - { TestImages.Jpeg.Baseline.Calliphora, 80}, - { TestImages.Jpeg.Progressive.Fb, 75 } + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 }, + { TestImages.Jpeg.Issues.IncorrectQuality845, 99 } }; [Theory] @@ -84,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var decoder = new JpegDecoder(); 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); @@ -101,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { var decoder = new JpegDecoder(); IImageInfo image = decoder.Identify(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); @@ -117,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { var decoder = new JpegDecoder(); IImageInfo image = decoder.Identify(Configuration.Default, stream); - JpegMetaData meta = image.MetaData.GetFormatMetaData(JpegFormat.Instance); + JpegMetadata meta = image.Metadata.GetFormatMetadata(JpegFormat.Instance); Assert.Equal(quality, meta.Quality); } } @@ -132,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var decoder = new JpegDecoder(); using (Image image = decoder.Decode(Configuration.Default, stream)) { - JpegMetaData meta = image.MetaData.GetFormatMetaData(JpegFormat.Instance); + JpegMetadata meta = image.Metadata.GetFormatMetadata(JpegFormat.Instance); Assert.Equal(quality, meta.Quality); } } @@ -179,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel); } - ExifProfile exifProfile = imageInfo.MetaData.ExifProfile; + ExifProfile exifProfile = imageInfo.Metadata.ExifProfile; if (exifProfilePresent) { @@ -191,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Null(exifProfile); } - IccProfile iccProfile = imageInfo.MetaData.IccProfile; + IccProfile iccProfile = imageInfo.Metadata.IccProfile; if (iccProfilePresent) { @@ -215,17 +216,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Snake.jpg has both Exif and ICC profiles defined: var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); - using (Image image = testFile.CreateImage(decoder)) + using (Image image = testFile.CreateRgba32Image(decoder)) { if (ignoreMetaData) { - Assert.Null(image.MetaData.ExifProfile); - Assert.Null(image.MetaData.IccProfile); + Assert.Null(image.Metadata.ExifProfile); + Assert.Null(image.Metadata.IccProfile); } else { - Assert.NotNull(image.MetaData.ExifProfile); - Assert.NotNull(image.MetaData.IccProfile); + Assert.NotNull(image.Metadata.ExifProfile); + Assert.NotNull(image.Metadata.IccProfile); } } } @@ -238,8 +239,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImageInfo(TestImages.Jpeg.Baseline.Floorplan, JpegDecoder, useIdentify, imageInfo => { - Assert.Equal(300, imageInfo.MetaData.HorizontalResolution); - Assert.Equal(300, imageInfo.MetaData.VerticalResolution); + Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(300, imageInfo.Metadata.VerticalResolution); }); } @@ -251,8 +252,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImageInfo(TestImages.Jpeg.Baseline.Jpeg420Exif, JpegDecoder, useIdentify, imageInfo => { - Assert.Equal(72, imageInfo.MetaData.HorizontalResolution); - Assert.Equal(72, imageInfo.MetaData.VerticalResolution); + Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(72, imageInfo.Metadata.VerticalResolution); }); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 15f7f92a8..e316fe67e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -147,8 +147,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); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 598d99274..d9013b507 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -3,7 +3,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -48,7 +48,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 +57,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.GetFormatMetadata(JpegFormat.Instance); Assert.Equal(quality, meta.Quality); } } @@ -137,7 +137,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 +160,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 +180,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 +189,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); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs index 431de4be3..793bdd522 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs @@ -11,8 +11,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void CloneIsDeep() { - var meta = new JpegMetaData() { Quality = 50 }; - var clone = (JpegMetaData)meta.DeepClone(); + var meta = new JpegMetadata() { Quality = 50 }; + var clone = (JpegMetadata)meta.DeepClone(); clone.Quality = 99; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs new file mode 100644 index 000000000..e59584991 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// 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 + short[] 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/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index f51f9b6c5..5bb2db784 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming @@ -8,7 +8,7 @@ using System.IO; using System.Text; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -41,7 +41,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png TestImages.Png.Rgb24BppTrans, TestImages.Png.GrayAlpha8Bit, - TestImages.Png.Gray1BitTrans + TestImages.Png.Gray1BitTrans, + TestImages.Png.Bad.ZlibOverflow, + TestImages.Png.Bad.ZlibOverflow2 }; public static readonly string[] TestImages48Bpp = @@ -67,6 +69,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png TestImages.Png.GrayTrns16BitInterlaced }; + public static readonly string[] TestImagesGray8BitInterlaced = + { + TestImages.Png.GrayAlpha1BitInterlaced, + TestImages.Png.GrayAlpha2BitInterlaced, + TestImages.Png.Gray4BitInterlaced, + TestImages.Png.GrayAlpha8BitInterlaced + }; + public static readonly TheoryData RatioFiles = new TheoryData { @@ -123,6 +133,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [Theory] + [WithFileCollection(nameof(TestImagesGray8BitInterlaced), PixelTypes.Rgba32)] + public void Decoder_Gray8bitInterlaced(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } + [Theory] [WithFileCollection(nameof(TestImagesGray16Bit), PixelTypes.Rgb48)] public void Decode_Gray16Bit(TestImageProvider provider) @@ -147,6 +169,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [Theory] + [WithFile(TestImages.Png.GrayAlpha8BitInterlaced, PixelTypes)] + public void Decoder_CanDecodeGrey8bitWithAlpha(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } + [Theory] [WithFile(TestImages.Png.Splash, PixelTypes)] public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) @@ -169,11 +203,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var testFile = TestFile.Create(TestImages.Png.Blur); - using (Image image = testFile.CreateImage(options)) + using (Image image = testFile.CreateRgba32Image(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); + 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); } } @@ -187,9 +221,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var testFile = TestFile.Create(TestImages.Png.Blur); - using (Image image = testFile.CreateImage(options)) + using (Image image = testFile.CreateRgba32Image(options)) { - Assert.Equal(0, image.MetaData.Properties.Count); + Assert.Equal(0, image.Metadata.Properties.Count); } } @@ -203,10 +237,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var testFile = TestFile.Create(TestImages.Png.Blur); - using (Image image = testFile.CreateImage(options)) + using (Image image = testFile.CreateRgba32Image(options)) { - Assert.Equal(1, image.MetaData.Properties.Count); - Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); + Assert.Equal(1, image.Metadata.Properties.Count); + Assert.Equal("潓瑦慷敲", image.Metadata.Properties[0].Name); } } @@ -237,7 +271,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); 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); @@ -254,11 +288,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { var decoder = new PngDecoder(); IImageInfo image = decoder.Identify(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); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 9079b15fb..b8178fd4f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -7,7 +7,7 @@ using System.Linq; 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.ImageComparison; @@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var options = new PngEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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); @@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var options = new PngEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateImage()) + using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { @@ -253,7 +253,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png memStream.Position = 0; using (var output = Image.Load(memStream)) { - PngMetaData meta = output.MetaData.GetFormatMetaData(PngFormat.Instance); + PngMetadata meta = output.Metadata.GetFormatMetadata(PngFormat.Instance); Assert.Equal(pngBitDepth, meta.BitDepth); } @@ -268,9 +268,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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); + PngMetadata inMeta = input.Metadata.GetFormatMetadata(PngFormat.Instance); Assert.True(inMeta.HasTrans); using (var memStream = new MemoryStream()) @@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png memStream.Position = 0; using (var output = Image.Load(memStream)) { - PngMetaData outMeta = output.MetaData.GetFormatMetaData(PngFormat.Instance); + PngMetadata outMeta = output.Metadata.GetFormatMetadata(PngFormat.Instance); Assert.True(outMeta.HasTrans); switch (pngColorType) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs index a21bb9acb..72fc2f865 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs @@ -11,13 +11,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Fact] public void CloneIsDeep() { - var meta = new PngMetaData() + var meta = new PngMetadata() { BitDepth = PngBitDepth.Bit16, ColorType = PngColorType.GrayscaleWithAlpha, Gamma = 2 }; - var clone = (PngMetaData)meta.DeepClone(); + var clone = (PngMetadata)meta.DeepClone(); clone.BitDepth = PngBitDepth.Bit2; clone.ColorType = PngColorType.Palette; diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs index ef6b133f7..ee309e0e3 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs @@ -9,6 +9,7 @@ using System.Threading; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -334,5 +335,39 @@ namespace SixLabors.ImageSharp.Tests.Helpers TestImageExtensions.CompareBuffers(expected.Span, actual.Span); } } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsRequiresValidRectangle(int width, int height) + { + var parallelSettings = new ParallelExecutionSettings(); + + var rect = new Rectangle(0, 0, width, height); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelHelper.IterateRows(rect, parallelSettings, (rows) => { })); + + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) + { + var parallelSettings = new ParallelExecutionSettings(); + + var rect = new Rectangle(0, 0, width, height); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelHelper.IterateRowsWithTempBuffer(rect, parallelSettings, (rows, memory) => { })); + + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs index 629b3cdeb..3aead6aaa 100644 --- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -34,5 +34,54 @@ namespace SixLabors.ImageSharp.Tests.Helpers Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); } } + + [Fact] + public void Slice1() + { + RowInterval rowInterval = new RowInterval(10, 20); + RowInterval sliced = rowInterval.Slice(5); + + Assert.Equal(15, sliced.Min); + Assert.Equal(20, sliced.Max); + } + + [Fact] + public void Slice2() + { + RowInterval rowInterval = new RowInterval(10, 20); + RowInterval sliced = rowInterval.Slice(3, 5); + + Assert.Equal(13, sliced.Min); + Assert.Equal(18, sliced.Max); + } + + [Fact] + public void Equality_WhenTrue() + { + RowInterval a = new RowInterval(42, 123); + RowInterval b = new RowInterval(42, 123); + + Assert.True(a.Equals(b)); + Assert.True(a.Equals((object)b)); + Assert.True(a == b); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + RowInterval a = new RowInterval(42, 123); + RowInterval b = new RowInterval(42, 125); + RowInterval c = new RowInterval(40, 123); + + Assert.False(a.Equals(b)); + Assert.False(c.Equals(a)); + Assert.False(b.Equals(c)); + + Assert.False(a.Equals((object)b)); + Assert.False(a.Equals(null)); + Assert.False(a == b); + Assert.True(a != c); + } } } diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs index 9416be740..f2e98b131 100644 --- a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -11,6 +11,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class Vector4UtilsTests { + private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + [Theory] [InlineData(0)] [InlineData(1)] @@ -23,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers Vector4Utils.Premultiply(source); - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + Assert.Equal(expected, source, this.ApproximateFloatComparer); } [Theory] @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers Vector4Utils.UnPremultiply(source); - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + Assert.Equal(expected, source, this.ApproximateFloatComparer); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs new file mode 100644 index 000000000..37c3a27cd --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -0,0 +1,346 @@ +// // Copyright (c) Six Labors and contributors. +// // 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("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_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 000000000..0956cce9c --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -0,0 +1,325 @@ +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +using System; +using System.Drawing.Imaging; +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 (Image sourceImage = new Image( + this.Image.GetConfiguration(), + this.Image.Width, + this.Image.Height, + (Bgra32)Color.Blue)) + { + this.Collection.AddFrame(sourceImage.Frames.RootFrame); + } + + Rgba32[] expectedAllBlue = + Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); + + Assert.Equal(2, this.Collection.Count); + ImageFrame actualFrame = (ImageFrame)this.Collection[1]; + + actualFrame.ComparePixelBufferTo(expectedAllBlue); + } + + [Fact] + public void InsertFrame_OfDifferentPixelType() + { + using (Image sourceImage = new Image( + this.Image.GetConfiguration(), + this.Image.Width, + this.Image.Height, + (Bgra32)Color.Blue)) + { + this.Collection.InsertFrame(0, sourceImage.Frames.RootFrame); + } + + Rgba32[] expectedAllBlue = + Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); + + Assert.Equal(2, this.Collection.Count); + ImageFrame actualFrame = (ImageFrame)this.Collection[0]; + + actualFrame.ComparePixelBufferTo(expectedAllBlue); + + } + + [Fact] + public void Constructor_ShouldCreateOneFrame() + { + Assert.Equal(1, this.Collection.Count); + } + + [Fact] + public void AddNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + this.Collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1)); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } + + [Fact] + public void AddNewFrame_Frame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.AddFrame((ImageFrame)null); + }); + + Assert.StartsWith("Value cannot be 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("Value cannot be 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 : struct, IPixel + { + using (Image img = provider.GetImage()) + { + ImageFrameCollection nonGenericFrameCollection = img.Frames; + + nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); // add a frame anyway + using (Image cloned = nonGenericFrameCollection.CloneFrame(0)) + { + Assert.Equal(2, img.Frames.Count); + + Image expectedClone = (Image)cloned; + + expectedClone.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(); + + ImageFrameCollection nonGenericFrameCollection = img.Frames; + + nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); + using (Image cloned = nonGenericFrameCollection.ExportFrame(0)) + { + Assert.Equal(1, img.Frames.Count); + + Image expectedClone = (Image)cloned; + expectedClone.ComparePixelBufferTo(sourcePixelData); + } + } + } + + [Fact] + public void CreateFrame_Default() + { + this.Image.Frames.CreateFrame(); + + Assert.Equal(2, this.Image.Frames.Count); + + ImageFrame frame = (ImageFrame)this.Image.Frames[1]; + + frame.ComparePixelBufferTo(default(Rgba32)); + } + + [Fact] + public void CreateFrame_CustomFillColor() + { + this.Image.Frames.CreateFrame(Rgba32.HotPink); + + Assert.Equal(2, this.Image.Frames.Count); + + ImageFrame frame = (ImageFrame)this.Image.Frames[1]; + + frame.ComparePixelBufferTo(Rgba32.HotPink); + } + + [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_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. + /// + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void ConstructGif_FromDifferentPixelTypes(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (Image 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 : struct, 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.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata bData = b.Metadata.GetFormatMetadata(GifFormat.Instance); + + 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 000000000..a29f45271 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +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/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs deleted file mode 100644 index 392397057..000000000 --- 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 041b6c846..000000000 --- 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/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index aabc3f50e..ec6705d0e 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -16,7 +16,9 @@ namespace SixLabors.ImageSharp.Tests { public abstract class ImageLoadTestBase : IDisposable { - protected Image returnImage; + protected Image localStreamReturnImageRgba32; + + protected Image localStreamReturnImageAgnostic; protected Mock localDecoder; @@ -48,12 +50,14 @@ namespace SixLabors.ImageSharp.Tests protected ImageLoadTestBase() { - this.returnImage = new Image(1, 1); + this.localStreamReturnImageRgba32 = new Image(1, 1); + this.localStreamReturnImageAgnostic = new Image(1, 1); this.localImageFormatMock = new Mock(); this.localDecoder = new Mock(); this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => { @@ -63,7 +67,19 @@ 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.LocalConfiguration = new Configuration { @@ -85,7 +101,8 @@ namespace SixLabors.ImageSharp.Tests public void Dispose() { // clean up the global object; - this.returnImage?.Dispose(); + this.localStreamReturnImageRgba32?.Dispose(); + this.localStreamReturnImageAgnostic?.Dispose(); } } } 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 1a21d3d10..000000000 --- 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 000000000..92159f0c9 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs @@ -0,0 +1,100 @@ +// // Copyright (c) Six Labors and contributors. +// // 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() + { + System.IO.FileNotFoundException ex = Assert.Throws( + () => + { + Image.Load(this.TopLevelConfiguration, Guid.NewGuid().ToString()); + }); + } + + [Fact] + public void WhenPathIsNull_Throws() + { + ArgumentNullException ex = Assert.Throws( + () => + { + Image.Load(this.TopLevelConfiguration,(string)null); + }); + } + } + } +} \ No newline at end of file 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 000000000..19cf7ee64 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -0,0 +1,103 @@ +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +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 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 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() + { + 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_FromBytes.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes.cs deleted file mode 100644 index eed1a2825..000000000 --- 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 000000000..5fe87fedc --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs @@ -0,0 +1,119 @@ +// Copyright (c) Six Labors and contributors. +// 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 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 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) + { + TestFormat 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) + { + TestFormat 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); + } + } + } +} \ No newline at end of file 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 000000000..67b19a086 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +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); + } + } + } + } +} \ No newline at end of file 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 6b6acb1b8..000000000 --- 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 000000000..5ca86c3ce --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// 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() + { + 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)); + } + + [Fact] + public void Configuration_Stream_Decoder_Agnostic() + { + 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)); + } + + [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); + } + } + } +} \ No newline at end of file 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 000000000..df1f73c7a --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +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_UknownImageFormatException() + { + Assert.Throws(() => + { + using (var img = Image.Load(Configuration.Default, this.Stream, out IImageFormat format)) + { + } + }); + } + + [Fact] + public void Image_Load_T_Throws_UknownImageFormatException() + { + Assert.Throws(() => + { + using (var img = 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 000000000..980ed17ce --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +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 Stream { get; } = new MemoryStream(Data); + + 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); + } + } + + public void Dispose() + { + this.Stream?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 16d999ad2..ea9957314 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -9,7 +9,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Shapes; using SixLabors.ImageSharp.Processing; @@ -85,7 +85,7 @@ 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); @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests 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,8 +113,8 @@ 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)) { @@ -144,8 +144,8 @@ 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)) { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index c5c7d19e1..60384c057 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; @@ -72,14 +72,14 @@ 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)) { 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); } diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs index 91f6804c0..67804a18f 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/tests/ImageSharp.Tests/ImageInfoTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.Primitives; using Xunit; @@ -19,7 +19,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 +28,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 index 869882f67..30371f000 100644 --- a/tests/ImageSharp.Tests/ImageOperationTests.cs +++ b/tests/ImageSharp.Tests/ImageOperationTests.cs @@ -21,14 +21,15 @@ namespace SixLabors.ImageSharp.Tests { private readonly Image image; private readonly FakeImageOperationsProvider provider; - private readonly IImageProcessor processor; - - public Configuration Configuration { get; private set; } + private readonly IImageProcessor processorDefinition; public ImageOperationTests() { this.provider = new FakeImageOperationsProvider(); - this.processor = new Mock>().Object; + + Mock processorMock = new Mock(); + this.processorDefinition = processorMock.Object; + this.image = new Image(new Configuration() { ImageOperationsProvider = this.provider @@ -38,61 +39,73 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void MutateCallsImageOperationsProvider_Func_OriginalImage() { - this.image.Mutate(x => x.ApplyProcessor(this.processor)); + this.image.Mutate(x => x.ApplyProcessor(this.processorDefinition)); Assert.True(this.provider.HasCreated(this.image)); - Assert.Contains(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + Assert.Contains( + this.processorDefinition, + this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); } [Fact] public void MutateCallsImageOperationsProvider_ListOfProcessors_OriginalImage() { - this.image.Mutate(this.processor); + this.image.Mutate(this.processorDefinition); Assert.True(this.provider.HasCreated(this.image)); - Assert.Contains(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + 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.processor)); + Image returned = this.image.Clone(x => x.ApplyProcessor(this.processorDefinition)); Assert.True(this.provider.HasCreated(returned)); - Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor)); + Assert.Contains( + this.processorDefinition, + this.provider.AppliedOperations(returned).Select(x => x.NonGenericProcessor)); } [Fact] public void CloneCallsImageOperationsProvider_ListOfProcessors_WithDuplicateImage() { - Image returned = this.image.Clone(this.processor); + Image returned = this.image.Clone(this.processorDefinition); Assert.True(this.provider.HasCreated(returned)); - Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor)); + Assert.Contains( + this.processorDefinition, + this.provider.AppliedOperations(returned).Select(x => x.NonGenericProcessor)); } [Fact] public void CloneCallsImageOperationsProvider_Func_NotOnOrigional() { - Image returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); + Image returned = this.image.Clone(x => x.ApplyProcessor(this.processorDefinition)); Assert.False(this.provider.HasCreated(this.image)); - Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + Assert.DoesNotContain( + this.processorDefinition, + this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); } [Fact] public void CloneCallsImageOperationsProvider_ListOfProcessors_NotOnOrigional() { - Image returned = this.image.Clone(this.processor); + Image returned = this.image.Clone(this.processorDefinition); Assert.False(this.provider.HasCreated(this.image)); - Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + Assert.DoesNotContain( + this.processorDefinition, + this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); } [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)); + operations.ApplyProcessors(this.processorDefinition); + Assert.Contains(this.processorDefinition, operations.Applied.Select(x => x.NonGenericProcessor)); } public void Dispose() => this.image.Dispose(); diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 7452d6e49..4f92f4007 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -1,6 +1,8 @@ - + + + - net462;net472;netcoreapp2.1 + netcoreapp2.1;net462;net472 True latest full @@ -9,40 +11,21 @@ SixLabors.ImageSharp.Tests SixLabors.ImageSharp.Tests AnyCPU;x64;x86 + SixLabors.ImageSharp.Tests + netcoreapp2.1;net462;net472 - - false - - - - false - - - - false - - - - - - - - - - - - - - - + - - - + + + + + - + + PreserveNewest @@ -54,4 +37,11 @@ PreserveNewest + + + + + + + diff --git a/tests/ImageSharp.Tests/Issues/Issue412.cs b/tests/ImageSharp.Tests/Issues/Issue412.cs index b0374ce1f..7b2cfd81c 100644 --- a/tests/ImageSharp.Tests/Issues/Issue412.cs +++ b/tests/ImageSharp.Tests/Issues/Issue412.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Issues { context.DrawLines( new GraphicsOptions(false), - NamedColors.Black, + Color.Black, 1, new[] { @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Issues context.DrawLines( new GraphicsOptions(false), - NamedColors.Red, + Color.Red, 1, new[] { @@ -46,8 +46,9 @@ namespace SixLabors.ImageSharp.Tests.Issues { for (var x = 0; x < 40; x++) { + TPixel red = Color.Red.ToPixel(); - Assert.True(NamedColors.Red.Equals(image[x, y]), $"expected {NamedColors.Red} but found {image[x, y]} at [{x}, {y}]"); + Assert.True(red.Equals(image[x, y]), $"expected {Color.Red} but found {image[x, y]} at [{x}, {y}]"); } } } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 19ec725f2..4af3b81e2 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -127,5 +127,56 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.Equal(new Size(10, 5), b.Size()); } } + + [Theory] + [InlineData(100, 20, 0, 90, 10)] + [InlineData(100, 3, 0, 50, 50)] + [InlineData(123, 23, 10, 80, 13)] + [InlineData(10, 1, 3, 6, 3)] + [InlineData(2, 2, 0, 1, 1)] + [InlineData(5, 1, 1, 3, 2)] + public void CopyColumns(int width, int height, int startIndex, int destIndex, int columnCount) + { + Random rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) + { + rnd.RandomFill(b.Span, 0, 1); + + b.CopyColumns(startIndex, destIndex, columnCount); + + for (int y = 0; y < b.Height; y++) + { + Span row = b.GetRowSpan(y); + + Span s = row.Slice(startIndex, columnCount); + Span d = row.Slice(destIndex, columnCount); + + Xunit.Assert.True(s.SequenceEqual(d)); + } + } + } + + [Fact] + public void CopyColumns_InvokeMultipleTimes() + { + Random rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) + { + rnd.RandomFill(b.Span, 0, 1); + + b.CopyColumns(0, 50, 22); + b.CopyColumns(0, 50, 22); + + for (int y = 0; y < b.Height; y++) + { + Span row = b.GetRowSpan(y); + + Span s = row.Slice(0, 22); + Span d = row.Slice(50, 22); + + Xunit.Assert.True(s.SequenceEqual(d)); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs index 8c4903960..bafb117f7 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -19,14 +19,14 @@ namespace SixLabors.ImageSharp.Tests const int colorTableLength = 128; const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; - var metaData = new ImageFrameMetaData(); - GifFrameMetaData gifFrameMetaData = metaData.GetFormatMetaData(GifFormat.Instance); + 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); + var clone = new ImageFrameMetadata(metaData); + GifFrameMetadata cloneGifFrameMetaData = clone.GetFormatMetadata(GifFormat.Instance); Assert.Equal(frameDelay, cloneGifFrameMetaData.FrameDelay); Assert.Equal(colorTableLength, cloneGifFrameMetaData.ColorTableLength); @@ -36,9 +36,9 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CloneIsDeep() { - var metaData = new ImageFrameMetaData(); - ImageFrameMetaData clone = metaData.DeepClone(); - Assert.False(metaData.GetFormatMetaData(GifFormat.Instance).Equals(clone.GetFormatMetaData(GifFormat.Instance))); + 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 index b9619cb3f..5f02ce7ae 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -2,8 +2,8 @@ // 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.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; @@ -12,14 +12,14 @@ using Xunit; namespace SixLabors.ImageSharp.Tests { /// - /// Tests the class. + /// Tests the class. /// public class ImageMetaDataTests { [Fact] public void ConstructorImageMetaData() { - var metaData = new ImageMetaData(); + var metaData = new ImageMetadata(); var exifProfile = new ExifProfile(); var imageProperty = new ImageProperty("name", "value"); @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests metaData.VerticalResolution = 2; metaData.Properties.Add(imageProperty); - ImageMetaData clone = metaData.DeepClone(); + ImageMetadata clone = metaData.DeepClone(); Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); Assert.Equal(4, clone.HorizontalResolution); @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CloneIsDeep() { - var metaData = new ImageMetaData(); + var metaData = new ImageMetadata(); var exifProfile = new ExifProfile(); var imageProperty = new ImageProperty("name", "value"); @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests metaData.VerticalResolution = 2; metaData.Properties.Add(imageProperty); - ImageMetaData clone = metaData.DeepClone(); + ImageMetadata clone = metaData.DeepClone(); clone.HorizontalResolution = 2; clone.VerticalResolution = 4; @@ -58,13 +58,13 @@ namespace SixLabors.ImageSharp.Tests 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))); + Assert.False(metaData.GetFormatMetadata(GifFormat.Instance).Equals(clone.GetFormatMetadata(GifFormat.Instance))); } [Fact] public void HorizontalResolution() { - var metaData = new ImageMetaData(); + var metaData = new ImageMetadata(); Assert.Equal(96, metaData.HorizontalResolution); metaData.HorizontalResolution = 0; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void VerticalResolution() { - var metaData = new ImageMetaData(); + var metaData = new ImageMetadata(); Assert.Equal(96, metaData.VerticalResolution); metaData.VerticalResolution = 0; @@ -101,14 +101,14 @@ namespace SixLabors.ImageSharp.Tests 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.ExifProfile = exifProfile; + image.Metadata.HorizontalResolution = 400; + image.Metadata.VerticalResolution = 500; - image.MetaData.SyncProfiles(); + 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()); + 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 index b5886522a..8cce5ba41 100644 --- a/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using Xunit; namespace SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index c10ffb6c8..668e699c5 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -8,8 +8,8 @@ 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.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; @@ -41,19 +41,19 @@ namespace SixLabors.ImageSharp.Tests [InlineData(TestImageWriteFormat.Png)] public void Constructor(TestImageWriteFormat imageFormat) { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateImage(); + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateRgba32Image(); - Assert.Null(image.MetaData.ExifProfile); + Assert.Null(image.Metadata.ExifProfile); - image.MetaData.ExifProfile = new ExifProfile(); - image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); + 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()); + 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); + ExifValue value = image.Metadata.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); TestValue(value, "Dirk Lemstra"); } @@ -94,11 +94,11 @@ namespace SixLabors.ImageSharp.Tests profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); var image = new Image(1, 1); - image.MetaData.ExifProfile = profile; + image.Metadata.ExifProfile = profile; image = WriteAndRead(image, imageFormat); - profile = image.MetaData.ExifProfile; + profile = image.Metadata.ExifProfile; Assert.NotNull(profile); ExifValue value = profile.GetValue(ExifTag.ExposureTime); @@ -109,11 +109,11 @@ namespace SixLabors.ImageSharp.Tests profile = GetExifProfile(); profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); - image.MetaData.ExifProfile = profile; + image.Metadata.ExifProfile = profile; image = WriteAndRead(image, imageFormat); - profile = image.MetaData.ExifProfile; + profile = image.Metadata.ExifProfile; Assert.NotNull(profile); value = profile.GetValue(ExifTag.ExposureTime); @@ -126,25 +126,25 @@ namespace SixLabors.ImageSharp.Tests [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 image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); image = WriteAndReadJpeg(image); - ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + 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.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); image = WriteAndRead(image, imageFormat); - value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + 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.Metadata.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); image = WriteAndRead(image, imageFormat); - value = image.MetaData.ExifProfile.GetValue(ExifTag.FlashEnergy); + value = image.Metadata.ExifProfile.GetValue(ExifTag.FlashEnergy); Assert.NotNull(value); Assert.Equal(new Rational(double.PositiveInfinity), value.Value); } @@ -156,75 +156,75 @@ namespace SixLabors.ImageSharp.Tests { 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"); + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); - ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.Software); + 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)); + image.Metadata.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); - value = image.MetaData.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + 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)); + 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; + image.Metadata.HorizontalResolution = 150.0; - value = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); + 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); + image.Metadata.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); - value = image.MetaData.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + value = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); TestValue(value, (string)null); - image.MetaData.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); + image.Metadata.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); - value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); + 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()); + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(17, image.Metadata.ExifProfile.Values.Count()); - value = image.MetaData.ExifProfile.GetValue(ExifTag.Software); + value = image.Metadata.ExifProfile.GetValue(ExifTag.Software); TestValue(value, "ImageSharp"); - value = image.MetaData.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + value = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); TestValue(value, new SignedRational(75.55)); - value = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); + value = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); TestValue(value, new Rational(150.0)); - value = image.MetaData.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + value = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); Assert.Null(value); - value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); + value = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); TestValue(value, latitude); - image.MetaData.ExifProfile.Parts = ExifParts.ExifTags; + 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); + 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.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()); + Assert.Equal(7, image.Metadata.ExifProfile.Values.Count()); } [Fact] @@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Tests exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - var metaData = new ImageMetaData + var metaData = new ImageMetadata { ExifProfile = exifProfile, HorizontalResolution = 200, @@ -292,13 +292,13 @@ namespace SixLabors.ImageSharp.Tests ExifProfile expectedProfile = CreateExifProfile(); var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); expectedProfile.SetValue(exifValueToChange, junk.ToString()); - image.MetaData.ExifProfile = expectedProfile; + image.Metadata.ExifProfile = expectedProfile; // act Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); // assert - ExifProfile actualProfile = reloadedImage.MetaData.ExifProfile; + ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; Assert.NotNull(actualProfile); foreach (ExifTag expectedProfileTag in expectedProfileTags) { @@ -314,10 +314,10 @@ namespace SixLabors.ImageSharp.Tests // 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(); + Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); Assert.NotNull(image); - ExifProfile profile = image.MetaData.ExifProfile; + ExifProfile profile = image.Metadata.ExifProfile; Assert.NotNull(profile); foreach (ExifValue value in profile.Values) @@ -333,9 +333,9 @@ namespace SixLabors.ImageSharp.Tests public void TestArrayValueWithUnspecifiedSize() { // This images contains array in the exif profile that has zero components. - Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateImage(); + Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateRgba32Image(); - ExifProfile profile = image.MetaData.ExifProfile; + ExifProfile profile = image.Metadata.ExifProfile; Assert.NotNull(profile); // Force parsing of the profile. @@ -353,13 +353,13 @@ namespace SixLabors.ImageSharp.Tests // arrange var image = new Image(1, 1); ExifProfile expected = CreateExifProfile(); - image.MetaData.ExifProfile = expected; + image.Metadata.ExifProfile = expected; // act Image reloadedImage = WriteAndRead(image, imageFormat); // assert - ExifProfile actual = reloadedImage.MetaData.ExifProfile; + ExifProfile actual = reloadedImage.Metadata.ExifProfile; Assert.NotNull(actual); foreach (KeyValuePair expectedProfileValue in TestProfileValues) { @@ -408,9 +408,9 @@ namespace SixLabors.ImageSharp.Tests internal static ExifProfile GetExifProfile() { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - ExifProfile profile = image.MetaData.ExifProfile; + ExifProfile profile = image.Metadata.ExifProfile; Assert.NotNull(profile); return profile; diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs index c9542a98a..19ff7d269 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using Xunit; namespace SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs index 2b8d4d716..144a6e4a3 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using Xunit; namespace SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs index 8bc192af5..8d786811c 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Linq; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Tests private static ExifValue GetExifValue() { ExifProfile profile; - using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image()) { - profile = image.MetaData.ExifProfile; + profile = image.Metadata.ExifProfile; } Assert.NotNull(profile); diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs index beca4db49..b6fa98b61 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs index 43a1ed7f4..04284cb49 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs index 328cc3fa6..7987e9410 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs index 5599e80d1..f9e5428cd 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs index 86f308ea1..6296390a0 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs @@ -3,7 +3,7 @@ using System; using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs index f8924c43c..e73ee7c9e 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs index aba587846..dc2c5b6ac 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs index a3e5a20f6..fc3502e3e 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs index 6a47c988c..585bda648 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs index 9286ac815..621673ce4 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs index 71dd18621..088222222 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs @@ -3,7 +3,7 @@ using System.Numerics; using SixLabors.Memory; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Theory] [MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] - internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, DenseMatrix data) + internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) { IccDataWriter writer = CreateWriter(); diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs index 43165c617..829b556af 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs index 1d482e2c1..ed8a10551 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs @@ -3,7 +3,7 @@ using System; using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs index 845a149b5..098950be8 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs index f3ef5effb..269a8d561 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs index a3f796275..b7b446699 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs index 17b5dacc4..a1944669d 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs index c91076afc..b4ed52a3d 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs index 99ae73f49..43c27335a 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs index 46b8b31b4..5c80ac36b 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; namespace SixLabors.ImageSharp.Tests.Icc diff --git a/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs index 8f68c9d03..b6e87626b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs @@ -94,5 +94,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats input.ToRgba32(ref actual); Assert.Equal(expected, actual); } + + [Fact] + public void Alpha8_FromBgra5551() + { + // arrange + var alpha = default(Alpha8); + byte expected = byte.MaxValue; + + // act + alpha.FromBgra5551(new Bgra5551(0.0f, 0.0f, 0.0f, 1.0f)); + + // assert + Assert.Equal(expected, alpha.PackedValue); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs index b9f741490..1ccf485fe 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. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -9,6 +10,67 @@ 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() { @@ -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 2295fbe56..026670485 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs @@ -9,6 +9,24 @@ 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 } }; @@ -95,5 +113,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 967e358e1..ccefa85c1 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs @@ -9,6 +9,36 @@ 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() { @@ -63,6 +93,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.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromGrey16() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + [Fact] public void Bgr565_Clamping() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index a5c53ed8b..4c5de7ee9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -9,6 +10,30 @@ 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() { @@ -102,5 +127,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 8b56ec19f..d4c998625 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs @@ -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.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromGrey8() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromBgr24() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgb24() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + [Fact] public void Bgra4444_Clamping() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs index 76edee8a7..7751d7ab9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs @@ -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); @@ -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.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromGrey8() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromBgr24() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromRgb24() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + [Fact] public void Bgra5551_Clamping() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs index 8391ef25a..2dff656ac 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs @@ -9,6 +9,36 @@ 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() { @@ -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.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromGrey16() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgb24() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromBgra5551() + { + // arrange + var byte4 = default(Byte4); + uint expected = 0xFFFFFFFF; + + // act + byte4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgba32() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue1 = uint.MaxValue; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgb48() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgba64() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + [Fact] public void Byte4_Clamping() { diff --git a/tests/ImageSharp.Tests/PixelFormats/ColorBuilderTests.cs b/tests/ImageSharp.Tests/PixelFormats/ColorBuilderTests.cs deleted file mode 100644 index e56cac279..000000000 --- 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 302e56ec7..000000000 --- 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 index cb19c031d..8a0bd62c1 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs @@ -9,6 +9,24 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { public class Gray16Tests { + [Fact] + public void AreEqual() + { + var color1 = new Gray16(3000); + var color2 = new Gray16(3000); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new Gray16(12345); + var color2 = new Gray16(54321); + + Assert.NotEqual(color1, color2); + } + [Theory] [InlineData(0)] [InlineData(65535)] @@ -126,5 +144,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual.B); Assert.Equal(byte.MaxValue, actual.A); } + + [Fact] + public void Gray16_FromBgra5551() + { + // arrange + var gray = default(Gray16); + ushort expected = ushort.MaxValue; + + // act + gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, gray.PackedValue); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs index 1e17985e6..74fd903ca 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs @@ -1,10 +1,8 @@ // 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; @@ -45,6 +43,24 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void Gray8_PackedValue_EqualsInput(byte input) => Assert.Equal(input, new Gray8(input).PackedValue); + [Fact] + public void AreEqual() + { + var color1 = new Gray8(100); + var color2 = new Gray8(100); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new Gray8(100); + var color2 = new Gray8(200); + + Assert.NotEqual(color1, color2); + } + [Fact] public void Gray8_FromScaledVector4() { @@ -148,6 +164,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(byte.MaxValue, actual.A); } + [Fact] + public void Gray8_FromBgra5551() + { + // arrange + var grey = default(Gray8); + byte expected = byte.MaxValue; + + // act + grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, grey.PackedValue); + } + public class Rgba32Compatibility { // ReSharper disable once MemberHidesStaticFromOuterClass diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index ccdd23e8f..57da5438c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -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 c61dd97d2..ed1a0b720 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -65,5 +65,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // 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/NormalizedByte2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs index 506ebe0fe..1533f9cf9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs @@ -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 19a49e5d8..7687a7777 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -9,6 +9,36 @@ 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() { @@ -60,5 +90,145 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // assert Assert.Equal(expected, actual); } + + [Fact] + public void NormalizedByte4_FromArgb32() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromBgr24() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromGrey8() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromGrey16() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgb24() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgba32() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromBgra5551() + { + // arrange + var normalizedByte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + normalizedByte4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, normalizedByte4.ToVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgb48() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgba64() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_ToRgba32() + { + // arrange + var byte4 = new NormalizedByte4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + byte4.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index 216ed4ad7..ff9350b70 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -69,5 +69,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 d06d46d06..b872e58b6 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -9,6 +9,36 @@ 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() { @@ -61,5 +91,145 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // assert Assert.Equal(expected, actual); } + + [Fact] + public void NormalizedShort4_FromArgb32() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromBgr24() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromGrey8() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromGrey16() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgb24() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgba32() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgb48() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgba64() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_ToRgba32() + { + // arrange + var byte4 = new NormalizedShort4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + byte4.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedShort4_FromBgra5551() + { + // arrange + var normalizedShort4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + normalizedShort4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, normalizedShort4.ToVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs index 9c3ea90d5..0c7a76081 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders public void PorterDuffOutputIsCorrect(TestImageProvider provider, PixelAlphaCompositionMode mode) { var srcFile = TestFile.Create(TestImages.Png.PDSrc); - using (Image src = srcFile.CreateImage()) + using (Image src = srcFile.CreateRgba32Image()) using (Image dest = provider.GetImage()) { GraphicsOptions options = new GraphicsOptions diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs new file mode 100644 index 000000000..d03cfeee2 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -0,0 +1,115 @@ +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +using 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 : struct, IPixel where TDestinationPixel : struct, 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; + } + + // Gray8 and Gray16 are special implementations of IPixel in that they do not conform to the + // standard RGBA colorspace format and must be converted from RGBA using the special ITU BT709 algorithm. + // One of the requirements of FromScaledVector4/ToScaledVector4 is that it unaware of this and + // packs/unpacks the pixel without and conversion so we employ custom methods do do this. + if (typeof(TDestinationPixel) == typeof(Gray16)) + { + ref Gray16 gray16Ref = ref MemoryMarshal.GetReference( + MemoryMarshal.Cast(destinationPixels)); + for (int i = 0; i < count; i++) + { + ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref gray16Ref, i); + dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); + } + + return; + } + + if (typeof(TDestinationPixel) == typeof(Gray8)) + { + ref Gray8 gray8Ref = ref MemoryMarshal.GetReference( + MemoryMarshal.Cast(destinationPixels)); + for (int i = 0; i < count; i++) + { + ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref gray8Ref, i); + dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); + } + + return; + } + + // Normal conversion + ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(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 c539e9dcf..f1f56fcd4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs @@ -5,7 +5,7 @@ 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 +122,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 000000000..e98e14fc6 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs @@ -0,0 +1,65 @@ +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +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); + } + } +} \ 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 323d3914c..afcec7938 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs @@ -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.Bgra5551OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs new file mode 100644 index 000000000..aa9b7bb1b --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors and contributors. +// 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 index c3de33547..f44401ae9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -25,90 +26,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [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 index acd6ef23a..30c429000 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs @@ -24,91 +24,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Fact] public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromGray8Bytes(int count) - { - byte[] source = CreateByteTestData(count); - var expected = new Gray8[count]; - - for (int i = 0; i < count; i++) - { - expected[i].FromGray8(new Gray8(source[i])); - } - - TestOperation( - source, - expected, - (s, d) => Operations.FromGray8Bytes(this.Configuration, s, d.GetSpan(), count) - ); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToGray8Bytes(int count) - { - Gray8[] source = CreatePixelTestData(count); - byte[] expected = new byte[count]; - var gray = default(Gray8); - - for (int i = 0; i < count; i++) - { - gray.FromScaledVector4(source[i].ToScaledVector4()); - expected[i] = gray.PackedValue; - } - - TestOperation( - source, - expected, - (s, d) => Operations.ToGray8Bytes(this.Configuration, s, d.GetSpan(), count) - ); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromGray16Bytes(int count) - { - byte[] source = CreateByteTestData(count * 2); - Span sourceSpan = source.AsSpan(); - var expected = new Gray8[count]; - - for (int i = 0; i < count; i++) - { - int i2 = i * 2; - expected[i].FromGray16(MemoryMarshal.Cast(sourceSpan.Slice(i2, 2))[0]); - } - - TestOperation( - source, - expected, - (s, d) => Operations.FromGray16Bytes(this.Configuration, s, d.GetSpan(), count) - ); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToGray16Bytes(int count) - { - Gray8[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 2]; - Gray16 gray = default; - - for (int i = 0; i < count; i++) - { - int i2 = i * 2; - gray.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes bytes = Unsafe.As(ref gray); - expected[i2] = bytes[0]; - expected[i2 + 1] = bytes[1]; - } - - TestOperation( - source, - expected, - (s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count) - ); - } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs new file mode 100644 index 000000000..37793911a --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Rgb24OperationsTests : PixelOperationsTests + { + public Rgb24OperationsTests(ITestOutputHelper output) + : base(output) + { + this.HasAlpha = false; + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index d9845e474..71adc1749 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -3,12 +3,14 @@ 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.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; @@ -33,6 +35,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations null; #endif + protected bool HasAlpha { get; set; } = true; + protected PixelOperationsTests(ITestOutputHelper output) : base(output) { @@ -70,25 +74,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,7 +114,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()) ); } @@ -116,7 +128,140 @@ 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) + { + Vector4Utils.Premultiply(ref v); + } + } + + void expectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.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) + { + Vector4Utils.Premultiply(ref v); + } + } + + void expectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.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) + { + Vector4Utils.Premultiply(ref v); + } + } + + void expectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.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) ); } @@ -134,6 +279,33 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations ); } + + public static readonly TheoryData Generic_To_Data = new TheoryData + { + default(Rgba32), + default(Bgra32), + default(Rgb24), + default(Gray8), + default(Gray16), + default(Rgb48), + default(Rgba64) + }; + + [Theory] + [MemberData(nameof(Generic_To_Data))] + public void Generic_To(TDestPixel dummy) + where TDestPixel : struct, IPixel + { + const int Count = 2134; + TPixel[] source = CreatePixelTestData(Count); + TDestPixel[] expected = new TDestPixel[Count]; + + PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); + + TestOperation(source, expected, (s, d) => Operations.To(this.Configuration, (ReadOnlySpan)s, d.GetSpan())); + } + + [Theory] [MemberData(nameof(ArraySizesData))] public void ToScaledVector4(int count) @@ -144,7 +316,119 @@ 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) + { + Vector4Utils.UnPremultiply(ref v); + } + + void expectedAction(ref Vector4 v) + { + Vector4Utils.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) + { + Vector4Utils.UnPremultiply(ref v); + } + + void expectedAction(ref Vector4 v) + { + Vector4Utils.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) + { + Vector4Utils.UnPremultiply(ref v); + SRgbCompanding.Compress(ref v); + } + + void expectedAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + Vector4Utils.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) ); } @@ -477,25 +761,123 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations ); } - internal static Vector4[] CreateExpectedVector4Data(TPixel[] source) + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromGray8(int count) + { + byte[] sourceBytes = CreateByteTestData(count); + Gray8[] source = sourceBytes.Select(b => new Gray8(b)).ToArray(); + var expected = new TPixel[count]; + + + for (int i = 0; i < count; i++) + { + expected[i].FromGray8(source[i]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromGray8(this.Configuration, s, d.GetSpan()) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToGray8(int count) + { + TPixel[] source = CreatePixelTestData(count); + Gray8[] expected = new Gray8[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromScaledVector4(source[i].ToScaledVector4()); + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToGray8(this.Configuration, s, d.GetSpan()) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromGray16(int count) + { + Gray16[] source = CreateVector4TestData(count).Select(v => + { + Gray16 g = default; + g.FromVector4(v); + return g; + }).ToArray(); + + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int i2 = i * 2; + expected[i].FromGray16(source[i]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromGray16(this.Configuration, s, d.GetSpan()) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToGray16(int count) + { + TPixel[] source = CreatePixelTestData(count); + Gray16[] expected = new Gray16[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromScaledVector4(source[i].ToScaledVector4()); + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToGray16(this.Configuration, s, d.GetSpan()) + ); + } + + 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 +895,22 @@ 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 +919,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 +937,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); } @@ -627,6 +1018,18 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations // ReSharper restore PossibleNullReferenceException } } + else if (typeof(TDest) == typeof(Gray16)) + { + // Minor difference is tolerated for 16 bit pixel values + Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); + Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); + + for (int i = 0; i < count; i++) + { + int difference = expected[i].PackedValue - actual[i].PackedValue; + Assert.True(Math.Abs(difference) < 2); + } + } else { Span expected = this.ExpectedDestBuffer.AsSpan(); @@ -639,4 +1042,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs index 46e5fbc3c..bccaaf816 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs @@ -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 92e8d302d..df459422c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs @@ -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 d30e49860..3bddc21ab 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs @@ -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 a897dd4cd..3a28c82b7 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs @@ -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() { @@ -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.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromGrey16() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgb24() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgb48() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgba64() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + [Fact] public void Rgba1010102_Clamping() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index ad1d13740..275afa35d 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -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 3e5d7a56e..55a2dda7a 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -33,37 +33,50 @@ 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 = short4.ToScaledVector4(); - pixel.FromScaledVector4(scaled); - ulong actual = pixel.PackedValue; + Vector4 scaled = source.ToScaledVector4(); + + Rgba64 actual = default; + actual.FromScaledVector4(scaled); + // assert - Assert.Equal(expected, actual); + Assert.Equal(source, actual); } [Fact] @@ -91,5 +104,190 @@ 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() + { + Rgba64 c1 = new Rgba64(100, 2000, 3000, 40000); + Rgba64 c2 = new Rgba64(100, 2000, 3000, 40000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + Rgba64 c1 = new Rgba64(100, 2000, 3000, 40000); + Rgba64 c2 = new Rgba64(101, 2000, 3000, 40000); + Rgba64 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); + Rgba64 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); + Rgba64 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); + Rgba64 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); + Rgba64 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); + Rgba64 actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Vector4() + { + Vector4 source = new Vector4(0f, 0.2f, 0.5f, 1f); + Rgba64 expected = default; + expected.FromScaledVector4(source); + + Rgba64 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 e880e3851..570d72cf4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs @@ -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.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, rgba.ToScaledVector4()); + } + + [Fact] + public void RgbaVector_FromGrey8() + { + // arrange + var rgba = default(RgbaVector); + Vector4 expected = Vector4.One; + + // act + rgba.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expected, rgba.ToScaledVector4()); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index c9a3b33c9..4ae172ed4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -141,5 +141,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 247342a05..1cb7d8998 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -181,5 +181,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/Primitives/ColorMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs new file mode 100644 index 000000000..2fbe260ec --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs @@ -0,0 +1,270 @@ +using System; +using System.Globalization; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Primitives +{ + public class ColorMatrixTests + { + private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + + [Fact] + public void ColorMatrixIdentityIsCorrect() + { + ColorMatrix val = default; + val.M11 = val.M22 = val.M33 = val.M44 = 1F; + + Assert.Equal(val, ColorMatrix.Identity, this.ApproximateFloatComparer); + } + + [Fact] + public void ColorMatrixCanDetectIdentity() + { + ColorMatrix m = ColorMatrix.Identity; + Assert.True(m.IsIdentity); + + m.M12 = 1F; + Assert.False(m.IsIdentity); + } + + [Fact] + public void ColorMatrixEquality() + { + ColorMatrix m = KnownFilterMatrices.CreateHueFilter(45F); + ColorMatrix m2 = KnownFilterMatrices.CreateHueFilter(45F); + object obj = m2; + + Assert.True(m.Equals(obj)); + Assert.True(m.Equals(m2)); + Assert.True(m == m2); + Assert.False(m != m2); + } + + [Fact] + public void ColorMatrixMultiply() + { + ColorMatrix value1 = this.CreateAllTwos(); + ColorMatrix value2 = this.CreateAllThrees(); + + ColorMatrix m; + + // First row + m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); + m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); + m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); + m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); + + // Second row + m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); + m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); + m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); + m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); + + // Third row + m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); + m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); + m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); + m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); + + // Fourth row + m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); + m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); + m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); + m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); + + // Fifth row + m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; + m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; + m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53; + m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54; + + Assert.Equal(m, value1 * value2, this.ApproximateFloatComparer); + } + + [Fact] + public void ColorMatrixMultiplyScalar() + { + ColorMatrix m = this.CreateAllTwos(); + Assert.Equal(this.CreateAllFours(), m * 2, this.ApproximateFloatComparer); + } + + [Fact] + public void ColorMatrixSubtract() + { + ColorMatrix m = this.CreateAllOnes() + this.CreateAllTwos(); + Assert.Equal(this.CreateAllThrees(), m); + } + + [Fact] + public void ColorMatrixNegate() + { + ColorMatrix m = this.CreateAllOnes() * -1F; + Assert.Equal(m, -this.CreateAllOnes()); + } + + [Fact] + public void ColorMatrixAdd() + { + ColorMatrix m = this.CreateAllOnes() + this.CreateAllTwos(); + Assert.Equal(this.CreateAllThrees(), m); + } + + [Fact] + public void ColorMatrixHashCode() + { +#if NETCOREAPP2_1 + ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); + HashCode hash = default; + hash.Add(m.M11); + hash.Add(m.M12); + hash.Add(m.M13); + hash.Add(m.M14); + hash.Add(m.M21); + hash.Add(m.M22); + hash.Add(m.M23); + hash.Add(m.M24); + hash.Add(m.M31); + hash.Add(m.M32); + hash.Add(m.M33); + hash.Add(m.M34); + hash.Add(m.M41); + hash.Add(m.M42); + hash.Add(m.M43); + hash.Add(m.M44); + hash.Add(m.M51); + hash.Add(m.M52); + hash.Add(m.M53); + hash.Add(m.M54); + + Assert.Equal(hash.ToHashCode(), m.GetHashCode()); +#endif + } + + [Fact] + public void ColorMatrixToString() + { + ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); + + CultureInfo ci = CultureInfo.CurrentCulture; + + string expected = string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}", + m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci), + m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci), + m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci), + m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci), + m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci)); + + Assert.Equal(expected, m.ToString()); + } + + private ColorMatrix CreateAllOnes() + { + return new ColorMatrix + { + M11 = 1F, + M12 = 1F, + M13 = 1F, + M14 = 1F, + M21 = 1F, + M22 = 1F, + M23 = 1F, + M24 = 1F, + M31 = 1F, + M32 = 1F, + M33 = 1F, + M34 = 1F, + M41 = 1F, + M42 = 1F, + M43 = 1F, + M44 = 1F, + M51 = 1F, + M52 = 1F, + M53 = 1F, + M54 = 1F + }; + } + + private ColorMatrix CreateAllTwos() + { + return new ColorMatrix + { + M11 = 2F, + M12 = 2F, + M13 = 2F, + M14 = 2F, + M21 = 2F, + M22 = 2F, + M23 = 2F, + M24 = 2F, + M31 = 2F, + M32 = 2F, + M33 = 2F, + M34 = 2F, + M41 = 2F, + M42 = 2F, + M43 = 2F, + M44 = 2F, + M51 = 2F, + M52 = 2F, + M53 = 2F, + M54 = 2F + }; + } + + private ColorMatrix CreateAllThrees() + { + return new ColorMatrix + { + M11 = 3F, + M12 = 3F, + M13 = 3F, + M14 = 3F, + M21 = 3F, + M22 = 3F, + M23 = 3F, + M24 = 3F, + M31 = 3F, + M32 = 3F, + M33 = 3F, + M34 = 3F, + M41 = 3F, + M42 = 3F, + M43 = 3F, + M44 = 3F, + M51 = 3F, + M52 = 3F, + M53 = 3F, + M54 = 3F + }; + } + + private ColorMatrix CreateAllFours() + { + return new ColorMatrix + { + M11 = 4F, + M12 = 4F, + M13 = 4F, + M14 = 4F, + M21 = 4F, + M22 = 4F, + M23 = 4F, + M24 = 4F, + M31 = 4F, + M32 = 4F, + M33 = 4F, + M34 = 4F, + M41 = 4F, + M42 = 4F, + M43 = 4F, + M44 = 4F, + M51 = 4F, + M52 = 4F, + M53 = 4F, + M54 = 4F + }; + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs index 5f6e825f6..dcbf0ca03 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs @@ -25,38 +25,38 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void BinaryDither_CorrectProcessor() { this.operations.BinaryDither(this.orderedDither); - BinaryOrderedDitherProcessor p = this.Verify>(); + BinaryOrderedDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); - 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 BinaryDither_rect_CorrectProcessor() { this.operations.BinaryDither(this.orderedDither, this.rect); - BinaryOrderedDitherProcessor p = this.Verify>(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); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); } [Fact] public void BinaryDither_index_CorrectProcessor() { - this.operations.BinaryDither(this.orderedDither, NamedColors.Yellow, NamedColors.HotPink); - BinaryOrderedDitherProcessor p = this.Verify>(); + this.operations.BinaryDither(this.orderedDither, Color.Yellow, Color.HotPink); + BinaryOrderedDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(NamedColors.Yellow, p.UpperColor); - Assert.Equal(NamedColors.HotPink, p.LowerColor); + Assert.Equal(Color.Yellow, p.UpperColor); + Assert.Equal(Color.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); + this.operations.BinaryDither(this.orderedDither, Color.Yellow, Color.HotPink, this.rect); + BinaryOrderedDitherProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(NamedColors.HotPink, p.LowerColor); + Assert.Equal(Color.HotPink, p.LowerColor); } @@ -64,44 +64,44 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void BinaryDither_ErrorDiffuser_CorrectProcessor() { this.operations.BinaryDiffuse(this.errorDiffuser, .4F); - BinaryErrorDiffusionProcessor p = this.Verify>(); + 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); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); } [Fact] public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor() { this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect); - BinaryErrorDiffusionProcessor p = this.Verify>(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); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); } [Fact] public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors() { - this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors.HotPink, NamedColors.Yellow); - BinaryErrorDiffusionProcessor p = this.Verify>(); + this.operations.BinaryDiffuse(this.errorDiffuser, .5F, Color.HotPink, Color.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); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.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); + this.operations.BinaryDiffuse(this.errorDiffuser, .5F, Color.HotPink, Color.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); + 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/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs index 569c4ba21..f0fcba181 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs @@ -15,40 +15,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/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs index e425b6315..8c659848f 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs @@ -14,7 +14,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 +23,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 +32,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 60fa19b49..07b9b1b8c 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -21,8 +21,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution this.operations.DetectEdges(); // TODO: Enable once we have updated the images - // SobelProcessor processor = this.Verify>(); - // Assert.True(processor.Grayscale); + SobelProcessor processor = this.Verify(); + Assert.True(processor.Grayscale); } [Fact] @@ -31,45 +31,45 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution this.operations.DetectEdges(this.rect); // TODO: Enable once we have updated the images - // SobelProcessor processor = this.Verify>(this.rect); - // Assert.True(processor.Grayscale); + SobelProcessor processor = this.Verify(this.rect); + Assert.True(processor.Grayscale); } public static IEnumerable EdgeDetectionTheoryData => new[] { - new object[]{ new TestType>(), EdgeDetectionOperators.Kayyali }, - new object[]{ new TestType>(), EdgeDetectionOperators.Kirsch }, - new object[]{ new TestType>(), EdgeDetectionOperators.Laplacian3x3 }, - new object[]{ new TestType>(), EdgeDetectionOperators.Laplacian5x5 }, - new object[]{ new TestType>(), EdgeDetectionOperators.LaplacianOfGaussian }, - new object[]{ new TestType>(), EdgeDetectionOperators.Prewitt }, - new object[]{ new TestType>(), EdgeDetectionOperators.RobertsCross }, - new object[]{ new TestType>(), EdgeDetectionOperators.Robinson }, - new object[]{ new TestType>(), EdgeDetectionOperators.Scharr }, - new object[]{ new TestType>(), EdgeDetectionOperators.Sobel }, + 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 + where TProcessor : EdgeDetectorProcessor { this.operations.DetectEdges(filter); // TODO: Enable once we have updated the images - // var processor = this.Verify(); - // Assert.True(processor.Grayscale); + var processor = this.Verify(); + Assert.True(processor.Grayscale); } [Theory] [MemberData(nameof(EdgeDetectionTheoryData))] public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet(TestType type, EdgeDetectionOperators filter) - where TProcessor : IEdgeDetectorProcessor + where TProcessor : EdgeDetectorProcessor { bool grey = (int)filter % 2 == 0; this.operations.DetectEdges(filter, grey); // TODO: Enable once we have updated the images - // var processor = this.Verify() - // Assert.Equal(grey, processor.Grayscale); + var processor = this.Verify(); + Assert.Equal(grey, processor.Grayscale); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs index c87a834eb..0f64ebbeb 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs @@ -14,7 +14,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 +23,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 +32,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 675498745..7c2205f4e 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs @@ -14,7 +14,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 +23,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 +32,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/DelegateTest.cs b/tests/ImageSharp.Tests/Processing/DelegateTest.cs deleted file mode 100644 index 73d3c8023..000000000 --- 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 f393d5923..d91688cd2 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Dithering; @@ -12,13 +14,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization { public class DitherTest : BaseImageOperationsExtensionTest { + private class Assert : Xunit.Assert + { + public static void Equal(ReadOnlySpan a, ReadOnlySpan b) + { + Xunit.Assert.True(a.SequenceEqual(b)); + } + } + private readonly IOrderedDither orderedDither; private readonly IErrorDiffuser errorDiffuser; - private readonly Rgba32[] TestPalette = + private readonly Color[] TestPalette = { - Rgba32.Red, - Rgba32.Green, - Rgba32.Blue + Color.Red, + Color.Green, + Color.Blue }; public DitherTest() @@ -31,24 +41,24 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_CorrectProcessor() { this.operations.Dither(this.orderedDither); - OrderedDitherPaletteProcessor p = this.Verify>(); + OrderedDitherPaletteProcessor 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); + OrderedDitherPaletteProcessor 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>(); + OrderedDitherPaletteProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(this.TestPalette, p.Palette); } @@ -57,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_index_rect_CorrectProcessor() { this.operations.Dither(this.orderedDither, this.TestPalette, this.rect); - OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + OrderedDitherPaletteProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(this.TestPalette, p.Palette); } @@ -67,27 +77,27 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_ErrorDiffuser_CorrectProcessor() { this.operations.Diffuse(this.errorDiffuser, .4F); - ErrorDiffusionPaletteProcessor p = this.Verify>(); + ErrorDiffusionPaletteProcessor p = this.Verify(); Assert.Equal(this.errorDiffuser, p.Diffuser); Assert.Equal(.4F, p.Threshold); - Assert.Equal(NamedColors.WebSafePalette, p.Palette); + 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); + ErrorDiffusionPaletteProcessor p = this.Verify(this.rect); Assert.Equal(this.errorDiffuser, p.Diffuser); Assert.Equal(.3F, p.Threshold); - Assert.Equal(NamedColors.WebSafePalette, p.Palette); + Assert.Equal(Color.WebSafePalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_CorrectProcessorWithColors() { this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette); - ErrorDiffusionPaletteProcessor p = this.Verify>(); + ErrorDiffusionPaletteProcessor p = this.Verify(); Assert.Equal(this.errorDiffuser, p.Diffuser); Assert.Equal(.5F, p.Threshold); Assert.Equal(this.TestPalette, p.Palette); @@ -97,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() { this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect); - ErrorDiffusionPaletteProcessor p = this.Verify>(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); diff --git a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs index 7775de2d2..14758958f 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs @@ -14,41 +14,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); + var processor = this.Verify(); Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions); - Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + 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); + var processor = this.Verify(this.rect); Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions); - Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + 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); + var 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); + var 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 9cd24fc6d..6e1ee40f5 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs @@ -14,7 +14,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,7 +24,7 @@ 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); @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void OilPaint_Levels_Brsuh_OilPaintingProcessorDefaultsSet() { this.operations.OilPaint(34, 65); - var processor = this.Verify>(); + var processor = this.Verify(); Assert.Equal(34, processor.Levels); Assert.Equal(65, processor.BrushSize); @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void OilPaint_Levels_Brsuh_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); diff --git a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs index a93eaf0bc..d8ce29170 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs @@ -14,7 +14,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 +23,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 +32,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/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs index d651f2f04..f913068e3 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs @@ -14,14 +14,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void BlackWhite_CorrectProcessor() { this.operations.BlackWhite(); - BlackWhiteProcessor p = this.Verify>(); + BlackWhiteProcessor p = this.Verify(); } [Fact] public void BlackWhite_rect_CorrectProcessor() { this.operations.BlackWhite(this.rect); - BlackWhiteProcessor p = this.Verify>(this.rect); + BlackWhiteProcessor p = this.Verify(this.rect); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs index e210450a8..c26524880 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs @@ -14,7 +14,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 +23,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 aeafe5fe1..7dd894403 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs @@ -16,20 +16,20 @@ 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 } + 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(); @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters [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); diff --git a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs index 21a552e6a..ff2b1c702 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs @@ -16,7 +16,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,7 +25,7 @@ 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); } diff --git a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs index 414a0d74e..d598f0ac8 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs @@ -17,14 +17,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Filter_CorrectProcessor() { this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F)); - FilterProcessor p = this.Verify>(); + FilterProcessor p = this.Verify(); } [Fact] public void Filter_rect_CorrectProcessor() { this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F), this.rect); - FilterProcessor p = this.Verify>(this.rect); + FilterProcessor p = this.Verify(this.rect); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs index d63d97820..94e1b00a7 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs @@ -16,13 +16,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public class GrayscaleTest : BaseImageOperationsExtensionTest { public static IEnumerable ModeTheoryData = new[] { - new object[]{ new TestType>(), GrayscaleMode.Bt709 } + 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(); @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters [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 f56578dd6..0eb25043c 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs @@ -15,7 +15,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 +24,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 c93afc942..ff59eab05 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs @@ -14,14 +14,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void Invert_InvertProcessorDefaultsSet() { this.operations.Invert(); - var processor = this.Verify>(); + var processor = this.Verify(); } [Fact] public void Pixelate_rect_PixelateProcessorDefaultsSet() { this.operations.Invert(this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs index a98252140..a06a9d65c 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs @@ -14,14 +14,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Kodachrome_amount_KodachromeProcessorDefaultsSet() { this.operations.Kodachrome(); - var processor = this.Verify>(); + var processor = this.Verify(); } [Fact] public void Kodachrome_amount_rect_KodachromeProcessorDefaultsSet() { this.operations.Kodachrome(this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs index c104f8c25..446cfa1c3 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs @@ -18,14 +18,14 @@ namespace SixLabors.ImageSharp.Tests public void Lomograph_amount_LomographProcessorDefaultsSet() { this.operations.Lomograph(); - var processor = this.Verify>(); + var processor = this.Verify(); } [Fact] public void Lomograph_amount_rect_LomographProcessorDefaultsSet() { this.operations.Lomograph(this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs index adbb8cf29..95ea2c23e 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs @@ -14,7 +14,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 +23,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 f28827b71..f63b892bc 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs @@ -15,14 +15,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Polaroid_amount_PolaroidProcessorDefaultsSet() { this.operations.Polaroid(); - var processor = this.Verify>(); + var processor = this.Verify(); } [Fact] public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet() { this.operations.Polaroid(this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs index 4b8e80881..b1583b4ac 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs @@ -14,7 +14,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 +23,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 9351c8443..441c9739d 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs @@ -14,14 +14,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Sepia_amount_SepiaProcessorDefaultsSet() { this.operations.Sepia(); - var processor = this.Verify>(); + var processor = this.Verify(); } [Fact] public void Sepia_amount_rect_SepiaProcessorDefaultsSet() { this.operations.Sepia(this.rect); - var processor = this.Verify>(this.rect); + var processor = this.Verify(this.rect); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index 1595ed32c..fc9a583dd 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -3,12 +3,16 @@ 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 { public class HistogramEqualizationTests { + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); + [Theory] [InlineData(256)] [InlineData(65536)] @@ -27,18 +31,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[] - { + byte[] expected = new byte[] + { 0, 12, 53, 32, 146, 53, 174, 53, 57, 32, 12, 227, 219, 202, 32, 154, 65, 85, 93, 239, 251, 227, 65, 158, @@ -47,22 +52,66 @@ 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 : struct, 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 : struct, 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); + } + } } } diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs index 899082e36..0301f5c03 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -18,10 +18,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Glow_GlowProcessorWithDefaultValues() { this.operations.Glow(); - var p = this.Verify>(); + var p = this.Verify(); Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.GlowColor); + Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } @@ -29,10 +29,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Glow_Color_GlowProcessorWithDefaultValues() { this.operations.Glow(Rgba32.Aquamarine); - var p = this.Verify>(); + var p = this.Verify(); Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Aquamarine, p.GlowColor); + Assert.Equal(Color.Aquamarine, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } @@ -40,10 +40,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Glow_Radux_GlowProcessorWithDefaultValues() { this.operations.Glow(3.5f); - var p = this.Verify>(); + var p = this.Verify(); Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.GlowColor); + Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); } @@ -52,10 +52,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays { var rect = new Rectangle(12, 123, 43, 65); this.operations.Glow(rect); - var p = this.Verify>(rect); + var p = this.Verify(rect); Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.GlowColor); + Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } } diff --git a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs index f47bffe26..2538aa137 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs @@ -16,10 +16,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Vignette_VignetteProcessorWithDefaultValues() { this.operations.Vignette(); - var p = this.Verify>(); + var p = this.Verify(); Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.VignetteColor); + Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); } @@ -27,11 +27,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); + var p = this.Verify(); Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Aquamarine, p.VignetteColor); + Assert.Equal(Color.Aquamarine, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); } @@ -40,10 +40,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Vignette_Radux_VignetteProcessorWithDefaultValues() { this.operations.Vignette(3.5f, 12123f); - var p = this.Verify>(); + var p = this.Verify(); Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.VignetteColor); + Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX); Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY); } @@ -53,10 +53,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays { var rect = new Rectangle(12, 123, 43, 65); this.operations.Vignette(rect); - var p = this.Verify>(rect); + var p = this.Verify(rect); Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); - Assert.Equal(Rgba32.Black, p.VignetteColor); + Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs index 44fdfc703..d3507ed4c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -12,7 +12,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - public class BinaryDitherTests : FileTestBase + public class BinaryDitherTests { public static readonly string[] CommonTestImages = { @@ -40,14 +40,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { "Stucki", KnownDiffusers.Stucki }, }; + public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), DefaultPixelType)] - [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, DefaultPixelType)] + [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, PixelTypes.Rgba32)] public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IOrderedDither ditherer) where TPixel : struct, IPixel { @@ -59,8 +60,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), DefaultPixelType)] - [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, DefaultPixelType)] + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, PixelTypes.Rgba32)] public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IErrorDiffuser diffuser) where TPixel : struct, IPixel { @@ -72,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } [Theory] - [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + [WithFile(TestImages.Png.Bike, TestPixelTypes)] public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { @@ -84,7 +85,7 @@ 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 { @@ -96,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDitherFilterInBox(TestImageProvider provider) where TPixel : struct, IPixel { @@ -113,7 +114,7 @@ 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 { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs index 988c9125b..4ae5d6051 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs @@ -11,17 +11,24 @@ 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 { @@ -33,7 +40,7 @@ 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 { 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 000000000..0a10d0755 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs @@ -0,0 +1,54 @@ +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +{ + [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 : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunValidatingProcessorTest( + x => this.Apply(x, value), + value, + ValidatorComparer); + } + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => this.Apply(x, value, rect), + value, + ValidatorComparer); + } + + protected abstract void Apply(IImageProcessingContext ctx, int value); + + protected abstract void Apply(IImageProcessingContext ctx, int value, Rectangle bounds); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index 0c40debad..a7cf9360c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs @@ -1,51 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - using SixLabors.Primitives; -using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - 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 de72f6d09..05524b20b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -10,11 +10,16 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - public class DetectEdgesTest : FileTestBase + [GroupOutput("Convolution")] + public class DetectEdgesTest { + // I think our comparison is not accurate enough (nor can be) for RgbaVector. + // The image pixels are identical according to BeyondCompare. private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); - public static readonly string[] CommonTestImages = { TestImages.Png.Bike }; + public static readonly string[] TestImages = { Tests.TestImages.Png.Bike }; + + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; public static readonly TheoryData DetectEdgesFilters = new TheoryData { @@ -31,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution }; [Theory] - [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider provider) where TPixel : struct, IPixel { @@ -42,12 +47,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); ctx.DetectEdges(bounds); }, + comparer: ValidatorComparer, useReferenceOutputFrom: nameof(this.DetectEdges_InBox)); } [Theory] - [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, DefaultPixelType)] - [WithFileCollection(nameof(CommonTestImages), nameof(DetectEdgesFilters), DefaultPixelType)] + [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] + [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, EdgeDetectionOperators detector) where TPixel : struct, IPixel { @@ -60,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } [Theory] - [WithFileCollection(nameof(CommonTestImages), CommonNonDefaultPixelTypes)] + [WithFileCollection(nameof(TestImages), CommonNonDefaultPixelTypes)] public void DetectEdges_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { @@ -73,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } [Theory] - [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] + [WithFile(Tests.TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void DetectEdges_IsAppliedToAllFrames(TestImageProvider provider) where TPixel : struct, IPixel { @@ -85,7 +91,7 @@ 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 { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 6bd3b34bb..68eb8c4f6 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -10,37 +10,12 @@ 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 }; + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianBlur(value); - [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); - - 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 8eb1f85eb..4b497309e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs @@ -9,42 +9,12 @@ 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 9774cb50c..cb901c2a8 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -2,129 +2,153 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.Primitives; + using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - public class DitherTests : FileTestBase + public class DitherTests { - public static readonly string[] CommonTestImages = - { - TestImages.Png.CalliphoraPartial, TestImages.Png.Bike - }; - - public static readonly TheoryData OrderedDitherers = new TheoryData - { - { "Bayer8x8", KnownDitherers.BayerDither8x8 }, - { "Bayer4x4", KnownDitherers.BayerDither4x4 }, - { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, - { "Bayer2x2", KnownDitherers.BayerDither2x2 } - }; - - public static readonly TheoryData ErrorDiffusers = new TheoryData - { - { "Atkinson", KnownDiffusers.Atkinson }, - { "Burks", KnownDiffusers.Burks }, - { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, - { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, - { "Sierra2", KnownDiffusers.Sierra2 }, - { "Sierra3", KnownDiffusers.Sierra3 }, - { "SierraLite", KnownDiffusers.SierraLite }, - { "StevensonArce", KnownDiffusers.StevensonArce }, - { "Stucki", KnownDiffusers.Stucki }, - }; - + public const PixelTypes CommonNonDefaultPixelTypes = + PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24 | PixelTypes.RgbaVector; + + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; + + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + KnownDiffusers.Atkinson, + KnownDiffusers.Burks, + KnownDiffusers.FloydSteinberg, + KnownDiffusers.JarvisJudiceNinke, + KnownDiffusers.Sierra2, + KnownDiffusers.Sierra3, + KnownDiffusers.SierraLite, + KnownDiffusers.StevensonArce, + KnownDiffusers.Stucki, + }; + + public static readonly TheoryData OrderedDitherers = new TheoryData + { + KnownDitherers.BayerDither8x8, + KnownDitherers.BayerDither4x4, + KnownDitherers.OrderedDither3x3, + KnownDitherers.BayerDither2x2 + }; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); + private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + /// + /// The output is visually correct old 32bit runtime, + /// but it is very different because of floating point inaccuracies. + /// + private static readonly bool SkipAllDitherTests = + !TestEnvironment.Is64BitProcess && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion); + [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), DefaultPixelType)] - [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, DefaultPixelType)] - public void DitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IOrderedDither ditherer) + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDiffusionFilterInBox(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) + if (SkipAllDitherTests) { - image.Mutate(x => x.Dither(ditherer)); - image.DebugSave(provider, name); + return; } + + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Diffuse(DefaultErrorDiffuser, .5F, 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) + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDitherFilterInBox(TestImageProvider provider) where TPixel : struct, 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) + public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) where TPixel : struct, 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.Diffuse(DefaultErrorDiffuser, 0.5f), comparer: comparer); } [Theory] - [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] - public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] + public void DiffusionFilter_WorksWithAllErrorDiffusers( + TestImageProvider provider, + IErrorDiffuser diffuser) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) + if (SkipAllDitherTests) { - image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, 0.5f)); - image.DebugSave(provider); + return; } + + provider.RunValidatingProcessorTest( + x => x.Diffuse(diffuser, 0.5f), + testOutputDetails: diffuser.GetType().Name, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] - public void ApplyDitherFilterInBox(TestImageProvider provider) + [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] + public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) where TPixel : struct, 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) + [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] + public void DitherFilter_WorksWithAllDitherers( + TestImageProvider provider, + IOrderedDither ditherer) where TPixel : struct, 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.Diffuse(DefaultErrorDiffuser, .5F, bounds)); - image.DebugSave(provider); - - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + return; } + + provider.RunValidatingProcessorTest( + x => x.Dither(ditherer), + testOutputDetails: ditherer.GetType().Name, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); } } } \ 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 792c7b080..dc6ff6f3e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs @@ -10,35 +10,30 @@ 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) + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider) where TPixel : struct, 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) + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void InBox(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); - - 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 d4429aaf3..ea2273cd5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -10,40 +10,40 @@ 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) + [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider, int levels, int brushSize) where TPixel : struct, 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), + $"{levels}-{brushSize}", + appendPixelTypeToFileName: false); } [Theory] - [WithFileCollection(nameof(DefaultFiles), nameof(OilPaintValues), DefaultPixelType)] - public void ApplyOilPaintFilterInBox(TestImageProvider provider, int levels, int brushSize) + [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(OilPaintValues), 100, 100, PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int levels, int brushSize) where TPixel : struct, IPixel { - 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}"); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs index cb9a0ba0c..726f4b707 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs @@ -4,82 +4,32 @@ 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) + [WithFile(TestImages.Png.Ducky, nameof(PixelateValues), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider, int value) where TPixel : struct, 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) + [WithFile(TestImages.Png.CalliphoraPartial, nameof(PixelateValues), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int value) where TPixel : struct, 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/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs index ed790cbac..54a8dd4b7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs @@ -8,10 +8,13 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; [GroupOutput("Filters")] public class BrightnessTest { + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); + public static readonly TheoryData BrightnessValues = new TheoryData { @@ -22,9 +25,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [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); - } + where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs index 3d48e16ec..8ac56655e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs @@ -2,17 +2,19 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; [GroupOutput("Filters")] public class ColorBlindnessTest { + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.03F); + public static readonly TheoryData ColorBlindnessFilters = new TheoryData { @@ -29,9 +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()); - } + where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs index 479a3c33a..68daa80ea 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -1,16 +1,13 @@ // 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.ImageSharp.Tests.TestUtilities.ImageComparison; - -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { + using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; [GroupOutput("Filters")] @@ -25,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters public void ApplyFilter(TestImageProvider provider) where TPixel : struct, IPixel { - Matrix4x4 m = CreateCombinedTestFilterMatrix(); + ColorMatrix m = CreateCombinedTestFilterMatrix(); provider.RunValidatingProcessorTest(x => x.Filter(m), comparer: ValidatorComparer); } @@ -35,18 +32,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters public void ApplyFilterInBox(TestImageProvider provider) where TPixel : struct, IPixel { - Matrix4x4 m = CreateCombinedTestFilterMatrix(); + ColorMatrix m = CreateCombinedTestFilterMatrix(); provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer); } - private static Matrix4x4 CreateCombinedTestFilterMatrix() + private static ColorMatrix CreateCombinedTestFilterMatrix() { - Matrix4x4 brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F); - Matrix4x4 hue = KnownFilterMatrices.CreateHueFilter(180F); - Matrix4x4 saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F); - Matrix4x4 m = brightness * hue * saturation; - return m; + ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F); + ColorMatrix hue = KnownFilterMatrices.CreateHueFilter(180F); + ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F); + return brightness * hue * saturation; } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs index 479ee346a..27aeb4b05 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs @@ -10,59 +10,14 @@ 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); - } - } + protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Glow(color); - [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); - } - } + protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => + ctx.Glow(radiusX); - [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); - - image.Mutate(x => x.Glow(bounds)); - image.DebugSave(provider); - - 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 000000000..eaad82f9a --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Reflection; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; + +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 : struct, 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 : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunValidatingProcessorTest( + x => + { + Size size = x.GetCurrentSize(); + this.Apply(x, size.Width / 4f, size.Height / 4f); + }, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunRectangleConstrainedValidatingProcessorTest((x, rect) => this.Apply(x, rect)); + } + + protected abstract void Apply(IImageProcessingContext ctx, Color color); + + protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY); + + protected abstract void Apply(IImageProcessingContext ctx, Rectangle rect); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs index 3a378a095..9448feefe 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs @@ -1,68 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - using SixLabors.Primitives; -using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.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/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index a4e6edd53..05ce70047 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -11,21 +11,21 @@ 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[] Rgb = new Color[] { Rgba32.Red, Rgba32.Green, Rgba32.Blue }; [Fact] public void PaletteQuantizerConstructor() { - var quantizer = new PaletteQuantizer(Rgb); + var quantizer = new PaletteQuantizer(Rgb); Assert.Equal(Rgb, quantizer.Palette); Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); - quantizer = new PaletteQuantizer(Rgb, false); + quantizer = new PaletteQuantizer(Rgb, false); Assert.Equal(Rgb, quantizer.Palette); Assert.Null(quantizer.Diffuser); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); + quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); Assert.Equal(Rgb, quantizer.Palette); Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); } @@ -33,35 +33,27 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [Fact] public void PaletteQuantizerCanCreateFrameQuantizer() { - var quantizer = new PaletteQuantizer(Rgb); - IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + var quantizer = new PaletteQuantizer(Rgb); + IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.True(frameQuantizer.Dither); Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); - quantizer = new PaletteQuantizer(Rgb, false); - frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + quantizer = new PaletteQuantizer(Rgb, false); + frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.False(frameQuantizer.Dither); Assert.Null(frameQuantizer.Diffuser); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); - frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); + frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.True(frameQuantizer.Dither); Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); } - [Fact] - public void PaletteQuantizerThrowsOnInvalidGenericMethodCall() - { - var quantizer = new PaletteQuantizer(Rgb); - - Assert.Throws(() => ((IQuantizer)quantizer).CreateFrameQuantizer(Configuration.Default)); - } - [Fact] public void KnownQuantizersWebSafeTests() { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index 9b37fb266..6dc9b3630 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -2,31 +2,20 @@ // 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 @@ -38,27 +27,38 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { ExifDataType.SignedLong, BitConverter.GetBytes((int) 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) + [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] + public void AutoOrient_WorksForAllExifOrientations(TestImageProvider provider, ushort orientation) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - image.MetaData.ExifProfile = new ExifProfile(); - image.MetaData.ExifProfile.SetValue(ExifTag.Orientation, orientation); - - image.Mutate(x => x.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) + [WithFile(FlipTestFile, nameof(InvalidOrientationValues), PixelTypes.Rgba32)] + public void AutoOrient_WorksWithCorruptExifData(TestImageProvider provider, ExifDataType dataType, byte[] orientation) where TPixel : struct, IPixel { var profile = new ExifProfile(); @@ -72,11 +72,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms bytes[18] = (byte)dataType; // Change the number of components bytes[20] = 1; + + byte[] orientationCodeData = new byte[8]; + Array.Copy(orientation, orientationCodeData, orientation.Length); + + ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); 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); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs index c01c3b1bd..50217e892 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -13,7 +13,7 @@ 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)] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs index 728527021..d20e6fa35 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs @@ -7,25 +7,24 @@ 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 - { - .25F, - .75F - }; + 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(DefaultFiles), nameof(EntropyCropValues), DefaultPixelType)] - public void ImageShouldEntropyCrop(TestImageProvider provider, float value) + [WithFileCollection(nameof(InputImages), nameof(EntropyCropValues), PixelTypes.Rgba32)] + public void EntropyCrop(TestImageProvider provider, float value) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.EntropyCrop(value)); - image.DebugSave(provider, value); - } + provider.RunValidatingProcessorTest(x => x.EntropyCrop(value), value, 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 6cce62d14..f56827870 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs @@ -7,10 +7,15 @@ 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 { @@ -24,7 +29,30 @@ 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 : struct, IPixel + { + Color color = Color.Red; + TPixel expected = color.ToPixel(); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50, color)); + image.DebugSave(provider); + + // Check pixels are filled + for (int y = 0; y < 25; y++) + { + for (int x = 0; x < 25; x++) + { + Assert.Equal(expected, image[x, y]); } } } 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 000000000..b7b4597c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +using 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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs new file mode 100644 index 000000000..b5ed64f7e --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; + +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); + + var actual = ResizeHelper.CalculateTargetLocationAndBounds( + sourceSize, + new ResizeOptions{ + Mode = ResizeMode.Min, + Size = target + }, + target.Width, + target.Height); + + Assert.Equal(sourceSize, actual.Item1); + Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), actual.Item2); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index 7d842c4e1..2c5914253 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -104,7 +104,7 @@ 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()); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index 5d3790f07..51680eee0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -36,6 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { nameof(KnownResamplers.Bicubic), 40, 50 }, { nameof(KnownResamplers.Bicubic), 500, 200 }, { nameof(KnownResamplers.Bicubic), 200, 500 }, + { nameof(KnownResamplers.Bicubic), 3032, 400 }, { nameof(KnownResamplers.Bicubic), 10, 25 }, @@ -93,7 +94,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms GenerateImageResizeData(); - [Theory(Skip = "Only for debugging and development")] + [Theory( + Skip = "Only for debugging and development" + )] [MemberData(nameof(KernelMapData))] public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize) { @@ -130,7 +133,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); var kernelMap = ResizeKernelMap.Calculate(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,8 +152,8 @@ 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; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 034b66ae9..8ae298c51 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -2,133 +2,207 @@ // 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.Memory; using SixLabors.Primitives; using Xunit; + // 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), - nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Bicubic), nameof(KnownResamplers.Box), nameof(KnownResamplers.Lanczos5), }; - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(AllResamplerNames), DefaultPixelType, 0.5f, null, null)] - [WithFileCollection(nameof(CommonTestImages), nameof(SmokeTestResamplerNames), DefaultPixelType, 0.3f, null, null)] - [WithFileCollection(nameof(CommonTestImages), nameof(SmokeTestResamplerNames), DefaultPixelType, 1.8f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 0.5f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 1f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 50, 50, DefaultPixelType, 8f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 201, 199, DefaultPixelType, null, 100, 99)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 301, 1180, DefaultPixelType, null, 300, 480)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 49, 80, DefaultPixelType, null, 301, 100)] - public void Resize_WorksWithAllResamplers( - TestImageProvider provider, - string samplerName, - float? ratio, - int? specificDestWidth, - int? specificDestHeight) + + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); + + [Fact] + public void Resize_PixelAgnostic() + { + var filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); + + using (Image 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 : struct, IPixel { - IResampler sampler = TestUtils.GetResampler(samplerName); + if (!TestEnvironment.Is64BitProcess) + { + return; + } - // 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; + provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024; - var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); + using (var image = provider.GetImage()) + { + image.Mutate(x => x.Resize(destSize, destSize)); + image.DebugSave(provider, appendPixelTypeToFileName: false); + } + } + + [Theory] + [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] + [WithBasicTestPatternImages(2, 256, PixelTypes.Rgba32, 1, 1, 1, 8)] + [WithBasicTestPatternImages(2, 32, PixelTypes.Rgba32, 1, 1, 1, 2)] + public void Resize_BasicSmall(TestImageProvider provider, int wN, int wD, int hN, int hD) + where TPixel : struct, IPixel + { + // Basic test case, very helpful for debugging + // [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] means: + // resizing: (15, 12) -> (10, 6) + // kernel dimensions: (3, 4) + - 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!"); - } + 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); + } + } - newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value); - destSizeInfo = $"{newSize.Width}x{newSize.Height}"; - } + private static readonly int SizeOfVector4 = Unsafe.SizeOf(); - 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); + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 50)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 60)] + [WithTestPatternImages(100, 400, PixelTypes.Rgba32, 110)] + [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 73)] + [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 5)] + [WithTestPatternImages(47, 193, PixelTypes.Rgba32, 73)] + [WithTestPatternImages(23, 211, PixelTypes.Rgba32, 31)] + public void WorkingBufferSizeHintInBytes_IsAppliedCorrectly( + TestImageProvider provider, + int workingBufferLimitInRows) + where TPixel : struct, IPixel + { + using (Image image0 = provider.GetImage()) + { + Size destSize = image0.Size() / 4; + + Configuration configuration = Configuration.CreateDefaultInstance(); + + int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; + TestMemoryAllocator allocator = new TestMemoryAllocator(); + configuration.MemoryAllocator = allocator; + configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; + + var verticalKernelMap = ResizeKernelMap.Calculate( + KnownResamplers.Bicubic, + destSize.Height, + image0.Height, + Configuration.Default.MemoryAllocator); + int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4; + verticalKernelMap.Dispose(); + + using (Image image = image0.Clone(configuration)) + { + 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) + [WithTestPatternImages(100, 100, DefaultPixelType)] + public void Resize_Compand(TestImageProvider provider) where TPixel : struct, IPixel { - provider.Configuration.MaxDegreeOfParallelism = - maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(image.Size() / 2, true)); - FormattableString details = $"MDP{maxDegreeOfParallelism}"; + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } + + [Theory] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)] + public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand) + where TPixel : struct, IPixel + { + string details = compand ? "Compand" : ""; provider.RunValidatingProcessorTest( - x => x.Resize(x.GetCurrentSize() / 2), + x => x.Resize(x.GetCurrentSize() / 2, compand), details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); } [Theory] - [WithTestPatternImages(100, 100, DefaultPixelType)] - public void Resize_Compand(TestImageProvider provider) + [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] + public void Resize_IsAppliedToAllFrames(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Resize(image.Size() / 2, true)); + image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.Bicubic)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); + // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :( + image.DebugSave(provider, extension: "gif"); } } @@ -152,41 +226,108 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms 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) + [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 { - 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) + [WithFileCollection(nameof(CommonTestImages), nameof(AllResamplerNames), DefaultPixelType, 0.5f, null, null)] + [WithFileCollection( + nameof(CommonTestImages), + nameof(SmokeTestResamplerNames), + DefaultPixelType, + 0.3f, + null, + null)] + [WithFileCollection( + nameof(CommonTestImages), + nameof(SmokeTestResamplerNames), + DefaultPixelType, + 1.8f, + null, + null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 0.5f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 1f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 50, 50, DefaultPixelType, 8f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 201, 199, DefaultPixelType, null, 100, 99)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 301, 1180, DefaultPixelType, null, 300, 480)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 49, 80, DefaultPixelType, null, 301, 100)] + public void Resize_WorksWithAllResamplers( + TestImageProvider provider, + string samplerName, + float? ratio, + int? specificDestWidth, + int? specificDestHeight) where TPixel : struct, IPixel { - 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); - } + // NeirestNeighbourResampler is producing slightly different results With classic .NET framework on 32bit + // most likely because of differences in numeric behavior. + // The difference is well visible when comparing output for + // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png + // TODO: Should we investigate this? + bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess + && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion) + && sampler is NearestNeighborResampler; + + var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); + + // Let's make the working buffer size non-default: + provider.Configuration.WorkingBufferSizeHintInBytes = 16 * 1024 * SizeOfVector4; + + provider.RunValidatingProcessorTest( + ctx => + { + SizeF newSize; + string destSizeInfo; + if (ratio.HasValue) + { + newSize = ctx.GetCurrentSize() * ratio.Value; + destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + else + { + if (!specificDestWidth.HasValue || !specificDestHeight.HasValue) + { + throw new InvalidOperationException( + "invalid dimensional input for Resize_WorksWithAllResamplers!"); + } + + newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value); + destSizeInfo = $"{newSize.Width}x{newSize.Height}"; + } + + FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}"; + + ctx.Resize((Size)newSize, sampler, false); + return testOutputDetails; + }, + comparer, + appendPixelTypeToFileName: false); } [Theory] @@ -196,10 +337,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { 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 +360,39 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWidthAndKeepAspect(TestImageProvider provider) + public void ResizeHeightAndKeepAspect(TestImageProvider provider) where TPixel : struct, 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 : 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 ResizeHeightAndKeepAspect(TestImageProvider provider) + public void ResizeWidthAndKeepAspect(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Resize(0, image.Height / 3, false)); + image.Mutate(x => x.Resize(image.Width / 3, 0, false)); image.DebugSave(provider); image.CompareToReferenceOutput(ValidatorComparer, provider); @@ -247,30 +412,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) + public void ResizeWithBoxPadMode(TestImageProvider provider) where TPixel : struct, 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)); @@ -286,10 +438,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { 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 +449,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWithPadMode(TestImageProvider provider) + public void ResizeWithCropWidthMode(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - var options = new ResizeOptions - { - Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad - }; + var options = new ResizeOptions { Size = new Size(image.Width / 2, image.Height) }; image.Mutate(x => x.Resize(options)); @@ -320,16 +465,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWithBoxPadMode(TestImageProvider provider) + public void ResizeWithMaxMode(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - var options = new ResizeOptions - { - Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad - }; + var options = new ResizeOptions { Size = new Size(300, 300), Mode = ResizeMode.Max }; image.Mutate(x => x.Resize(options)); @@ -340,16 +481,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWithMaxMode(TestImageProvider provider) + public void ResizeWithMinMode(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions - { - Size = new Size(300, 300), - Mode = ResizeMode.Max - }; + { + Size = new Size( + (int)Math.Round(image.Width * .75F), + (int)Math.Round(image.Height * .95F)), + Mode = ResizeMode.Min + }; image.Mutate(x => x.Resize(options)); @@ -360,16 +503,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] - public void ResizeWithMinMode(TestImageProvider provider) + public void ResizeWithPadMode(TestImageProvider provider) where TPixel : struct, 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)), - Mode = ResizeMode.Min - }; + { + Size = new Size(image.Width + 200, image.Height), Mode = ResizeMode.Pad + }; image.Mutate(x => x.Resize(options)); @@ -386,10 +528,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms 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 / 2, image.Height), Mode = ResizeMode.Stretch + }; image.Mutate(x => x.Resize(options)); @@ -397,61 +538,5 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms image.CompareToReferenceOutput(ValidatorComparer, provider); } } - - [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 TriangleWindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Triangle; - 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); - } } } \ 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 d6376b179..20e12cb7f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs @@ -9,7 +9,7 @@ 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,8 +24,8 @@ 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 { 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 29c51543f..000000000 --- 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 000000000..ad77027f0 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +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 : struct, IPixel + { + provider.RunValidatingProcessorTest(ctx => ctx.Skew(x, y), $"{x}_{y}", ValidatorComparer); + } + + [Theory] + [WithFile(TestImages.Png.Ducky, nameof(ResamplerNames), PixelTypes.Rgba32)] + public void Skew_WorksWithAllResamplers(TestImageProvider provider, string resamplerName) + where TPixel : struct, 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/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs index bba4661db..0547b46e3 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs @@ -16,7 +16,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 6731debd3..1c3bf2e90 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -18,7 +18,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 +30,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 03a8628a5..aa684acd5 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs @@ -16,7 +16,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 39adcaa3f..9fe2977ad 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -22,7 +22,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 82d768255..b870ddd08 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. // 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,7 +18,7 @@ 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); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index 948c79d8d..570ac4e2c 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -17,7 +17,7 @@ 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); @@ -30,7 +30,7 @@ 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); @@ -47,7 +47,7 @@ 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); @@ -73,7 +73,7 @@ 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); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs index dccf7afa6..079ff67b7 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs @@ -26,8 +26,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 ae312d723..3e2b85971 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs @@ -17,7 +17,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 +30,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 73754b971..38033e80d 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs @@ -16,7 +16,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/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs index 909e50535..3ac9af960 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. // 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; @@ -18,14 +18,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms using (var img = new Image(xy, xy)) { var profile = new ExifProfile(); - img.MetaData.ExifProfile = profile; + img.Metadata.ExifProfile = profile; profile.SetValue(ExifTag.PixelXDimension, (uint)xy); profile.SetValue(ExifTag.PixelYDimension, (uint)xy); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); - TransformProcessorHelpers.UpdateDimensionalMetData(img); + TransformProcessorHelpers.UpdateDimensionalMetadata(img); Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType); Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType); diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs index 65989556d..95a47fd7c 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -82,7 +82,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( diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index a0d7869e3..04e9803af 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. // Licensed under the Apache License, Version 2.0. +using System; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -47,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { - QuantizedFrame quantized = + IQuantizedFrame quantized = quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); int index = this.GetTransparentIndex(quantized); @@ -70,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { - QuantizedFrame quantized = + IQuantizedFrame quantized = quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); int index = this.GetTransparentIndex(quantized); @@ -79,15 +81,16 @@ namespace SixLabors.ImageSharp.Tests } } - private int GetTransparentIndex(QuantizedFrame quantized) + private int GetTransparentIndex(IQuantizedFrame quantized) where TPixel : struct, 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--) + ReadOnlySpan paletteSpan = quantized.Palette.Span; + for (int i = paletteSpan.Length - 1; i >= 0; i--) { - quantized.Palette[i].ToRgba32(ref trans); + paletteSpan[i].ToRgba32(ref trans); if (trans.Equals(default)) { diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs new file mode 100644 index 000000000..a1de7fd4b --- /dev/null +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -0,0 +1,170 @@ +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(false); + + using (var image = new Image(config, 1, 1, Rgba32.Black)) + using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) + { + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.GetPixelSpan().Length); + + Assert.Equal(Rgba32.Black, result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelSpan()[0]); + } + } + + [Fact] + public void SinglePixelTransparent() + { + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(false); + + using (var image = new Image(config, 1, 1, default(Rgba32))) + using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) + { + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.GetPixelSpan().Length); + + Assert.Equal(default, result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelSpan()[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(false); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) + using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) + { + Assert.Equal(256, result.Palette.Length); + Assert.Equal(256, result.GetPixelSpan().Length); + + var actualImage = new Image(1, 256); + + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = result.Palette.Length - 1; + for (int y = 0; y < actualImage.Height; y++) + { + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + int yy = y * actualImage.Width; + + for (int x = 0; x < actualImage.Width; x++) + { + int i = x + yy; + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; + } + } + + Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); + } + } + } + + [Theory] + [WithFile(TestImages.Png.LowColorVariance, PixelTypes.Rgba32)] + public void LowVariance(TestImageProvider provider) + where TPixel : struct, IPixel + { + // See https://github.com/SixLabors/ImageSharp/issues/866 + using (Image image = provider.GetImage()) + { + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(false); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) + using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) + { + 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(false); + + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) + using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) + { + Assert.Equal(4 * 8, result.Palette.Length); + Assert.Equal(256, result.GetPixelSpan().Length); + + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = result.Palette.Length - 1; + for (int y = 0; y < actualImage.Height; y++) + { + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + int yy = y * actualImage.Width; + + for (int x = 0; x < actualImage.Width; x++) + { + int i = x + yy; + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; + } + } + } + + Assert.True(expectedImage.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs index b39892a81..334ee026d 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs @@ -2,7 +2,7 @@ // 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 { diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs index 4ab0b0f6b..5ef2156c7 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs index fb648198d..586e84680 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs index f16da90c6..f19029f8c 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs index b037a7a9a..49ff19046 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -3,7 +3,7 @@ using System; using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs index f7c4efa92..b5da22443 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs @@ -3,7 +3,7 @@ using System.Globalization; using System.Numerics; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 089249e21..6ca86ced6 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Tests /// /// The . /// - public Image CreateImage() + public Image CreateRgba32Image() { return this.Image.Clone(); } @@ -145,9 +145,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 21ad4d2c1..9211e70f7 100644 --- a/tests/ImageSharp.Tests/TestFileSystem.cs +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -12,29 +12,24 @@ namespace SixLabors.ImageSharp.Tests /// public class TestFileSystem : ImageSharp.IO.IFileSystem { - - public static TestFileSystem Global { get; } = new TestFileSystem(); - - public static void RegisterGlobalTestFormat() - { - Configuration.Default.FileSystem = Global; - } - - Dictionary fileSystem = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary fileSystem = new Dictionary(StringComparer.OrdinalIgnoreCase); public void AddFile(string path, Stream 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; } @@ -60,5 +54,4 @@ namespace SixLabors.ImageSharp.Tests return File.OpenRead(path); } } -} - +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 64357a17e..23bd0a54c 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Numerics; using System.Reflection; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -18,6 +19,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(); @@ -49,12 +52,23 @@ namespace SixLabors.ImageSharp.Tests return ms; } - Dictionary _sampleImages = new Dictionary(); + public void VerifySpecificDecodeCall(byte[] marker, Configuration config) + where TPixel : struct, IPixel + { + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); - public void VerifyDecodeCall(byte[] marker, Configuration config) + 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)).ToArray(); + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend"); @@ -68,17 +82,19 @@ namespace SixLabors.ImageSharp.Tests public Image Sample() where TPixel : struct, 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"; @@ -123,10 +139,12 @@ namespace SixLabors.ImageSharp.Tests public byte[] marker; internal Configuration config; - public bool IsMatch(byte[] testMarker, Configuration config) + public Type pixelType; + + public bool IsMatch(byte[] testMarker, Configuration config, Type pixelType) { - if (this.config != config) + if (this.config != config || this.pixelType != pixelType) { return false; } @@ -191,7 +209,8 @@ namespace SixLabors.ImageSharp.Tests this.testFormat.DecodeCalls.Add(new DecodeOperation { marker = marker, - config = config + config = config, + pixelType = typeof(TPixel) }); // TODO record this happend so we can verify it. @@ -199,6 +218,8 @@ namespace SixLabors.ImageSharp.Tests } public bool IsSupportedFileFormat(Span header) => testFormat.IsSupportedFileFormat(header); + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } public class TestEncoder : ImageSharp.Formats.IImageEncoder @@ -219,5 +240,27 @@ namespace SixLabors.ImageSharp.Tests // TODO record this happend so we can verify it. } } + + + struct TestPixelForAgnosticDecode : IPixel + { + public PixelOperations CreatePixelOperations() => new PixelOperations(); + public void FromScaledVector4(Vector4 vector) { } + public Vector4 ToScaledVector4() => default; + public void FromVector4(Vector4 vector) { } + 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 FromGray8(Gray8 source) { } + public void FromGray16(Gray16 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 e5b93ab77..754ce20ca 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Linq; @@ -29,6 +29,10 @@ namespace SixLabors.ImageSharp.Tests public const string Gray4Bpp = "Png/gray_4bpp.png"; public const string Gray16Bit = "Png/gray-16.png"; public const string GrayAlpha8Bit = "Png/gray-alpha-8.png"; + public const string GrayAlpha8BitInterlaced = "Png/rollsroyce.png"; + public const string GrayAlpha1BitInterlaced = "Png/iftbbn0g01.png"; + public const string GrayAlpha2BitInterlaced = "Png/iftbbn0g02.png"; + public const string Gray4BitInterlaced = "Png/iftbbn0g04.png"; public const string GrayAlpha16Bit = "Png/gray-alpha-16.png"; public const string GrayTrns16BitInterlaced = "Png/gray-16-tRNS-interlaced.png"; public const string Rgb24BppTrans = "Png/rgb-8-tRNS.png"; @@ -49,6 +53,7 @@ namespace SixLabors.ImageSharp.Tests public const string Gray2BitTrans = "Png/gray-2-tRNS.png"; public const string Gray4BitTrans = "Png/gray-4-tRNS.png"; public const string Gray8BitTrans = "Png/gray-8-tRNS.png"; + public const string LowColorVariance = "Png/low-variance.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; @@ -81,6 +86,8 @@ namespace SixLabors.ImageSharp.Tests public const string ChunkLength1 = "Png/chunklength1.png"; public const string ChunkLength2 = "Png/chunklength2.png"; public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; + public const string ZlibOverflow = "Png/zlib-overflow.png"; + public const string ZlibOverflow2 = "Png/zlib-overflow2.png"; } public static readonly string[] All = @@ -135,12 +142,16 @@ 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 static readonly string[] All = { Cmyk, Ycck, Exif, Floorplan, Calliphora, Turtle, GammaDalaiLamaGray, - Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1 + Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, + Ratio1x1, Testorig12bit, YcckSubsample1222 }; } @@ -165,6 +176,35 @@ 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 IncorrectQuality845 = "Jpg/issues/Issue845-Incorrect-Quality99.jpg"; + public const string IncorrectColorspace855 = "Jpg/issues/issue855-incorrect-colorspace.jpg"; + + public static class Fuzz + { + public const string NullReferenceException797 = "Jpg/issues/fuzz/Issue797-NullReferenceException.jpg"; + public const string AccessViolationException798 = "Jpg/issues/fuzz/Issue798-AccessViolationException.jpg"; + public const string DivideByZeroException821 = "Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg"; + public const string DivideByZeroException822 = "Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg"; + public const string NullReferenceException823 = "Jpg/issues/fuzz/Issue823-NullReferenceException.jpg"; + public const string IndexOutOfRangeException824A = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg"; + public const string IndexOutOfRangeException824B = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg"; + public const string IndexOutOfRangeException824C = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg"; + public const string IndexOutOfRangeException824D = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg"; + public const string IndexOutOfRangeException824E = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg"; + public const string IndexOutOfRangeException824F = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg"; + public const string IndexOutOfRangeException824G = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg"; + public const string IndexOutOfRangeException824H = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg"; + public const string ArgumentOutOfRangeException825A = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg"; + public const string ArgumentOutOfRangeException825B = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg"; + public const string ArgumentOutOfRangeException825C = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg"; + public const string ArgumentOutOfRangeException825D = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg"; + public const string ArgumentException826A = "Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg"; + public const string ArgumentException826B = "Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg"; + public const string ArgumentException826C = "Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg"; + public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg"; + public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg"; + public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg"; + } } public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); @@ -191,38 +231,106 @@ 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 RLE = "Bmp/RunLengthEncoded.bmp"; - public const string RLEInverted = "Bmp/RunLengthEncoded-inverted.bmp"; + public const string RLE24 = "Bmp/rgb24rle24.bmp"; + public const string RLE24Cut = "Bmp/rle24rlecut.bmp"; + public const string RLE24Delta = "Bmp/rle24rlecut.bmp"; + public const string RLE8 = "Bmp/RunLengthEncoded.bmp"; + public const string RLE8Cut = "Bmp/pal8rlecut.bmp"; + public const string RLE8Delta = "Bmp/pal8rletrns.bmp"; + public const string Rle8Delta320240 = "Bmp/rle8-delta-320x240.bmp"; + public const string Rle8Blank160120 = "Bmp/rle8-blank-160x120.bmp"; + public const string RLE8Inverted = "Bmp/RunLengthEncoded-inverted.bmp"; + public const string RLE4 = "Bmp/pal4rle.bmp"; + public const string RLE4Cut = "Bmp/pal4rlecut.bmp"; + public const string RLE4Delta = "Bmp/pal4rletrns.bmp"; + public const string Rle4Delta320240 = "Bmp/rle4-delta-320x240.bmp"; public const string Bit1 = "Bmp/pal1.bmp"; public const string Bit1Pal1 = "Bmp/pal1p1.bmp"; public const string Bit4 = "Bmp/pal4.bmp"; public const string Bit8 = "Bmp/test8.bmp"; + public const string Bit8Gs = "Bmp/pal8gs.bmp"; public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; public const string Bit16 = "Bmp/test16.bmp"; public const string Bit16Inverted = "Bmp/test16-inverted.bmp"; public const string Bit32Rgb = "Bmp/rgb32.bmp"; - + public const string Bit32Rgba = "Bmp/rgba32.bmp"; + public const string Rgb16 = "Bmp/rgb16.bmp"; + // Note: This format can be called OS/2 BMPv1, or Windows BMPv2 public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp"; + + public const string WinBmpv3 = "Bmp/rgb24.bmp"; + public const string WinBmpv4 = "Bmp/pal8v4.bmp"; + public const string WinBmpv5 = "Bmp/pal8v5.bmp"; public const string Bit8Palette4 = "Bmp/pal8-0.bmp"; public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp"; + public const string Os2v2 = "Bmp/pal8os2v2.bmp"; + public const string Os2BitmapArray = "Bmp/ba-bm.bmp"; + public const string Os2BitmapArray9s = "Bmp/9S.BMP"; + public const string Os2BitmapArrayDiamond = "Bmp/DIAMOND.BMP"; + public const string Os2BitmapArrayMarble = "Bmp/GMARBLE.BMP"; + public const string Os2BitmapArraySkater = "Bmp/SKATER.BMP"; + public const string Os2BitmapArraySpade = "Bmp/SPADE.BMP"; + public const string Os2BitmapArraySunflower = "Bmp/SUNFLOW.BMP"; + public const string Os2BitmapArrayWarpd = "Bmp/WARPD.BMP"; + public const string Os2BitmapArrayPines = "Bmp/PINES.BMP"; + public const string LessThanFullSizedPalette = "Bmp/pal8os2sp.bmp"; + public const string Pal8Offset = "Bmp/pal8offs.bmp"; + public const string OversizedPalette = "Bmp/pal8oversizepal.bmp"; + public const string Rgb24LargePalette = "Bmp/rgb24largepal.bmp"; + public const string InvalidPaletteSize = "Bmp/invalidPaletteSize.bmp"; + public const string Rgb24jpeg = "Bmp/rgb24jpeg.bmp"; + public const string Rgb24png = "Bmp/rgb24png.bmp"; + public const string Rgba32v4 = "Bmp/rgba32v4.bmp"; - public static readonly string[] All + // Bitmap images with compression type BITFIELDS. + public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp"; + public const string Rgb32bf = "Bmp/rgb32bf.bmp"; + public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp"; + public const string Rgb16565 = "Bmp/rgb16-565.bmp"; + public const string Rgb16565pal = "Bmp/rgb16-565pal.bmp"; + public const string Issue735 = "Bmp/issue735.bmp"; + public const string Rgba32bf56AdobeV3 = "Bmp/rgba32h56.bmp"; + public const string Rgb32h52AdobeV3 = "Bmp/rgb32h52.bmp"; + public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp"; + public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp"; + + public static readonly string[] BitFields + = { + Rgb32bfdef, + Rgb32bf, + Rgb16565, + Rgb16bfdef, + Rgb16565pal, + Issue735, + }; + + public static readonly string[] Miscellaneous = { Car, F, - NegHeight, - CoreHeader, - V5Header, RLE, - RLEInverted, - Bit1, - Bit1Pal1, - Bit4, - Bit8, - Bit8Inverted, - Bit16, - Bit16Inverted + NegHeight }; + + public static readonly string[] Benchmark + = { + Car, + F, + NegHeight, + CoreHeader, + V5Header, + RLE4, + RLE8, + RLE8Inverted, + Bit1, + Bit1Pal1, + Bit4, + Bit8, + Bit8Inverted, + Bit16, + Bit16Inverted, + Bit32Rgb + }; } public static class Gif diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 47ca6cccb..872a935ff 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Numerics; +using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Tests { @@ -11,8 +12,9 @@ namespace SixLabors.ImageSharp.Tests /// internal readonly struct ApproximateFloatComparer : IEqualityComparer, + IEqualityComparer, IEqualityComparer, - IEqualityComparer + IEqualityComparer { private readonly float Epsilon; @@ -20,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests /// 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) @@ -34,17 +36,29 @@ namespace SixLabors.ImageSharp.Tests public int GetHashCode(float obj) => obj.GetHashCode(); /// - public bool Equals(Vector4 a, Vector4 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W); + public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); /// - public int GetHashCode(Vector4 obj) => obj.GetHashCode(); + public int GetHashCode(Vector2 obj) => obj.GetHashCode(); + + /// + public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W); /// - public bool Equals(Vector2 a, Vector2 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y); + public int GetHashCode(Vector4 obj) => obj.GetHashCode(); - public int GetHashCode(Vector2 obj) + /// + public bool Equals(ColorMatrix x, ColorMatrix y) { - throw new System.NotImplementedException(); + return + this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) + && this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) + && this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34) + && this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44) + && this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54); } + + /// + public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); } } \ 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 000000000..1e4324e04 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; + +namespace SixLabors.ImageSharp.Tests +{ + public class WithBasicTestPatternImagesAttribute : ImageDataAttributeBase + { + 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/WithSolidFilledImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs index f95db45f7..190e80fba 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs @@ -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( diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs new file mode 100644 index 000000000..752f8e18b --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -0,0 +1,65 @@ +// 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.PixelFormats; + +namespace SixLabors.ImageSharp.Tests +{ + public abstract partial class TestImageProvider + { + private class BasicTestPatternProvider : BlankProvider + { + 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); + + TPixel topLeftColor = Color.Red.ToPixel(); + TPixel topRightColor = Color.Green.ToPixel(); + TPixel bottomLeftColor = Color.Blue.ToPixel(); + + // Transparent purple: + TPixel bottomRightColor = default; + bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); + + 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; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index 7821d0b51..dae2f0cfe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -20,6 +20,9 @@ namespace SixLabors.ImageSharp.Tests this.Height = height; } + /// + /// This parameterless constructor is needed for xUnit deserialization + /// public BlankProvider() { this.Width = 100; @@ -32,7 +35,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) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 3ed696c47..8c5b88b28 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests Image cachedImage = cache.GetOrAdd(key, _ => this.LoadImage(decoder)); - return cachedImage.Clone(); + return cachedImage.Clone(this.Configuration); } public override void Deserialize(IXunitSerializationInfo info) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index d68c37a76..88b30ce34 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -35,6 +35,9 @@ namespace SixLabors.ImageSharp.Tests this.a = a; } + /// + /// This parameterless constructor is needed for xUnit deserialization + /// public SolidProvider() : base() { @@ -50,8 +53,7 @@ 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)); return image; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 5b5e4740a..8d493c7d4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -44,6 +44,12 @@ namespace SixLabors.ImageSharp.Tests 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, @@ -98,9 +104,9 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns an instance to the test case with the necessary traits. /// - public Image GetImage(Action> operationsToApply) + public Image GetImage(Action operationsToApply) { - Image img = GetImage(); + Image img = this.GetImage(); img.Mutate(operationsToApply); return img; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 17e5369d4..94b2b3391 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Net.Mime; using System.Numerics; using SixLabors.ImageSharp.Memory; @@ -20,13 +21,25 @@ namespace SixLabors.ImageSharp.Tests { static readonly Dictionary> TestImages = new Dictionary>(); + private static TPixel[] BlackWhitePixels = new[] { + Color.Black.ToPixel(), + Color.White.ToPixel() + }; + + private static TPixel[] PinkBluePixels = new[] { + 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() { } @@ -42,9 +55,8 @@ namespace SixLabors.ImageSharp.Tests DrawTestPattern(image); TestImages.Add(this.SourceFileOrDescription, image); } + return TestImages[this.SourceFileOrDescription].Clone(this.Configuration); } - - return TestImages[this.SourceFileOrDescription].Clone(); } /// @@ -78,12 +90,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 +98,9 @@ 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]; } } } @@ -111,11 +117,6 @@ 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++) @@ -123,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests if (y % stride == 0) { p++; - p = p % c.Length; + p = p % BlackWhitePixels.Length; } int pstart = p; for (int x = left; x < right; x++) @@ -131,9 +132,9 @@ namespace SixLabors.ImageSharp.Tests if (x % stride == 0) { p++; - p = p % c.Length; + p = p % BlackWhitePixels.Length; } - pixels[x, y] = c[p]; + pixels[x, y] = BlackWhitePixels[p]; } p = pstart; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index c91ef56a1..92cc9f636 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -152,21 +152,19 @@ 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 /// 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, + 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, diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index a051e577d..78431f31a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -58,6 +58,10 @@ namespace SixLabors.ImageSharp.Tests Rgb48 = 1 << 21, + Bgra5551 = 1 << 22, + + Gray8 = 1 << 23, + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper // "All" is handled as a separate, individual case instead of using bitwise OR diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 3dd330e4d..e81714ddc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -57,5 +57,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return result; } } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } \ 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 7e87c23db..79c19f2be 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -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; } @@ -91,33 +98,36 @@ 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; } @@ -131,27 +141,32 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs var resultBitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb); var fullRect = new Rectangle(0, 0, w, h); BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); - 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; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 427a56542..2de3c03aa 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -4,7 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs @@ -48,8 +48,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs 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); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs index e3d8bf380..4ccb38745 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs @@ -23,14 +23,19 @@ namespace SixLabors.ImageSharp.Tests { float[] 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. /// diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 29d39596b..8de3a347b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -9,10 +9,13 @@ using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; using Xunit; @@ -21,75 +24,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); - - for (int i = 0; i < tempSpan.Length; i++) - { - ref Vector4 v = ref tempSpan[i]; - v.W = 1F; - } + public static void MakeOpaque(this IImageProcessingContext ctx) => + ctx.ApplyProcessor(new MakeOpaqueProcessor()); - 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 /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. - public static Image DebugSave( - this Image image, + /// Custom encoder to use. + 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 +78,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 +119,6 @@ namespace SixLabors.ImageSharp.Tests encoder: encoder, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); - return image; } public static Image DebugSaveMultiFrame( @@ -255,6 +228,7 @@ namespace SixLabors.ImageSharp.Tests /// 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. /// public static Image CompareToReferenceOutput( this Image image, @@ -264,7 +238,8 @@ namespace SixLabors.ImageSharp.Tests string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) + bool appendSourceFileOrDescription = true, + IImageDecoder decoder = null) where TPixel : struct, IPixel { using (Image referenceImage = GetReferenceOutputImage( @@ -272,7 +247,8 @@ namespace SixLabors.ImageSharp.Tests testOutputDetails, extension, appendPixelTypeToFileName, - appendSourceFileOrDescription)) + appendSourceFileOrDescription, + decoder)) { comparer.VerifySimilarity(referenceImage, image); } @@ -356,7 +332,8 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) + bool appendSourceFileOrDescription = true, + IImageDecoder decoder = null) where TPixel : struct, IPixel { string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( @@ -370,7 +347,7 @@ namespace SixLabors.ImageSharp.Tests throw new System.IO.FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(referenceOutputFile); + decoder = decoder ?? TestEnvironment.GetReferenceDecoder(referenceOutputFile); return Image.Load(referenceOutputFile, decoder); } @@ -475,6 +452,20 @@ namespace SixLabors.ImageSharp.Tests return image; } + + /// + /// All pixels in all frames should be exactly equal to 'expectedPixelColor.ToPixel()'. + /// + public static Image ComparePixelBufferTo(this Image image, Color expectedPixelColor) + where TPixel : struct, IPixel + { + foreach (ImageFrame imageFrame in image.Frames) + { + imageFrame.ComparePixelBufferTo(expectedPixelColor.ToPixel()); + } + + return image; + } /// /// All pixels in the frame should be exactly equal to 'expectedPixel'. @@ -686,5 +677,37 @@ namespace SixLabors.ImageSharp.Tests return image; } + private class MakeOpaqueProcessor : IImageProcessor + { + public IImageProcessor CreatePixelSpecificProcessor() + where TPixel : struct, IPixel + { + return new MakeOpaqueProcessor(); + } + } + + private class MakeOpaqueProcessor : ImageProcessor + where TPixel : struct, IPixel + { + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + ParallelHelper.IterateRowsWithTempBuffer(sourceRectangle, configuration, + (rows, temp) => + { + Span tempSpan = temp.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + var rowSpan = source.GetPixelRowSpan(y).Slice(sourceRectangle.Left, sourceRectangle.Width); + PixelOperations.Instance.ToVector4(configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); + for (int i = 0; i < tempSpan.Length; i++) + { + ref Vector4 v = ref tempSpan[i]; + v.W = 1F; + } + PixelOperations.Instance.FromVector4Destructive(configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale); + } + }); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index dc755e682..5613e7b68 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -1,5 +1,7 @@ using System; using System.Buffers; +using System.Collections.Generic; +using System.Numerics; using System.Runtime.InteropServices; using SixLabors.Memory; @@ -8,6 +10,8 @@ namespace SixLabors.ImageSharp.Tests.Memory { internal class TestMemoryAllocator : MemoryAllocator { + private List allocationLog = new List(); + public TestMemoryAllocator(byte dirtyValue = 42) { this.DirtyValue = dirtyValue; @@ -18,10 +22,11 @@ namespace SixLabors.ImageSharp.Tests.Memory /// public byte DirtyValue { get; } + public IList AllocationLog => this.allocationLog; + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { + { T[] array = this.AllocateArray(length, options); - return new BasicArrayBuffer(array, length); } @@ -34,6 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Memory private T[] AllocateArray(int length, AllocationOptions options) where T : struct { + this.allocationLog.Add(AllocationRequest.Create(options, length)); var array = new T[length + 42]; if (options == AllocationOptions.None) @@ -44,6 +50,35 @@ namespace SixLabors.ImageSharp.Tests.Memory return array; } + + public struct AllocationRequest + { + private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes) + { + this.ElementType = elementType; + this.AllocationOptions = allocationOptions; + this.Length = length; + this.LengthInBytes = lengthInBytes; + + if (elementType == typeof(Vector4)) + { + + } + } + + public static AllocationRequest Create(AllocationOptions allocationOptions, int length) + { + Type type = typeof(T); + int elementSize = Marshal.SizeOf(type); + return new AllocationRequest(type, allocationOptions, length, length * elementSize); + } + + public Type ElementType { get; } + public AllocationOptions AllocationOptions { get; } + public int Length { get; } + public int LengthInBytes { get; } + } + /// /// Wraps an array as an instance. diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index e51aa28d8..b56ce0517 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -141,8 +141,15 @@ 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 : struct, IPixel => + GetColorByName(colorName).ToPixel(); /// /// Utility for testing image processor extension methods: @@ -158,7 +165,7 @@ namespace SixLabors.ImageSharp.Tests /// internal static void RunValidatingProcessorTest( this TestImageProvider provider, - Action> process, + Action process, object testOutputDetails = null, ImageComparer comparer = null, bool appendPixelTypeToFileName = true, @@ -195,7 +202,7 @@ 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) @@ -234,7 +241,7 @@ namespace SixLabors.ImageSharp.Tests public static void RunValidatingProcessorTestOnWrappedMemoryImage( this TestImageProvider provider, - Action> process, + Action process, object testOutputDetails = null, ImageComparer comparer = null, string useReferenceOutputFrom = null, @@ -284,11 +291,11 @@ 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 @@ -308,11 +315,11 @@ namespace SixLabors.ImageSharp.Tests } /// - /// 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 { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs index 033f0866a..15213d5d3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { foreach (string testFile in testFiles) { - Image image = TestFile.Create(testFile).CreateImage(decoder); + Image image = TestFile.Create(testFile).CreateRgba32Image(decoder); image.Dispose(); } }, diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index cac7828e9..b49baa5c4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -4,112 +4,135 @@ using System; using System.Collections.Concurrent; using System.IO; + +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; + using Xunit; using Xunit.Abstractions; + // 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 + /// + /// + /// + /// + public static Image CreateTestImage() + where TPixel : struct, IPixel => + new Image(3, 3); [Theory] - [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] - public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) + [MemberData(nameof(BasicData))] + public void Blank_MemberData(TestImageProvider provider) where TPixel : struct, 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) + [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()); + 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) + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( + TestImageProvider provider) where TPixel : struct, IPixel { + if (!TestEnvironment.Is64BitProcess) + { + // We don't cache with the 32 bit build. + return; + } + 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 : struct, 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) + public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( + TestImageProvider provider) where TPixel : struct, IPixel { if (!TestEnvironment.Is64BitProcess) @@ -120,121 +143,122 @@ 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 : struct, IPixel => + Assert.Empty(provider.Utility.OutputSubfolderName); - private string callerName = null; + [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); - internal void InitCaller(string name) + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void SaveTestOutputFileMultiFrame(TestImageProvider provider) + where TPixel : struct, 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) + [WithBasicTestPatternImages(50, 100, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(49, 17, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(20, 10, PixelTypes.Rgba32)] + public void Use_WithBasicTestPatternImages(TestImageProvider provider) where TPixel : struct, IPixel { - 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 : struct, 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 : struct, 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) + [WithFile(TestImages.Bmp.Car, PixelTypes.All, 123)] + [WithFile(TestImages.Bmp.F, PixelTypes.All, 123)] + public void Use_WithFileAttribute(TestImageProvider provider, int yo) where TPixel : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); - - TestDecoderWithParameters.DoTestThreadSafe(() => + using (Image img = provider.GetImage()) { - string testName = - nameof(this.GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); + Assert.True(img.Width * img.Height > 0); - var decoder1 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 42 }; - decoder1.InitCaller(testName); + Assert.Equal(123, yo); - var decoder2 = new TestDecoderWithParameters() { Param1 = "LoL", Param2 = 42 }; - decoder2.InitCaller(testName); - - 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 : struct, IPixel + { + EnsureCustomConfigurationIsApplied(provider); + } [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] @@ -249,20 +273,15 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void SaveTestOutputFileMultiFrame(TestImageProvider provider) + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] + public void Use_WithMemberFactoryAttribute(TestImageProvider provider) where TPixel : struct, 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); } } @@ -291,75 +310,116 @@ 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) + [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] + public void Use_WithTestPatternImages(TestImageProvider provider) where TPixel : struct, 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) + [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] + public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image img = provider.GetImage()) + EnsureCustomConfigurationIsApplied(provider); + } + + private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (var image1 = provider.GetImage()) { - img.DebugSave(provider); + var customConfiguration = Configuration.CreateDefaultInstance(); + provider.Configuration = customConfiguration; + + using (var image2 = provider.GetImage()) + using (var 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: - [Theory] - [MemberData(nameof(BasicData))] - public void Blank_MemberData(TestImageProvider provider) - where TPixel : struct, IPixel - { - Image img = provider.GetImage(); + private static readonly ConcurrentDictionary invocationCounts = + new ConcurrentDictionary(); - Assert.True(img.Width * img.Height > 0); + private static readonly object Monitor = new object(); + + private string callerName = null; + + public static void DoTestThreadSafe(Action action) + { + lock (Monitor) + { + action(); + } + } + + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + invocationCounts[this.callerName]++; + return new Image(42, 42); + } + + internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + + internal void InitCaller(string name) + { + this.callerName = name; + invocationCounts[name] = 0; + } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } - 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 object Monitor = new object(); - Image img = provider.GetImage(); + private string callerName = null; - Assert.True(img.Width * img.Height > 0); + 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 : struct, IPixel + { + invocationCounts[this.callerName]++; + return new Image(42, 42); + } + + internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + + internal void InitCaller(string name) + { + this.callerName = name; + invocationCounts[name] = 0; + } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } -} +} \ No newline at end of file diff --git a/tests/Images/External b/tests/Images/External index 69603ee5b..acc32594c 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 69603ee5b6f7dd64114fc44d321e50d9b2d439be +Subproject commit acc32594c125656840f8a17e69b0ebb49a370fa6 diff --git a/tests/Images/Input/Bmp/9S.BMP b/tests/Images/Input/Bmp/9S.BMP new file mode 100644 index 000000000..c889ec75e Binary files /dev/null and b/tests/Images/Input/Bmp/9S.BMP differ diff --git a/tests/Images/Input/Bmp/DIAMOND.BMP b/tests/Images/Input/Bmp/DIAMOND.BMP new file mode 100644 index 000000000..fff96d00e Binary files /dev/null and b/tests/Images/Input/Bmp/DIAMOND.BMP differ diff --git a/tests/Images/Input/Bmp/GMARBLE.BMP b/tests/Images/Input/Bmp/GMARBLE.BMP new file mode 100644 index 000000000..6d865a9e7 Binary files /dev/null and b/tests/Images/Input/Bmp/GMARBLE.BMP differ diff --git a/tests/Images/Input/Bmp/PINES.BMP b/tests/Images/Input/Bmp/PINES.BMP new file mode 100644 index 000000000..63dcc9f0f Binary files /dev/null and b/tests/Images/Input/Bmp/PINES.BMP differ diff --git a/tests/Images/Input/Bmp/SKATER.BMP b/tests/Images/Input/Bmp/SKATER.BMP new file mode 100644 index 000000000..ad0e24d28 Binary files /dev/null and b/tests/Images/Input/Bmp/SKATER.BMP differ diff --git a/tests/Images/Input/Bmp/SPADE.BMP b/tests/Images/Input/Bmp/SPADE.BMP new file mode 100644 index 000000000..31c0c02e7 Binary files /dev/null and b/tests/Images/Input/Bmp/SPADE.BMP differ diff --git a/tests/Images/Input/Bmp/SUNFLOW.BMP b/tests/Images/Input/Bmp/SUNFLOW.BMP new file mode 100644 index 000000000..852355224 Binary files /dev/null and b/tests/Images/Input/Bmp/SUNFLOW.BMP differ diff --git a/tests/Images/Input/Bmp/WARPD.BMP b/tests/Images/Input/Bmp/WARPD.BMP new file mode 100644 index 000000000..ecfcd79b0 Binary files /dev/null and b/tests/Images/Input/Bmp/WARPD.BMP differ diff --git a/tests/Images/Input/Bmp/ba-bm.bmp b/tests/Images/Input/Bmp/ba-bm.bmp new file mode 100644 index 000000000..d2615bde3 Binary files /dev/null and b/tests/Images/Input/Bmp/ba-bm.bmp differ diff --git a/tests/Images/Input/Bmp/invalidPaletteSize.bmp b/tests/Images/Input/Bmp/invalidPaletteSize.bmp new file mode 100644 index 000000000..afb120bbf Binary files /dev/null and b/tests/Images/Input/Bmp/invalidPaletteSize.bmp differ diff --git a/tests/Images/Input/Bmp/issue735.bmp b/tests/Images/Input/Bmp/issue735.bmp new file mode 100644 index 000000000..31fadfcf5 Binary files /dev/null and b/tests/Images/Input/Bmp/issue735.bmp differ diff --git a/tests/Images/Input/Bmp/pal4rle.bmp b/tests/Images/Input/Bmp/pal4rle.bmp new file mode 100644 index 000000000..a5672aebd Binary files /dev/null and b/tests/Images/Input/Bmp/pal4rle.bmp differ diff --git a/tests/Images/Input/Bmp/pal4rlecut.bmp b/tests/Images/Input/Bmp/pal4rlecut.bmp new file mode 100644 index 000000000..2f32d1d7a Binary files /dev/null and b/tests/Images/Input/Bmp/pal4rlecut.bmp differ diff --git a/tests/Images/Input/Bmp/pal4rletrns.bmp b/tests/Images/Input/Bmp/pal4rletrns.bmp new file mode 100644 index 000000000..58994e92b Binary files /dev/null and b/tests/Images/Input/Bmp/pal4rletrns.bmp differ diff --git a/tests/Images/Input/Bmp/pal8gs.bmp b/tests/Images/Input/Bmp/pal8gs.bmp new file mode 100644 index 000000000..66a0d70dc Binary files /dev/null and b/tests/Images/Input/Bmp/pal8gs.bmp differ diff --git a/tests/Images/Input/Bmp/pal8offs.bmp b/tests/Images/Input/Bmp/pal8offs.bmp new file mode 100644 index 000000000..8673e9740 Binary files /dev/null and b/tests/Images/Input/Bmp/pal8offs.bmp differ diff --git a/tests/Images/Input/Bmp/pal8os2sp.bmp b/tests/Images/Input/Bmp/pal8os2sp.bmp new file mode 100644 index 000000000..e532c8986 Binary files /dev/null and b/tests/Images/Input/Bmp/pal8os2sp.bmp differ diff --git a/tests/Images/Input/Bmp/pal8os2v2.bmp b/tests/Images/Input/Bmp/pal8os2v2.bmp new file mode 100644 index 000000000..1324a40d0 Binary files /dev/null and b/tests/Images/Input/Bmp/pal8os2v2.bmp differ diff --git a/tests/Images/Input/Bmp/pal8oversizepal.bmp b/tests/Images/Input/Bmp/pal8oversizepal.bmp new file mode 100644 index 000000000..93b8187ca Binary files /dev/null and b/tests/Images/Input/Bmp/pal8oversizepal.bmp differ diff --git a/tests/Images/Input/Bmp/pal8rlecut.bmp b/tests/Images/Input/Bmp/pal8rlecut.bmp new file mode 100644 index 000000000..840d31cce Binary files /dev/null and b/tests/Images/Input/Bmp/pal8rlecut.bmp differ diff --git a/tests/Images/Input/Bmp/pal8rletrns.bmp b/tests/Images/Input/Bmp/pal8rletrns.bmp new file mode 100644 index 000000000..a2af88d87 Binary files /dev/null and b/tests/Images/Input/Bmp/pal8rletrns.bmp differ diff --git a/tests/Images/Input/Bmp/pal8v4.bmp b/tests/Images/Input/Bmp/pal8v4.bmp new file mode 100644 index 000000000..34ebb8030 Binary files /dev/null and b/tests/Images/Input/Bmp/pal8v4.bmp differ diff --git a/tests/Images/Input/Bmp/pal8v5.bmp b/tests/Images/Input/Bmp/pal8v5.bmp new file mode 100644 index 000000000..c54647a31 Binary files /dev/null and b/tests/Images/Input/Bmp/pal8v5.bmp differ diff --git a/tests/Images/Input/Bmp/rgb16-565.bmp b/tests/Images/Input/Bmp/rgb16-565.bmp new file mode 100644 index 000000000..c03a27975 Binary files /dev/null and b/tests/Images/Input/Bmp/rgb16-565.bmp differ diff --git a/tests/Images/Input/Bmp/rgb16-565pal.bmp b/tests/Images/Input/Bmp/rgb16-565pal.bmp new file mode 100644 index 000000000..e7632e344 Binary files /dev/null and b/tests/Images/Input/Bmp/rgb16-565pal.bmp differ diff --git a/tests/Images/Input/Bmp/rgb16.bmp b/tests/Images/Input/Bmp/rgb16.bmp new file mode 100644 index 000000000..6bfe47af4 Binary files /dev/null and b/tests/Images/Input/Bmp/rgb16.bmp differ diff --git a/tests/Images/Input/Bmp/rgb16bfdef.bmp b/tests/Images/Input/Bmp/rgb16bfdef.bmp new file mode 100644 index 000000000..30fe8bb8d Binary files /dev/null and b/tests/Images/Input/Bmp/rgb16bfdef.bmp differ diff --git a/tests/Images/Input/Bmp/rgb24.bmp b/tests/Images/Input/Bmp/rgb24.bmp new file mode 100644 index 000000000..40f8bb094 Binary files /dev/null and b/tests/Images/Input/Bmp/rgb24.bmp differ diff --git a/tests/Images/Input/Bmp/rgb24jpeg.bmp b/tests/Images/Input/Bmp/rgb24jpeg.bmp new file mode 100644 index 000000000..87d73d75b Binary files /dev/null and b/tests/Images/Input/Bmp/rgb24jpeg.bmp differ diff --git a/tests/Images/Input/Bmp/rgb24largepal.bmp b/tests/Images/Input/Bmp/rgb24largepal.bmp new file mode 100644 index 000000000..d5e418c2d Binary files /dev/null and b/tests/Images/Input/Bmp/rgb24largepal.bmp differ diff --git a/tests/Images/Input/Bmp/rgb24png.bmp b/tests/Images/Input/Bmp/rgb24png.bmp new file mode 100644 index 000000000..e87ec7add Binary files /dev/null and b/tests/Images/Input/Bmp/rgb24png.bmp differ diff --git a/tests/Images/Input/Bmp/rgb24rle24.bmp b/tests/Images/Input/Bmp/rgb24rle24.bmp new file mode 100644 index 000000000..360aee649 Binary files /dev/null and b/tests/Images/Input/Bmp/rgb24rle24.bmp differ diff --git a/tests/Images/Input/Bmp/rgb32bf.bmp b/tests/Images/Input/Bmp/rgb32bf.bmp new file mode 100644 index 000000000..20fa9a132 Binary files /dev/null and b/tests/Images/Input/Bmp/rgb32bf.bmp differ diff --git a/tests/Images/Input/Bmp/rgb32bfdef.bmp b/tests/Images/Input/Bmp/rgb32bfdef.bmp new file mode 100644 index 000000000..d7e64e5a4 Binary files /dev/null and b/tests/Images/Input/Bmp/rgb32bfdef.bmp differ diff --git a/tests/Images/Input/Bmp/rgb32h52.bmp b/tests/Images/Input/Bmp/rgb32h52.bmp new file mode 100644 index 000000000..db6e4538e Binary files /dev/null and b/tests/Images/Input/Bmp/rgb32h52.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32-1010102.bmp b/tests/Images/Input/Bmp/rgba32-1010102.bmp new file mode 100644 index 000000000..1a918cebf Binary files /dev/null and b/tests/Images/Input/Bmp/rgba32-1010102.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32.bmp b/tests/Images/Input/Bmp/rgba32.bmp new file mode 100644 index 000000000..829c7c7e3 Binary files /dev/null and b/tests/Images/Input/Bmp/rgba32.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32abf.bmp b/tests/Images/Input/Bmp/rgba32abf.bmp new file mode 100644 index 000000000..d9bb0189c Binary files /dev/null and b/tests/Images/Input/Bmp/rgba32abf.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32h56.bmp b/tests/Images/Input/Bmp/rgba32h56.bmp new file mode 100644 index 000000000..343baa330 Binary files /dev/null and b/tests/Images/Input/Bmp/rgba32h56.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32v4.bmp b/tests/Images/Input/Bmp/rgba32v4.bmp new file mode 100644 index 000000000..9d644a8b1 Binary files /dev/null and b/tests/Images/Input/Bmp/rgba32v4.bmp differ diff --git a/tests/Images/Input/Bmp/rle24rlecut.bmp b/tests/Images/Input/Bmp/rle24rlecut.bmp new file mode 100644 index 000000000..8f58c2117 Binary files /dev/null and b/tests/Images/Input/Bmp/rle24rlecut.bmp differ diff --git a/tests/Images/Input/Bmp/rle24rletrns.bmp b/tests/Images/Input/Bmp/rle24rletrns.bmp new file mode 100644 index 000000000..baa86167f Binary files /dev/null and b/tests/Images/Input/Bmp/rle24rletrns.bmp differ 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 000000000..78a092787 Binary files /dev/null and b/tests/Images/Input/Bmp/rle4-delta-320x240.bmp differ 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 000000000..ca8736f3a Binary files /dev/null and b/tests/Images/Input/Bmp/rle8-blank-160x120.bmp differ 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 000000000..8007c55d1 Binary files /dev/null and b/tests/Images/Input/Bmp/rle8-delta-320x240.bmp differ diff --git a/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg b/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg new file mode 100644 index 000000000..c1b13352a Binary files /dev/null and b/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/testorig12.jpg b/tests/Images/Input/Jpg/baseline/testorig12.jpg new file mode 100644 index 000000000..861aff98e Binary files /dev/null and b/tests/Images/Input/Jpg/baseline/testorig12.jpg differ 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 000000000..7e112d69c Binary files /dev/null and b/tests/Images/Input/Jpg/baseline/ycck-subsample-1222.jpg differ 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 000000000..c796224f9 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue845-Incorrect-Quality99.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg new file mode 100644 index 000000000..560d77d47 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg new file mode 100644 index 000000000..30f61c863 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg new file mode 100644 index 000000000..8ace30e1f Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg new file mode 100644 index 000000000..4378f429e Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg new file mode 100644 index 000000000..e18bbe231 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg new file mode 100644 index 000000000..49e4d04e7 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg new file mode 100644 index 000000000..ac2e882a4 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg new file mode 100644 index 000000000..69b7c030a Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg new file mode 100644 index 000000000..34774a479 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg new file mode 100644 index 000000000..b6e7ed11e Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg new file mode 100644 index 000000000..bdc8c356a Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg new file mode 100644 index 000000000..bf691f3a8 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg new file mode 100644 index 000000000..54bdc2940 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg new file mode 100644 index 000000000..a47a0057d Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg new file mode 100644 index 000000000..ffc801787 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg new file mode 100644 index 000000000..dfd42e6f5 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg new file mode 100644 index 000000000..58e96a2b1 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg new file mode 100644 index 000000000..aed67b286 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg new file mode 100644 index 000000000..e320cf62d Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg new file mode 100644 index 000000000..54ccb95c3 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg new file mode 100644 index 000000000..5d770c3b4 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg new file mode 100644 index 000000000..0e284349e Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg new file mode 100644 index 000000000..69f190629 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg differ 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 000000000..54db7982c Binary files /dev/null and b/tests/Images/Input/Jpg/issues/issue855-incorrect-colorspace.jpg differ diff --git a/tests/Images/Input/Png/iftbbn0g01.png b/tests/Images/Input/Png/iftbbn0g01.png new file mode 100644 index 000000000..6eb27d10e Binary files /dev/null and b/tests/Images/Input/Png/iftbbn0g01.png differ diff --git a/tests/Images/Input/Png/iftbbn0g02.png b/tests/Images/Input/Png/iftbbn0g02.png new file mode 100644 index 000000000..46ba49777 Binary files /dev/null and b/tests/Images/Input/Png/iftbbn0g02.png differ diff --git a/tests/Images/Input/Png/iftbbn0g04.png b/tests/Images/Input/Png/iftbbn0g04.png new file mode 100644 index 000000000..e9db0ad50 Binary files /dev/null and b/tests/Images/Input/Png/iftbbn0g04.png differ diff --git a/tests/Images/Input/Png/low-variance.png b/tests/Images/Input/Png/low-variance.png new file mode 100644 index 000000000..5b6c19bac Binary files /dev/null and b/tests/Images/Input/Png/low-variance.png differ diff --git a/tests/Images/Input/Png/rollsroyce.png b/tests/Images/Input/Png/rollsroyce.png new file mode 100644 index 000000000..7067372a2 Binary files /dev/null and b/tests/Images/Input/Png/rollsroyce.png differ diff --git a/tests/Images/Input/Png/zlib-overflow.png b/tests/Images/Input/Png/zlib-overflow.png new file mode 100644 index 000000000..979e94274 Binary files /dev/null and b/tests/Images/Input/Png/zlib-overflow.png differ diff --git a/tests/Images/Input/Png/zlib-overflow2.png b/tests/Images/Input/Png/zlib-overflow2.png new file mode 100644 index 000000000..12da8ee60 Binary files /dev/null and b/tests/Images/Input/Png/zlib-overflow2.png differ