diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..128265ff6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "command": "dotnet", + "isShellCommand": true, + "args": [], + "tasks": [ + { + "taskName": "build", + "args": [ "src/*/project.json", "-f", "netstandard1.1" ], + "isBuildCommand": true, + "showOutput": "always", + "problemMatcher": "$msCompile" + }, + { + "taskName": "build benchmark", + "suppressTaskName": true, + "args": [ "build", "tests/ImageSharp.Benchmarks/project.json", "-f", "netcoreapp1.1", "-c", "Release" ], + "showOutput": "always", + "problemMatcher": "$msCompile" + }, + { + "taskName": "test", + "args": ["tests/ImageSharp.Tests/project.json", "-f", "netcoreapp1.1"], + "isTestCommand": true, + "showOutput": "always", + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/ImageSharp.ruleset b/ImageSharp.ruleset index 554dc16dd..2daf6243a 100644 --- a/ImageSharp.ruleset +++ b/ImageSharp.ruleset @@ -1,6 +1,7 @@  + diff --git a/ImageSharp.sln b/ImageSharp.sln index 97021cffe..dec5cbc36 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26127.0 +VisualStudioVersion = 15.0.26228.4 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" ProjectSection(SolutionItems) = preProject @@ -55,15 +55,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Sandbox46", "tes {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F} = {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F} {7213767C-0003-41CA-AB18-0223CFA7CE4B} = {7213767C-0003-41CA-AB18-0223CFA7CE4B} + {E5BD4F96-28A8-410C-8B63-1C5731948549} = {E5BD4F96-28A8-410C-8B63-1C5731948549} {C77661B9-F793-422E-8E27-AC60ECC5F215} = {C77661B9-F793-422E-8E27-AC60ECC5F215} {556ABDCF-ED93-4327-BE98-F6815F78B9B8} = {556ABDCF-ED93-4327-BE98-F6815F78B9B8} {A623CFE9-9D2B-4528-AD1F-2E834B061134} = {A623CFE9-9D2B-4528-AD1F-2E834B061134} EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Drawing.Paths", "src\ImageSharp.Drawing.Paths\ImageSharp.Drawing.Paths.csproj", "{E5BD4F96-28A8-410C-8B63-1C5731948549}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Drawing.Text", "src\ImageSharp.Drawing.Text\ImageSharp.Drawing.Text.csproj", "{329D7698-65BC-48AD-A16F-428682964493}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,100 +81,100 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.ActiveCfg = Debug|x64 - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.Build.0 = Debug|x64 - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.ActiveCfg = Debug|x86 - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.Build.0 = Debug|x86 + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.ActiveCfg = Debug|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.Build.0 = Debug|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.ActiveCfg = Debug|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.Build.0 = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.ActiveCfg = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.Build.0 = Release|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.ActiveCfg = Release|x64 - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|x64 - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|x86 - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|x86 + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.ActiveCfg = Release|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.ActiveCfg = Debug|x64 - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.Build.0 = Debug|x64 - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.ActiveCfg = Debug|x86 - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.Build.0 = Debug|x86 + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.Build.0 = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.ActiveCfg = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.Build.0 = Debug|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.Build.0 = Release|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.ActiveCfg = Release|x64 - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.Build.0 = Release|x64 - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.ActiveCfg = Release|x86 - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.Build.0 = Release|x86 + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.ActiveCfg = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.Build.0 = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.ActiveCfg = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.Build.0 = Release|Any CPU {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|Any CPU.Build.0 = Debug|Any CPU - {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|x64.ActiveCfg = Debug|x64 - {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|x64.Build.0 = Debug|x64 - {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|x86.ActiveCfg = Debug|x86 - {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|x86.Build.0 = Debug|x86 + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|x64.ActiveCfg = Debug|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|x64.Build.0 = Debug|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|x86.ActiveCfg = Debug|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|x86.Build.0 = Debug|Any CPU {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|Any CPU.ActiveCfg = Release|Any CPU {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|Any CPU.Build.0 = Release|Any CPU - {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|x64.ActiveCfg = Release|x64 - {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|x64.Build.0 = Release|x64 - {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|x86.ActiveCfg = Release|x86 - {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|x86.Build.0 = Release|x86 + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|x64.ActiveCfg = Release|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|x64.Build.0 = Release|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|x86.ActiveCfg = Release|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|x86.Build.0 = Release|Any CPU {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|x64.ActiveCfg = Debug|x64 - {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|x64.Build.0 = Debug|x64 - {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|x86.ActiveCfg = Debug|x86 - {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|x86.Build.0 = Debug|x86 + {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|x64.ActiveCfg = Debug|Any CPU + {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|x64.Build.0 = Debug|Any CPU + {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|x86.ActiveCfg = Debug|Any CPU + {C77661B9-F793-422E-8E27-AC60ECC5F215}.Debug|x86.Build.0 = Debug|Any CPU {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|Any CPU.ActiveCfg = Release|Any CPU {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|Any CPU.Build.0 = Release|Any CPU - {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|x64.ActiveCfg = Release|x64 - {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|x64.Build.0 = Release|x64 - {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|x86.ActiveCfg = Release|x86 - {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|x86.Build.0 = Release|x86 + {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|x64.ActiveCfg = Release|Any CPU + {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|x64.Build.0 = Release|Any CPU + {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|x86.ActiveCfg = Release|Any CPU + {C77661B9-F793-422E-8E27-AC60ECC5F215}.Release|x86.Build.0 = Release|Any CPU {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|x64.ActiveCfg = Debug|x64 - {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|x64.Build.0 = Debug|x64 - {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|x86.ActiveCfg = Debug|x86 - {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|x86.Build.0 = Debug|x86 + {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|x64.ActiveCfg = Debug|Any CPU + {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|x64.Build.0 = Debug|Any CPU + {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|x86.ActiveCfg = Debug|Any CPU + {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Debug|x86.Build.0 = Debug|Any CPU {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|Any CPU.ActiveCfg = Release|Any CPU {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|Any CPU.Build.0 = Release|Any CPU - {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|x64.ActiveCfg = Release|x64 - {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|x64.Build.0 = Release|x64 - {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|x86.ActiveCfg = Release|x86 - {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|x86.Build.0 = Release|x86 + {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|x64.ActiveCfg = Release|Any CPU + {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|x64.Build.0 = Release|Any CPU + {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|x86.ActiveCfg = Release|Any CPU + {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F}.Release|x86.Build.0 = Release|Any CPU {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|x64.ActiveCfg = Debug|x64 - {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|x64.Build.0 = Debug|x64 - {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|x86.ActiveCfg = Debug|x86 - {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|x86.Build.0 = Debug|x86 + {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|x64.ActiveCfg = Debug|Any CPU + {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|x64.Build.0 = Debug|Any CPU + {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|x86.ActiveCfg = Debug|Any CPU + {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Debug|x86.Build.0 = Debug|Any CPU {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|Any CPU.Build.0 = Release|Any CPU - {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|x64.ActiveCfg = Release|x64 - {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|x64.Build.0 = Release|x64 - {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|x86.ActiveCfg = Release|x86 - {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|x86.Build.0 = Release|x86 + {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|x64.ActiveCfg = Release|Any CPU + {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|x64.Build.0 = Release|Any CPU + {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|x86.ActiveCfg = Release|Any CPU + {7213767C-0003-41CA-AB18-0223CFA7CE4B}.Release|x86.Build.0 = Release|Any CPU {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|x64.ActiveCfg = Debug|x64 - {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|x64.Build.0 = Debug|x64 - {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|x86.ActiveCfg = Debug|x86 - {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|x86.Build.0 = Debug|x86 + {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|x64.ActiveCfg = Debug|Any CPU + {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|x64.Build.0 = Debug|Any CPU + {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|x86.ActiveCfg = Debug|Any CPU + {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Debug|x86.Build.0 = Debug|Any CPU {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|Any CPU.Build.0 = Release|Any CPU - {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|x64.ActiveCfg = Release|x64 - {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|x64.Build.0 = Release|x64 - {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|x86.ActiveCfg = Release|x86 - {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|x86.Build.0 = Release|x86 + {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|x64.ActiveCfg = Release|Any CPU + {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|x64.Build.0 = Release|Any CPU + {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|x86.ActiveCfg = Release|Any CPU + {556ABDCF-ED93-4327-BE98-F6815F78B9B8}.Release|x86.Build.0 = Release|Any CPU {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|x64.ActiveCfg = Debug|x64 - {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|x64.Build.0 = Debug|x64 - {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|x86.ActiveCfg = Debug|x86 - {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|x86.Build.0 = Debug|x86 + {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|x64.ActiveCfg = Debug|Any CPU + {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|x64.Build.0 = Debug|Any CPU + {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|x86.ActiveCfg = Debug|Any CPU + {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Debug|x86.Build.0 = Debug|Any CPU {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|Any CPU.ActiveCfg = Release|Any CPU {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|Any CPU.Build.0 = Release|Any CPU - {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|x64.ActiveCfg = Release|x64 - {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|x64.Build.0 = Release|x64 - {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|x86.ActiveCfg = Release|x86 - {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|x86.Build.0 = Release|x86 + {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|x64.ActiveCfg = Release|Any CPU + {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|x64.Build.0 = Release|Any CPU + {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|x86.ActiveCfg = Release|Any CPU + {A623CFE9-9D2B-4528-AD1F-2E834B061134}.Release|x86.Build.0 = Release|Any CPU {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -184,28 +189,52 @@ Global {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|x86.Build.0 = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|x64 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|x64 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.ActiveCfg = Debug|x86 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.Build.0 = Debug|x86 + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.ActiveCfg = Release|x64 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|x64 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.ActiveCfg = Release|x86 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.Build.0 = Release|x86 + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.ActiveCfg = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.ActiveCfg = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.ActiveCfg = Debug|x64 - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|x64 - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.ActiveCfg = Debug|x86 - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.Build.0 = Debug|x86 + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.ActiveCfg = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.ActiveCfg = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.Build.0 = Release|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.ActiveCfg = Release|x64 - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|x64 - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|x86 - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|x86 + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.ActiveCfg = Release|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|x64.ActiveCfg = Debug|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|x64.Build.0 = Debug|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|x86.ActiveCfg = Debug|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|x86.Build.0 = Debug|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|Any CPU.Build.0 = Release|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|x64.ActiveCfg = Release|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|x64.Build.0 = Release|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|x86.ActiveCfg = Release|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|x86.Build.0 = Release|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Debug|Any CPU.Build.0 = Debug|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Debug|x64.ActiveCfg = Debug|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Debug|x64.Build.0 = Debug|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Debug|x86.ActiveCfg = Debug|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Debug|x86.Build.0 = Debug|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Release|Any CPU.ActiveCfg = Release|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Release|Any CPU.Build.0 = Release|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Release|x64.ActiveCfg = Release|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Release|x64.Build.0 = Release|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Release|x86.ActiveCfg = Release|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -223,5 +252,7 @@ Global {96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {E5BD4F96-28A8-410C-8B63-1C5731948549} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} + {329D7698-65BC-48AD-A16F-428682964493} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 4d499fa35..9d5c9788a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ImageSharp +# ImageSharp **ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. @@ -45,7 +45,9 @@ Packages include: Contains methods like Resize, Crop, Skew, Rotate - Anything that alters the dimensions of the image. Contains methods like Gaussian Blur, Pixelate, Edge Detection - Anything that maintains the original image dimensions. - **ImageSharp.Drawing** - Brushes and various drawing algorithms. + Brushes and various drawing algorithms, including drawing Images + - **ImageSharp.Drawing.Paths** + Various vector drawing methods for drawing paths, polygons etc. ### Manual build @@ -54,6 +56,11 @@ If you prefer, you can compile ImageSharp yourself (please do and help!), you'll - [Visual Studio 2015 with Update 3 (or above)](https://www.visualstudio.com/news/releasenotes/vs2015-update3-vs) - The [.NET Core 1.0 SDK Installer](https://www.microsoft.com/net/core#windows) - Non VSCode link. +Alternatively on Linux you can use: + +- [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) +- [.Net Core 1.1](https://www.microsoft.com/net/core#linuxubuntu) + To clone it locally click the "Clone in Windows" button above or run the following git commands. ```bash @@ -72,11 +79,21 @@ Many `Image` methods are also fluent. Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix. +On platforms supporting netstandard 1.3+ +```csharp +using (Image image = new Image("foo.jpg")) +{ + image.Resize(image.Width / 2, image.Height / 2) + .Grayscale() + .Save("bar.jpg"); // automatic encoder selected based on extension. +} +``` +on netstandard 1.1 - 1.2 ```csharp using (FileStream stream = File.OpenRead("foo.jpg")) using (FileStream output = File.OpenWrite("bar.jpg")) +using (Image image = new Image(stream)) { - Image image = new Image(stream); image.Resize(image.Width / 2, image.Height / 2) .Grayscale() .Save(output); @@ -92,7 +109,7 @@ new BrightnessProcessor(50).Apply(sourceImage, sourceImage.Bounds); Setting individual pixel values is perfomed as follows: ```csharp -Image image = new Image(400, 400); +using (image = new Image(400, 400) using (var pixels = image.Lock()) { pixels[200, 200] = Color.White; diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png index 81e29fee5..d9ae997ba 100644 --- a/build/icons/imagesharp-logo-128.png +++ b/build/icons/imagesharp-logo-128.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01f79400a4a77c764273a97fbc76982546e88510fa4cc64a7b2e83e265b0e141 -size 2490 +oid sha256:47f14bb7d24f7228cd8833d8d1881a72750b2c7813f391bd2a0dd0eeea936841 +size 6569 diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png index 5b78542cb..f1e67dd78 100644 --- a/build/icons/imagesharp-logo-256.png +++ b/build/icons/imagesharp-logo-256.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5598d4cb5bad33aefc1084c4f389b071143a59505f00f7b7831c81254f1140f8 -size 4225 +oid sha256:757ec2f45cc5f9c2083fc65a236100f1a7776eee16bd1095a550e05783106a9f +size 13949 diff --git a/build/icons/imagesharp-logo-32.png b/build/icons/imagesharp-logo-32.png index 31e32300d..80435989a 100644 --- a/build/icons/imagesharp-logo-32.png +++ b/build/icons/imagesharp-logo-32.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:536e75abeaa2c35f34a95d34bee4f8bd13cf5514a979960566945da4ec8827d1 -size 979 +oid sha256:0f3a5375ce20321c2cfdc888a21dcb629d3e6a85641df5cca7c66e5b2a5f70f6 +size 1439 diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png index ee02fb650..a5f880e3a 100644 --- a/build/icons/imagesharp-logo-512.png +++ b/build/icons/imagesharp-logo-512.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8288a69f4182b25e04ba6419b452c6cdbca0013856352c0aeb08abc67f67e4d -size 7951 +oid sha256:0e4cd18406375999c2bee1c39ad439b37f9524485d6e247ab0f14d2eb90a65f3 +size 31256 diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png index 9919f97d6..f59e202bf 100644 --- a/build/icons/imagesharp-logo-64.png +++ b/build/icons/imagesharp-logo-64.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ab6d98a7a55caf570016a62a2f4d72842e22cbe03460ad60a4dc196b7b1d303 -size 1698 +oid sha256:fa25e5dbe84f942107a1c29f4f68ff2a73f497412ae91b6e60fc5464bc9b5f05 +size 3132 diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png new file mode 100644 index 000000000..20779215f --- /dev/null +++ b/build/icons/imagesharp-logo-heading.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf2335642c6fd291befa0b203dbfb3387d99434369399b35aeea037c0f9eba45 +size 10474 diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png index 0f80ceeaa..e0f1854cc 100644 --- a/build/icons/imagesharp-logo.png +++ b/build/icons/imagesharp-logo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f20a3b2613811efa3455ee65b532284d88636d8796ee2279c08a584aa01744e -size 19129 +oid sha256:e4217fe820af06a593903441f0719cab1ca650fd4de795f0e6808c4240a89819 +size 59646 diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg index 9638e9785..2df3cc80c 100644 --- a/build/icons/imagesharp-logo.svg +++ b/build/icons/imagesharp-logo.svg @@ -1,59 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/dotnet-latest.ps1 b/dotnet-latest.ps1 index 32c53d7e1..2a77d2ec9 100644 --- a/dotnet-latest.ps1 +++ b/dotnet-latest.ps1 @@ -17,8 +17,8 @@ if (Get-Command "dotnet.exe" -ErrorAction SilentlyContinue) { Write-Host "dotnet SDK already installed" $version = dotnet --version 2>&1 - if($version -ne "1.0.0-rc3-004530"){ - Write-Host "$version installed but require 1.0.0-rc3-004530" + if($version -ne "1.0.1"){ + Write-Host "$version installed but require 1.0.1" $installRequired = $TRUE }else{ Write-Host "$version already installed" @@ -34,8 +34,8 @@ if($installRequired -eq $TRUE) Write-Host $installScript - Invoke-WebRequest "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0-rc3/scripts/obtain/dotnet-install.ps1" ` + Invoke-WebRequest "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.1/scripts/obtain/dotnet-install.ps1" ` -OutFile $installScript & $installScript -} +} \ No newline at end of file diff --git a/features.md b/features.md index 764d7c4a6..6bc5630ee 100644 --- a/features.md +++ b/features.md @@ -10,13 +10,23 @@ We've achieved a lot so far and hope to do a lot more in the future. We're alway - [x] Bmp (Read: 32bit, 24bit, 16 bit. Write: 32bit, 24bit just now) - [x] Png (Read: Rgb, Rgba, Grayscale, Grayscale + alpha, Palette. Write: Rgb, Rgba, Grayscale, Grayscale + alpha, Palette) Supports interlaced decoding - [x] Gif (Includes animated) - - [ ] Tiff + - [ ] Tiff (Help needed) - **Metadata** - [x] EXIF Read/Write (Jpeg just now) -- **Quantizers (IQuantizer with alpha channel support + thresholding)** +- **Quantizers (IQuantizer with alpha channel support, dithering, and thresholding)** - [x] Octree - [x] Xiaolin Wu - [x] Palette +- **DIthering (Error diffusion and Ordered)** + - [x] Atkinson + - [x] Burks + - [x] FloydSteinburg + - [x] JarvisJudiceNinke + - [x] Sieera2 + - [x] Sierra3 + - [x] SerraLite + - [x] Bayer + - [x] Ordered - **Basic color structs with implicit operators.** - [x] Color - 32bit color in RGBA order (IPackedPixel\). - [x] Bgra32 @@ -133,5 +143,5 @@ We've achieved a lot so far and hope to do a lot more in the future. We're alway - [x] DrawImage - [ ] Gradient brush (Need help) - **DrawingText** - - [x] DrawString (Single variant support just now, no italic,bold) + - [ ] DrawString (In-progress. Single variant support just now, no italic,bold) - Other stuff I haven't thought of. \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Paths/DrawBeziers.cs b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs new file mode 100644 index 000000000..936d5a9ce --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// 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 options. + /// The . + public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(new Pen(brush, thickness), new Path(new BezierLineSegment(points)), options); + } + + /// + /// 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 Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points) + where TColor : struct, IPixel + { + return source.Draw(new Pen(brush, thickness), new Path(new BezierLineSegment(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 Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points) + where TColor : struct, IPixel + { + return 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 color. + /// The thickness. + /// The points. + /// The options. + /// The . + public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawBeziers(new SolidBrush(color), thickness, points, options); + } + + /// + /// 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 options. + /// The . + public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(pen, new Path(new BezierLineSegment(points)), options); + } + + /// + /// 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 Image DrawBeziers(this Image source, IPen pen, Vector2[] points) + where TColor : struct, IPixel + { + return source.Draw(pen, new Path(new BezierLineSegment(points))); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/DrawLines.cs b/src/ImageSharp.Drawing.Paths/DrawLines.cs new file mode 100644 index 000000000..42f4406e8 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawLines.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// 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 options. + /// The . + public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points)), options); + } + + /// + /// 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 Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points) + where TColor : struct, IPixel + { + return 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 Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points) + where TColor : struct, IPixel + { + return 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 color. + /// The thickness. + /// The points. + /// The options. + /// The .> + public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawLines(new SolidBrush(color), thickness, points, options); + } + + /// + /// 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 options. + /// The . + public static Image DrawLines(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(pen, new Path(new LinearLineSegment(points)), options); + } + + /// + /// 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 Image DrawLines(this Image source, IPen pen, Vector2[] points) + where TColor : struct, IPixel + { + return source.Draw(pen, new Path(new LinearLineSegment(points))); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/DrawPath.cs b/src/ImageSharp.Drawing.Paths/DrawPath.cs new file mode 100644 index 000000000..e2c1442de --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawPath.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// 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 options. + /// The . + public static Image Draw(this Image source, IPen pen, IPath path, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(pen, new ShapePath(path), options); + } + + /// + /// 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 Image Draw(this Image source, IPen pen, IPath path) + where TColor : struct, IPixel + { + return source.Draw(pen, path, GraphicsOptions.Default); + } + + /// + /// 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 shape. + /// The options. + /// The . + public static Image Draw(this Image source, IBrush brush, float thickness, IPath path, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(new Pen(brush, thickness), path, options); + } + + /// + /// 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 Image Draw(this Image source, IBrush brush, float thickness, IPath path) + where TColor : struct, IPixel + { + return 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 color. + /// The thickness. + /// The path. + /// The options. + /// The . + public static Image Draw(this Image source, TColor color, float thickness, IPath path, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(new SolidBrush(color), thickness, path, options); + } + + /// + /// 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 Image Draw(this Image source, TColor color, float thickness, IPath path) + where TColor : struct, IPixel + { + return source.Draw(new SolidBrush(color), thickness, path); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs new file mode 100644 index 000000000..8043d18e5 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// 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 options. + /// The . + public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points)), options); + } + + /// + /// 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 Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points) + where TColor : struct, IPixel + { + return 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 Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points) + where TColor : struct, IPixel + { + return 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 color. + /// The thickness. + /// The points. + /// The options. + /// The . + public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawPolygon(new SolidBrush(color), thickness, points, options); + } + + /// + /// 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 Image DrawPolygon(this Image source, IPen pen, Vector2[] points) + where TColor : struct, IPixel + { + return source.Draw(pen, new Polygon(new LinearLineSegment(points)), GraphicsOptions.Default); + } + + /// + /// 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 options. + /// The . + public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(pen, new Polygon(new LinearLineSegment(points)), options); + } + } +} diff --git a/src/ImageSharp.Drawing/DrawRectangle.cs b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs similarity index 53% rename from src/ImageSharp.Drawing/DrawRectangle.cs rename to src/ImageSharp.Drawing.Paths/DrawRectangle.cs index 38ed578b6..b35665240 100644 --- a/src/ImageSharp.Drawing/DrawRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs @@ -9,10 +9,7 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; - using Drawing.Paths; using Drawing.Pens; - using Drawing.Processors; - using Drawing.Shapes; /// /// Extension methods for the type. @@ -23,97 +20,91 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The shape. /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Draw(this Image source, IPen pen, Rectangle shape, GraphicsOptions options) + where TColor : struct, IPixel { - return source.Apply(new DrawPathProcessor(pen, (IPath)new RectangularPolygon(shape), options)); + return source.Draw(pen, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options); } /// /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Draw(this Image source, IPen pen, Rectangle shape) + where TColor : struct, IPixel { - return source.DrawPolygon(pen, shape, GraphicsOptions.Default); + return source.Draw(pen, shape, GraphicsOptions.Default); } /// /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The shape. /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, RectangleF shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Draw(this Image source, IBrush brush, float thickness, Rectangle shape, GraphicsOptions options) + where TColor : struct, IPixel { - return source.DrawPolygon(new Pen(brush, thickness), shape, options); + return source.Draw(new Pen(brush, thickness), shape, options); } /// /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, RectangleF shape) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Draw(this Image source, IBrush brush, float thickness, Rectangle shape) + where TColor : struct, IPixel { - return source.DrawPolygon(new Pen(brush, thickness), shape); + return source.Draw(new Pen(brush, thickness), shape); } /// /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The shape. /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, TColor color, float thickness, RectangleF shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Draw(this Image source, TColor color, float thickness, Rectangle shape, GraphicsOptions options) + where TColor : struct, IPixel { - return source.DrawPolygon(new SolidBrush(color), thickness, shape, options); + return source.Draw(new SolidBrush(color), thickness, shape, options); } /// /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, TColor color, float thickness, RectangleF shape) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Draw(this Image source, TColor color, float thickness, Rectangle shape) + where TColor : struct, IPixel { - return source.DrawPolygon(new SolidBrush(color), thickness, shape); + return source.Draw(new SolidBrush(color), thickness, shape); } } } diff --git a/src/ImageSharp.Drawing.Paths/FillPaths.cs b/src/ImageSharp.Drawing.Paths/FillPaths.cs new file mode 100644 index 000000000..92e227ce1 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/FillPaths.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + using Drawing; + using Drawing.Brushes; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// 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 shape. + /// The graphics options. + /// The . + public static Image Fill(this Image source, IBrush brush, IPath path, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Fill(brush, new ShapeRegion(path), options); + } + + /// + /// 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 Image Fill(this Image source, IBrush brush, IPath path) + where TColor : struct, IPixel + { + return source.Fill(brush, new ShapeRegion(path), GraphicsOptions.Default); + } + + /// + /// 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 options. + /// The . + public static Image Fill(this Image source, TColor color, IPath path, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Fill(new SolidBrush(color), path, options); + } + + /// + /// 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 Image Fill(this Image source, TColor color, IPath path) + where TColor : struct, IPixel + { + return source.Fill(new SolidBrush(color), path); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/FillPolygon.cs b/src/ImageSharp.Drawing.Paths/FillPolygon.cs new file mode 100644 index 000000000..cd3d15466 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/FillPolygon.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// 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 options. + /// The . + public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Fill(brush, new Polygon(new LinearLineSegment(points)), options); + } + + /// + /// 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 Image FillPolygon(this Image source, IBrush brush, Vector2[] points) + where TColor : struct, IPixel + { + return 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 color. + /// The points. + /// The options. + /// The . + public static Image FillPolygon(this Image source, TColor color, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points)), options); + } + + /// + /// 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 Image FillPolygon(this Image source, TColor color, Vector2[] points) + where TColor : struct, IPixel + { + return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points))); + } + } +} diff --git a/src/ImageSharp.Drawing/FillRectangle.cs b/src/ImageSharp.Drawing.Paths/FillRectangle.cs similarity index 65% rename from src/ImageSharp.Drawing/FillRectangle.cs rename to src/ImageSharp.Drawing.Paths/FillRectangle.cs index d29b58e1b..1928e54d3 100644 --- a/src/ImageSharp.Drawing/FillRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/FillRectangle.cs @@ -9,8 +9,6 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; - using Drawing.Processors; - using Drawing.Shapes; /// /// Extension methods for the type. @@ -21,46 +19,42 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The shape. /// The options. - /// - /// The Image - /// - public static Image Fill(this Image source, IBrush brush, RectangleF shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Fill(this Image source, IBrush brush, Rectangle shape, GraphicsOptions options) + where TColor : struct, IPixel { - return source.Apply(new FillShapeProcessor(brush, new RectangularPolygon(shape), options)); + return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options); } /// /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The shape. - /// The Image - public static Image Fill(this Image source, IBrush brush, RectangleF shape) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Fill(this Image source, IBrush brush, Rectangle shape) + where TColor : struct, IPixel { - return source.Apply(new FillShapeProcessor(brush, new RectangularPolygon(shape), GraphicsOptions.Default)); + return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height)); } /// /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The shape. /// The options. - /// - /// The Image - /// - public static Image Fill(this Image source, TColor color, RectangleF shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Fill(this Image source, TColor color, Rectangle shape, GraphicsOptions options) + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), shape, options); } @@ -69,12 +63,12 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The shape. - /// The Image - public static Image Fill(this Image source, TColor color, RectangleF shape) - where TColor : struct, IPackedPixel, IEquatable + /// The . + public static Image Fill(this Image source, TColor color, Rectangle shape) + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), shape); } diff --git a/src/ImageSharp.Drawing.Paths/ImageSharp.Drawing.Paths.csproj b/src/ImageSharp.Drawing.Paths/ImageSharp.Drawing.Paths.csproj new file mode 100644 index 000000000..7afccc882 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/ImageSharp.Drawing.Paths.csproj @@ -0,0 +1,84 @@ + + + + A cross-platform library for the processing of image files; written in C# + ImageSharp.Drawing.Paths + 1.0.0-alpha2 + James Jackson-South and contributors + netstandard1.1;net45;net461 + true + true + ImageSharp.Drawing.Paths + ImageSharp.Drawing.Paths + Image Resize Crop Gif Jpg Jpeg Bitmap Png Core + https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/build/icons/imagesharp-logo-128.png + https://github.com/JimBobSquarePants/ImageSharp + http://www.apache.org/licenses/LICENSE-2.0 + git + https://github.com/JimBobSquarePants/ImageSharp + false + false + false + false + false + false + false + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + diff --git a/src/ImageSharp.Drawing.Paths/Properties/AssemblyInfo.cs b/src/ImageSharp.Drawing.Paths/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..fba25a9db --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// Common values read from `AssemblyInfo.Common.cs` diff --git a/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs new file mode 100644 index 000000000..2fa5fe43f --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System; + + /// + /// Extension methods for helping to bridge Shaper2D and ImageSharp primitives. + /// + internal static class RectangleExtensions + { + /// + /// Converts a Shaper2D to an ImageSharp by creating a the entirely surrounds the source. + /// + /// The image this method extends. + /// A representation of this + public static Rectangle Convert(this SixLabors.Shapes.Rectangle source) + { + int left = (int)Math.Floor(source.Left); + int right = (int)Math.Ceiling(source.Right); + int top = (int)Math.Floor(source.Top); + int bottom = (int)Math.Ceiling(source.Bottom); + return new Rectangle(left, top, right - left, bottom - top); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Paths/ShapePath.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs new file mode 100644 index 000000000..d0376ccc1 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -0,0 +1,104 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Buffers; + using System.Collections.Immutable; + using System.Numerics; + + using SixLabors.Shapes; + + using Rectangle = ImageSharp.Rectangle; + + /// + /// A drawable mapping between a and a drawable region. + /// + internal class ShapePath : Drawable + { + /// + /// Initializes a new instance of the class. + /// + /// The path. + public ShapePath(IPath path) + { + this.Path = path; + this.Bounds = path.Bounds.Convert(); + } + + /// + /// Gets the fillable shape + /// + public IPath Path { get; } + + /// + public override int MaxIntersections => this.Path.MaxIntersections; + + /// + public override Rectangle Bounds { get; } + + /// + public override int ScanX(int x, float[] buffer, int length, int offset) + { + Vector2 start = new Vector2(x, this.Bounds.Top - 1); + Vector2 end = new Vector2(x, this.Bounds.Bottom + 1); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + try + { + int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0); + + for (int i = 0; i < count; i++) + { + buffer[i + offset] = innerbuffer[i].Y; + } + + return count; + } + finally + { + ArrayPool.Shared.Return(innerbuffer); + } + } + + /// + public override int ScanY(int y, float[] buffer, int length, int offset) + { + Vector2 start = new Vector2(this.Bounds.Left - 1, y); + Vector2 end = new Vector2(this.Bounds.Right + 1, y); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + try + { + int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0); + + for (int i = 0; i < count; i++) + { + buffer[i + offset] = innerbuffer[i].X; + } + + return count; + } + finally + { + ArrayPool.Shared.Return(innerbuffer); + } + } + + /// + public override PointInfo GetPointInfo(int x, int y) + { + Vector2 point = new Vector2(x, y); + SixLabors.Shapes.PointInfo dist = this.Path.Distance(point); + + return new PointInfo + { + DistanceAlongPath = dist.DistanceAlongPath, + DistanceFromPath = + dist.DistanceFromPath < 0 + ? -dist.DistanceFromPath + : dist.DistanceFromPath + }; + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs new file mode 100644 index 000000000..b02c5c2e5 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Buffers; + using System.Numerics; + + using SixLabors.Shapes; + + using Rectangle = ImageSharp.Rectangle; + + /// + /// A mapping between a and a region. + /// + internal class ShapeRegion : Region + { + /// + /// Initializes a new instance of the class. + /// + /// The shape. + public ShapeRegion(IPath shape) + { + this.Shape = shape.AsClosedPath(); + this.Bounds = shape.Bounds.Convert(); + } + + /// + /// Gets the fillable shape + /// + public IPath Shape { get; } + + /// + public override int MaxIntersections => this.Shape.MaxIntersections; + + /// + public override Rectangle Bounds { get; } + + /// + public override int ScanX(int x, float[] buffer, int length, int offset) + { + Vector2 start = new Vector2(x, this.Bounds.Top - 1); + Vector2 end = new Vector2(x, this.Bounds.Bottom + 1); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + try + { + int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0); + + for (int i = 0; i < count; i++) + { + buffer[i + offset] = innerbuffer[i].Y; + } + + return count; + } + finally + { + ArrayPool.Shared.Return(innerbuffer); + } + } + + /// + public override int ScanY(int y, float[] buffer, int length, int offset) + { + Vector2 start = new Vector2(this.Bounds.Left - 1, y); + Vector2 end = new Vector2(this.Bounds.Right + 1, y); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + try + { + int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0); + + for (int i = 0; i < count; i++) + { + buffer[i + offset] = innerbuffer[i].X; + } + + return count; + } + finally + { + ArrayPool.Shared.Return(innerbuffer); + } + } + } +} diff --git a/src/ImageSharp.Drawing.Text/DrawText.cs b/src/ImageSharp.Drawing.Text/DrawText.cs new file mode 100644 index 000000000..28781fab2 --- /dev/null +++ b/src/ImageSharp.Drawing.Text/DrawText.cs @@ -0,0 +1,203 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + + using SixLabors.Fonts; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, TColor color, Vector2 location) + where TColor : struct, IPixel + { + return source.DrawText(text, font, color, location, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The location. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, TColor color, Vector2 location, TextGraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawText(text, font, Brushes.Solid(color), null, location, options); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, Vector2 location) + where TColor : struct, IPixel + { + return source.DrawText(text, font, brush, location, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The location. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, Vector2 location, TextGraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawText(text, font, brush, null, location, options); + } + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IPen pen, Vector2 location) + where TColor : struct, IPixel + { + return source.DrawText(text, font, pen, location, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The location. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IPen pen, Vector2 location, TextGraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawText(text, font, null, pen, location, options); + } + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, Vector2 location) + where TColor : struct, IPixel + { + return source.DrawText(text, font, brush, pen, location, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The location. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, Vector2 location, TextGraphicsOptions options) + where TColor : struct, IPixel + { + GlyphBuilder glyphBuilder = new GlyphBuilder(location); + + TextRenderer renderer = new TextRenderer(glyphBuilder); + + Vector2 dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); + FontSpan style = new FontSpan(font) + { + ApplyKerning = options.ApplyKerning, + TabWidth = options.TabWidth + }; + + renderer.RenderText(text, style, dpi); + + System.Collections.Generic.IEnumerable shapesToDraw = glyphBuilder.Paths; + + GraphicsOptions pathOptions = (GraphicsOptions)options; + if (brush != null) + { + foreach (SixLabors.Shapes.IPath s in shapesToDraw) + { + source.Fill(brush, s, pathOptions); + } + } + + if (pen != null) + { + foreach (SixLabors.Shapes.IPath s in shapesToDraw) + { + source.Draw(pen, s, pathOptions); + } + } + + return source; + } + } +} diff --git a/src/ImageSharp.Drawing.Text/GlyphBuilder.cs b/src/ImageSharp.Drawing.Text/GlyphBuilder.cs new file mode 100644 index 000000000..ac5d01de7 --- /dev/null +++ b/src/ImageSharp.Drawing.Text/GlyphBuilder.cs @@ -0,0 +1,126 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Collections.Generic; + using System.Numerics; + + using SixLabors.Fonts; + using SixLabors.Shapes; + + /// + /// rendering surface that Fonts can use to generate Shapes. + /// + internal class GlyphBuilder : IGlyphRenderer + { + private readonly PathBuilder builder = new PathBuilder(); + private readonly List paths = new List(); + private Vector2 currentPoint = default(Vector2); + + /// + /// Initializes a new instance of the class. + /// + public GlyphBuilder() + : this(Vector2.Zero) + { + // glyphs are renderd realative to bottom left so invert the Y axis to allow it to render on top left origin surface + this.builder = new PathBuilder(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The origin. + public GlyphBuilder(Vector2 origin) + { + this.builder = new PathBuilder(); + this.builder.SetOrigin(origin); + } + + /// + /// Gets the paths that have been rendered by this. + /// + public IEnumerable Paths => this.paths; + + /// + /// Begins the glyph. + /// + void IGlyphRenderer.BeginGlyph() + { + this.builder.Clear(); + } + + /// + /// Begins the figure. + /// + void IGlyphRenderer.BeginFigure() + { + this.builder.StartFigure(); + } + + /// + /// Draws a cubic bezier from the current point to the + /// + /// The second control point. + /// The third control point. + /// The point. + void IGlyphRenderer.CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point) + { + this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); + this.currentPoint = point; + } + + /// + /// Ends the glyph. + /// + void IGlyphRenderer.EndGlyph() + { + this.paths.Add(this.builder.Build()); + } + + /// + /// Ends the figure. + /// + void IGlyphRenderer.EndFigure() + { + this.builder.CloseFigure(); + } + + /// + /// Draws a line from the current point to the . + /// + /// The point. + void IGlyphRenderer.LineTo(Vector2 point) + { + this.builder.AddLine(this.currentPoint, point); + this.currentPoint = point; + } + + /// + /// Moves to current point to the supplied vector. + /// + /// The point. + void IGlyphRenderer.MoveTo(Vector2 point) + { + this.builder.StartFigure(); + this.currentPoint = point; + } + + /// + /// Draws a quadratics bezier from the current point to the + /// + /// The second control point. + /// The point. + void IGlyphRenderer.QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point) + { + Vector2 c1 = (((secondControlPoint - this.currentPoint) * 2) / 3) + this.currentPoint; + Vector2 c2 = (((secondControlPoint - point) * 2) / 3) + point; + + this.builder.AddBezier(this.currentPoint, c1, c2, point); + this.currentPoint = point; + } + } +} diff --git a/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.csproj b/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.csproj new file mode 100644 index 000000000..f0e23c0c2 --- /dev/null +++ b/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.csproj @@ -0,0 +1,84 @@ + + + + A cross-platform library for the processing of image files; written in C# + ImageSharp.Drawing.Text + 1.0.0-alpha2 + James Jackson-South and contributors + netstandard1.1;net45;net461 + true + true + ImageSharp.Drawing.Text + ImageSharp.Drawing.Text + Image Resize Crop Gif Jpg Jpeg Bitmap Png Core + https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/build/icons/imagesharp-logo-128.png + https://github.com/JimBobSquarePants/ImageSharp + http://www.apache.org/licenses/LICENSE-2.0 + git + https://github.com/JimBobSquarePants/ImageSharp + false + false + false + false + false + false + false + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + diff --git a/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs b/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..fba25a9db --- /dev/null +++ b/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// Common values read from `AssemblyInfo.Common.cs` diff --git a/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs new file mode 100644 index 000000000..e707ef5e5 --- /dev/null +++ b/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs @@ -0,0 +1,68 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + /// + /// Options for influencing the drawing functions. + /// + public struct TextGraphicsOptions + { + /// + /// Represents the default . + /// + public static readonly TextGraphicsOptions Default = new TextGraphicsOptions(true); + + /// + /// Whether antialiasing should be applied. + /// + public bool Antialias; + + /// + /// Whether the text should be drawing with kerning enabled. + /// + public bool ApplyKerning; + + /// + /// The number of space widths a tab should lock to. + /// + public float TabWidth; + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + public TextGraphicsOptions(bool enableAntialiasing) + { + this.Antialias = enableAntialiasing; + this.ApplyKerning = true; + this.TabWidth = 4; + } + + /// + /// 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); + } + + /// + /// 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs b/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs index a5cf02b8a..d77a6d594 100644 --- a/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Drawing.Brushes /// The pixel format. /// A Brush public class Brushes - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Percent10 Hatch Pattern diff --git a/src/ImageSharp.Drawing/Brushes/IBrush.cs b/src/ImageSharp.Drawing/Brushes/IBrush.cs index b28180204..df05fa23e 100644 --- a/src/ImageSharp.Drawing/Brushes/IBrush.cs +++ b/src/ImageSharp.Drawing/Brushes/IBrush.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Drawing /// logic for converting a pixel location to a . /// public interface IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Creates the applicator for this brush. diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 9ce235a84..2707c0064 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The pixel format. public class ImageBrush : IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The image to paint. @@ -82,18 +82,24 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - public override TColor GetColor(Vector2 point) + public override TColor this[int x, int y] { - // Offset the requested pixel by the value in the rectangle (the shapes position) - point = point - this.offset; - int x = (int)point.X % this.xLength; - int y = (int)point.Y % this.yLength; + get + { + var point = new Vector2(x, y); - return this.source[x, y]; + // Offset the requested pixel by the value in the rectangle (the shapes position) + point = point - this.offset; + x = (int)point.X % this.xLength; + y = (int)point.Y % this.yLength; + + return this.source[x, y]; + } } /// diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 7749f5ba8..741ab3f00 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -42,7 +42,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The pixel format. public class PatternBrush : IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The pattern. @@ -134,17 +134,21 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. - /// - /// The point. + /// # + /// The x. + /// The y. /// - /// The color + /// The Color. /// - public override TColor GetColor(Vector2 point) + public override TColor this[int x, int y] { - int x = (int)point.X % this.xLength; - int y = (int)point.Y % this.stride; + get + { + x = x % this.xLength; + y = y % this.stride; - return this.pattern[x][y]; + return this.pattern[x][y]; + } } /// diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 885be5715..b66827e49 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -14,18 +14,17 @@ namespace ImageSharp.Drawing.Processors /// The pixel format. /// public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public abstract void Dispose(); - /// /// Gets the color for a single pixel. /// - /// The point. - /// The color - public abstract TColor GetColor(Vector2 point); + /// The x cordinate. + /// The y cordinate. + /// The a that should be applied to the pixel. + public abstract TColor this[int x, int y] { get; } + + /// + public abstract void Dispose(); } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 7149f22a0..542c3cfed 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The pixel format. public class RecolorBrush : IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -109,24 +109,31 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - public override TColor GetColor(Vector2 point) + public override TColor this[int x, int y] { - // Offset the requested pixel by the value in the rectangle (the shapes position) - TColor result = this.source[(int)point.X, (int)point.Y]; - Vector4 background = result.ToVector4(); - float distance = Vector4.DistanceSquared(background, this.sourceColor); - if (distance <= this.threshold) + get { - var lerpAmount = (this.threshold - distance) / this.threshold; - Vector4 blended = Vector4BlendTransforms.PremultipliedLerp(background, this.targetColor, lerpAmount); - result.PackFromVector4(blended); + // Offset the requested pixel by the value in the rectangle (the shapes position) + TColor result = this.source[x, y]; + Vector4 background = result.ToVector4(); + float distance = Vector4.DistanceSquared(background, this.sourceColor); + if (distance <= this.threshold) + { + var lerpAmount = (this.threshold - distance) / this.threshold; + Vector4 blended = Vector4BlendTransforms.PremultipliedLerp( + background, + this.targetColor, + lerpAmount); + result.PackFromVector4(blended); + } + + return result; } - - return result; } /// diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index c3e311399..30351dbe1 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The pixel format. public class SolidBrush : IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The color to paint. @@ -67,14 +67,12 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - public override TColor GetColor(Vector2 point) - { - return this.color; - } + public override TColor this[int x, int y] => this.color; /// public override void Dispose() diff --git a/src/ImageSharp.Drawing/Draw.cs b/src/ImageSharp.Drawing/Draw.cs deleted file mode 100644 index c10665b83..000000000 --- a/src/ImageSharp.Drawing/Draw.cs +++ /dev/null @@ -1,507 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Numerics; - using Drawing; - using Drawing.Brushes; - using Drawing.Paths; - using Drawing.Pens; - using Drawing.Processors; - using Drawing.Shapes; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The shape. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IPen pen, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new DrawPathProcessor(pen, shape, options)); - } - - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, IPen pen, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(pen, shape, GraphicsOptions.Default); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The shape. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new Pen(brush, thickness), shape, options); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new Pen(brush, thickness), shape); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The shape. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, TColor color, float thickness, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new SolidBrush(color), thickness, shape, options); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, TColor color, float thickness, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new SolidBrush(color), thickness, shape); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points)), options); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The Image - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(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 source. - /// The color. - /// The thickness. - /// The points. - /// The Image - public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return 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 source. - /// The color. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new SolidBrush(color), thickness, points, options); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided Pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(pen, new Polygon(new LinearLineSegment(points)), options); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided Pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The Image - public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(pen, new Polygon(new LinearLineSegment(points))); - } - - /// - /// Draws the path with the provided pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The path. - /// The options. - /// - /// The Image - /// - public static Image DrawPath(this Image source, IPen pen, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new DrawPathProcessor(pen, path, options)); - } - - /// - /// Draws the path with the provided pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The path. - /// The Image - public static Image DrawPath(this Image source, IPen pen, IPath path) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new DrawPathProcessor(pen, path, GraphicsOptions.Default)); - } - - /// - /// Draws the path with the bursh at the privdied thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The path. - /// The options. - /// - /// The Image - /// - public static Image DrawPath(this Image source, IBrush brush, float thickness, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), path, options); - } - - /// - /// Draws the path with the bursh at the privdied thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The path. - /// The Image - public static Image DrawPath(this Image source, IBrush brush, float thickness, IPath path) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), path); - } - - /// - /// Draws the path with the bursh at the privdied thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The path. - /// The options. - /// - /// The Image - /// - public static Image DrawPath(this Image source, TColor color, float thickness, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new SolidBrush(color), thickness, path, options); - } - - /// - /// Draws the path with the bursh at the privdied thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The path. - /// The Image - public static Image DrawPath(this Image source, TColor color, float thickness, IPath path) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new SolidBrush(color), thickness, path); - } - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), new Path(new LinearLineSegment(points)), options); - } - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The Image - public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(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 source. - /// The color. - /// The thickness. - /// The points. - /// The Image - public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return 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 source. - /// The color. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawLines(new SolidBrush(color), thickness, points, options); - } - - /// - /// Draws the provided Points as an open Linear path with the supplied pen - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawLines(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(pen, new Path(new LinearLineSegment(points)), options); - } - - /// - /// Draws the provided Points as an open Linear path with the supplied pen - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The Image - public static Image DrawLines(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(pen, new Path(new LinearLineSegment(points))); - } - - /// - /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), new Path(new BezierLineSegment(points)), options); - } - - /// - /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The Image - public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), new Path(new BezierLineSegment(points))); - } - - /// - /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The points. - /// The Image - public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return 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 source. - /// The color. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawBeziers(new SolidBrush(color), thickness, points, options); - } - - /// - /// Draws the provided Points as an open Bezier path with the supplied pen - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(pen, new Path(new BezierLineSegment(points)), options); - } - - /// - /// Draws the provided Points as an open Bezier path with the supplied pen - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The Image - public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(pen, new Path(new BezierLineSegment(points))); - } - } -} diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index 4b3fd491d..16582e7ee 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The opacity of the image image to blend. Must be between 0 and 100. /// The . public static Image Blend(this Image source, Image image, int percent = 50) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DrawImage(source, image, percent, default(Size), default(Point)); } @@ -39,7 +39,7 @@ namespace ImageSharp /// The location to draw the blended image. /// The . public static Image DrawImage(this Image source, Image image, int percent, Size size, Point location) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (size == default(Size)) { @@ -51,7 +51,8 @@ namespace ImageSharp location = Point.Empty; } - return source.Apply(source.Bounds, new DrawImageProcessor(image, size, location, percent)); + source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/DrawPath.cs b/src/ImageSharp.Drawing/DrawPath.cs new file mode 100644 index 000000000..e91b97203 --- /dev/null +++ b/src/ImageSharp.Drawing/DrawPath.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using Drawing.Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the outline of the region with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The path. + /// The options. + /// The . + public static Image Draw(this Image source, IPen pen, Drawable path, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Apply(new DrawPathProcessor(pen, path, options)); + } + + /// + /// 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 Image Draw(this Image source, IPen pen, Drawable path) + where TColor : struct, IPixel + { + return source.Draw(pen, path, GraphicsOptions.Default); + } + + /// + /// 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 options. + /// The . + public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(new Pen(brush, thickness), path, options); + } + + /// + /// 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 Image Draw(this Image source, IBrush brush, float thickness, Drawable path) + where TColor : struct, IPixel + { + return 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 color. + /// The thickness. + /// The path. + /// The options. + /// The . + public static Image Draw(this Image source, TColor color, float thickness, Drawable path, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Draw(new SolidBrush(color), thickness, path, options); + } + + /// + /// 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 Image Draw(this Image source, TColor color, float thickness, Drawable path) + where TColor : struct, IPixel + { + return source.Draw(new SolidBrush(color), thickness, path); + } + } +} diff --git a/src/ImageSharp.Drawing/Drawable.cs b/src/ImageSharp.Drawing/Drawable.cs new file mode 100644 index 000000000..62f5e62c1 --- /dev/null +++ b/src/ImageSharp.Drawing/Drawable.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + /// + /// Represents a path or set of paths that can be drawn as an outline. + /// + public abstract class Drawable + { + /// + /// Gets the maximum number of intersections to could be returned. + /// + public abstract int MaxIntersections { get; } + + /// + /// Gets the bounds. + /// + public abstract Rectangle Bounds { get; } + + /// + /// Gets the point information for the specified x and y location. + /// + /// The x. + /// The y. + /// Information about the point in relation to a drawable edge + public abstract PointInfo GetPointInfo(int x, int y); + + /// + /// Scans the X axis for intersections. + /// + /// The x. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + public abstract int ScanX(int x, float[] buffer, int length, int offset); + + /// + /// Scans the Y axis for intersections. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + public abstract int ScanY(int y, float[] buffer, int length, int offset); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Fill.cs b/src/ImageSharp.Drawing/Fill.cs deleted file mode 100644 index c0f43bdd1..000000000 --- a/src/ImageSharp.Drawing/Fill.cs +++ /dev/null @@ -1,173 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Numerics; - using Drawing; - using Drawing.Brushes; - using Drawing.Paths; - using Drawing.Processors; - using Drawing.Shapes; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Flood fills the image with the specified brush. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The Image - public static Image Fill(this Image source, IBrush brush) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new FillProcessor(brush)); - } - - /// - /// Flood fills the image with the specified color. - /// - /// The type of the color. - /// The source. - /// The color. - /// The Image - public static Image Fill(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(new SolidBrush(color)); - } - - /// - /// Flood fills the image in the shape o fhte provided polygon with the specified brush.. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The shape. - /// The graphics options. - /// The Image - public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new FillShapeProcessor(brush, shape, options)); - } - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The shape. - /// The Image - public static Image Fill(this Image source, IBrush brush, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new FillShapeProcessor(brush, shape, GraphicsOptions.Default)); - } - - /// - /// Flood fills the image in the shape o fhte provided polygon with the specified brush.. - /// - /// The type of the color. - /// The source. - /// The color. - /// The shape. - /// The options. - /// - /// The Image - /// - public static Image Fill(this Image source, TColor color, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(new SolidBrush(color), shape, options); - } - - /// - /// Flood fills the image in the shape o fhte provided polygon with the specified brush.. - /// - /// The type of the color. - /// The source. - /// The color. - /// The shape. - /// The Image - public static Image Fill(this Image source, TColor color, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(new SolidBrush(color), shape); - } - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The type of the color. - /// The source. - /// The brush. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - // using Polygon directly instead of LinearPolygon as its will have less indirection - return source.Fill(brush, new Polygon(new LinearLineSegment(points)), options); - } - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The type of the color. - /// The source. - /// The brush. - /// The points. - /// The Image - public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - // using Polygon directly instead of LinearPolygon as its will have less indirection - return 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 source. - /// The color. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image FillPolygon(this Image source, TColor color, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - // using Polygon directly instead of LinearPolygon as its will have less indirection - return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points)), options); - } - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The type of the color. - /// The source. - /// The color. - /// The points. - /// The Image - public static Image FillPolygon(this Image source, TColor color, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - // using Polygon directly instead of LinearPolygon as its will have less indirection - return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points))); - } - } -} diff --git a/src/ImageSharp.Drawing/FillRegion.cs b/src/ImageSharp.Drawing/FillRegion.cs new file mode 100644 index 000000000..8aab20251 --- /dev/null +++ b/src/ImageSharp.Drawing/FillRegion.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + using Drawing; + using Drawing.Brushes; + using Drawing.Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// 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 Image Fill(this Image source, IBrush brush) + where TColor : struct, IPixel + { + return source.Apply(new FillProcessor(brush)); + } + + /// + /// Flood fills the image with the specified color. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The . + public static Image Fill(this Image source, TColor color) + where TColor : struct, IPixel + { + return 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 graphics options. + /// The . + public static Image Fill(this Image source, IBrush brush, Region region, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Apply(new FillRegionProcessor(brush, region, options)); + } + + /// + /// 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 Image Fill(this Image source, IBrush brush, Region region) + where TColor : struct, IPixel + { + return source.Fill(brush, region, GraphicsOptions.Default); + } + + /// + /// 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 options. + /// The . + public static Image Fill(this Image source, TColor color, Region region, GraphicsOptions options) + where TColor : struct, IPixel + { + return source.Fill(new SolidBrush(color), region, options); + } + + /// + /// 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 Image Fill(this Image source, TColor color, Region region) + where TColor : struct, IPixel + { + return source.Fill(new SolidBrush(color), region); + } + } +} diff --git a/src/ImageSharp.Drawing/Paths/BezierLineSegment.cs b/src/ImageSharp.Drawing/Paths/BezierLineSegment.cs deleted file mode 100644 index 647f97f1e..000000000 --- a/src/ImageSharp.Drawing/Paths/BezierLineSegment.cs +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// Represents a line segment that conistst of control points that will be rendered as a cubic bezier curve - /// - /// - public class BezierLineSegment : ILineSegment - { - /// - /// The segments per curve. - /// code for this taken from - /// - private const int SegmentsPerCurve = 50; - - /// - /// The line points. - /// - private readonly Vector2[] linePoints; - - /// - /// Initializes a new instance of the class. - /// - /// The points. - public BezierLineSegment(params Vector2[] points) - { - Guard.NotNull(points, nameof(points)); - Guard.MustBeGreaterThanOrEqualTo(points.Length, 4, nameof(points)); - - this.linePoints = this.GetDrawingPoints(points); - } - - /// - /// Returns the current a simple linear path. - /// - /// - /// Returns the current as simple linear path. - /// - public Vector2[] AsSimpleLinearPath() - { - return this.linePoints; - } - - /// - /// Returns the drawing points along the line. - /// - /// The control points. - /// - /// The . - /// - private Vector2[] GetDrawingPoints(Vector2[] controlPoints) - { - // TODO we need to calculate an optimal SegmentsPerCurve value - // depending on the calcualted length of this curve - int curveCount = (controlPoints.Length - 1) / 3; - int finalPointCount = (SegmentsPerCurve * curveCount) + 1; // we have SegmentsPerCurve for each curve plus the origon point; - - Vector2[] drawingPoints = new Vector2[finalPointCount]; - - int position = 0; - int targetPoint = controlPoints.Length - 3; - for (int i = 0; i < targetPoint; i += 3) - { - Vector2 p0 = controlPoints[i]; - Vector2 p1 = controlPoints[i + 1]; - Vector2 p2 = controlPoints[i + 2]; - Vector2 p3 = controlPoints[i + 3]; - - // only do this for the first end point. When i != 0, this coincides with the end point of the previous segment, - if (i == 0) - { - drawingPoints[position++] = this.CalculateBezierPoint(0, p0, p1, p2, p3); - } - - for (int j = 1; j <= SegmentsPerCurve; j++) - { - float t = j / (float)SegmentsPerCurve; - drawingPoints[position++] = this.CalculateBezierPoint(t, p0, p1, p2, p3); - } - } - - return drawingPoints; - } - - /// - /// Calculates the bezier point along the line. - /// - /// The position within the line. - /// The p 0. - /// The p 1. - /// The p 2. - /// The p 3. - /// - /// The . - /// - private Vector2 CalculateBezierPoint(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) - { - float u = 1 - t; - float tt = t * t; - float uu = u * u; - float uuu = uu * u; - float ttt = tt * t; - - Vector2 p = uuu * p0; // first term - - p += 3 * uu * t * p1; // second term - p += 3 * u * tt * p2; // third term - p += ttt * p3; // fourth term - - return p; - } - } -} diff --git a/src/ImageSharp.Drawing/Paths/ILineSegment.cs b/src/ImageSharp.Drawing/Paths/ILineSegment.cs deleted file mode 100644 index 380f0bf40..000000000 --- a/src/ImageSharp.Drawing/Paths/ILineSegment.cs +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// Represents a simple path segment - /// - public interface ILineSegment - { - /// - /// Converts the into a simple linear path.. - /// - /// Returns the current as simple linear path. - Vector2[] AsSimpleLinearPath(); // TODO move this over to ReadonlySpan once available - } -} diff --git a/src/ImageSharp.Drawing/Paths/IPath.cs b/src/ImageSharp.Drawing/Paths/IPath.cs deleted file mode 100644 index 278d97251..000000000 --- a/src/ImageSharp.Drawing/Paths/IPath.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// Represents a logic path that can be drawn - /// - public interface IPath : ILineSegment - { - /// - /// Gets the bounds enclosing the path - /// - /// - /// The bounds. - /// - RectangleF Bounds { get; } - - /// - /// Gets a value indicating whether this instance is closed. - /// - /// - /// true if this instance is closed; otherwise, false. - /// - bool IsClosed { get; } - - /// - /// Gets the length of the path - /// - /// - /// The length. - /// - float Length { get; } - - /// - /// Calculates the distance along and away from the path for a specified point. - /// - /// The point along the path. - /// - /// Returns details about the point and its distance away from the path. - /// - PointInfo Distance(Vector2 point); - } -} diff --git a/src/ImageSharp.Drawing/Paths/InternalPath.cs b/src/ImageSharp.Drawing/Paths/InternalPath.cs deleted file mode 100644 index 36a0704cb..000000000 --- a/src/ImageSharp.Drawing/Paths/InternalPath.cs +++ /dev/null @@ -1,516 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageSharp.Drawing.Paths -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - - /// - /// Internal logic for integrating linear paths. - /// - internal class InternalPath - { - /// - /// The maximum vector - /// - private static readonly Vector2 MaxVector = new Vector2(float.MaxValue); - - /// - /// The locker. - /// - private static readonly object Locker = new object(); - - /// - /// The points. - /// - private readonly Vector2[] points; - - /// - /// The closed path. - /// - private readonly bool closedPath; - - /// - /// The total distance. - /// - private readonly Lazy totalDistance; - - /// - /// The constant. - /// - private float[] constant; - - /// - /// The multiples. - /// - private float[] multiple; - - /// - /// The distances. - /// - private float[] distance; - - /// - /// The calculated. - /// - private bool calculated = false; - - /// - /// Initializes a new instance of the class. - /// - /// The segments. - /// if set to true [is closed path]. - internal InternalPath(ILineSegment[] segments, bool isClosedPath) - : this(Simplify(segments), isClosedPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The segment. - /// if set to true [is closed path]. - internal InternalPath(ILineSegment segment, bool isClosedPath) - : this(segment.AsSimpleLinearPath(), isClosedPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The points. - /// if set to true [is closed path]. - internal InternalPath(Vector2[] points, bool isClosedPath) - { - this.points = points; - this.closedPath = isClosedPath; - - float minX = this.points.Min(x => x.X); - float maxX = this.points.Max(x => x.X); - float minY = this.points.Min(x => x.Y); - float maxY = this.points.Max(x => x.Y); - - this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY); - this.totalDistance = new Lazy(this.CalculateLength); - } - - /// - /// Gets the bounds. - /// - /// - /// The bounds. - /// - public RectangleF Bounds - { - get; - } - - /// - /// Gets the length. - /// - /// - /// The length. - /// - public float Length => this.totalDistance.Value; - - /// - /// Gets the points. - /// - /// - /// The points. - /// - internal Vector2[] Points => this.points; - - /// - /// Calculates the distance from the path. - /// - /// The point. - /// Returns the distance from the path - public PointInfo DistanceFromPath(Vector2 point) - { - this.CalculateConstants(); - - PointInfoInternal internalInfo = default(PointInfoInternal); - internalInfo.DistanceSquared = float.MaxValue; // Set it to max so that CalculateShorterDistance can reduce it back down - - int polyCorners = this.points.Length; - - if (!this.closedPath) - { - polyCorners -= 1; - } - - int closestPoint = 0; - for (int i = 0; i < polyCorners; i++) - { - int next = i + 1; - if (this.closedPath && next == polyCorners) - { - next = 0; - } - - if (this.CalculateShorterDistance(this.points[i], this.points[next], point, ref internalInfo)) - { - closestPoint = i; - } - } - - return new PointInfo - { - DistanceAlongPath = this.distance[closestPoint] + Vector2.Distance(this.points[closestPoint], point), - DistanceFromPath = (float)Math.Sqrt(internalInfo.DistanceSquared), - SearchPoint = point, - ClosestPointOnPath = internalInfo.PointOnLine - }; - } - - /// - /// Based on a line described by and - /// populate a buffer for all points on the path that the line intersects. - /// - /// The start. - /// The end. - /// The buffer. - /// The count. - /// The offset. - /// number iof intersections hit - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - int polyCorners = this.points.Length; - - if (!this.closedPath) - { - polyCorners -= 1; - } - - int position = 0; - for (int i = 0; i < polyCorners && count > 0; i++) - { - int next = i + 1; - if (this.closedPath && next == polyCorners) - { - next = 0; - } - - Vector2 point = FindIntersection(this.points[i], this.points[next], start, end); - if (point != MaxVector) - { - buffer[position + offset] = point; - position++; - count--; - } - } - - return position; - } - - /// - /// Determines if the specified point is inside or outside the path. - /// - /// The point. - /// Returns true if the point is inside the closed path. - public bool PointInPolygon(Vector2 point) - { - // You can only be inside a path if its "closed" - if (!this.closedPath) - { - return false; - } - - if (!this.Bounds.Contains(point.X, point.Y)) - { - return false; - } - - this.CalculateConstants(); - - Vector2[] poly = this.points; - int polyCorners = poly.Length; - - int j = polyCorners - 1; - bool oddNodes = false; - - for (int i = 0; i < polyCorners; i++) - { - if ((poly[i].Y < point.Y && poly[j].Y >= point.Y) - || (poly[j].Y < point.Y && poly[i].Y >= point.Y)) - { - oddNodes ^= (point.Y * this.multiple[i]) + this.constant[i] < point.X; - } - - j = i; - } - - return oddNodes; - } - - /// - /// Determins if the bounding box for 2 lines - /// described by and - /// and and overlap. - /// - /// The line1 start. - /// The line1 end. - /// The line2 start. - /// The line2 end. - /// Returns true it the bounding box of the 2 lines intersect - private static bool BoundingBoxesIntersect(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End) - { - Vector2 topLeft1 = Vector2.Min(line1Start, line1End); - Vector2 bottomRight1 = Vector2.Max(line1Start, line1End); - - Vector2 topLeft2 = Vector2.Min(line2Start, line2End); - Vector2 bottomRight2 = Vector2.Max(line2Start, line2End); - - float left1 = topLeft1.X; - float right1 = bottomRight1.X; - float top1 = topLeft1.Y; - float bottom1 = bottomRight1.Y; - - float left2 = topLeft2.X; - float right2 = bottomRight2.X; - float top2 = topLeft2.Y; - float bottom2 = bottomRight2.Y; - - return left1 <= right2 && right1 >= left2 - && - top1 <= bottom2 && bottom1 >= top2; - } - - /// - /// Finds the point on line described by and - /// that intersects with line described by and - /// - /// The line1 start. - /// The line1 end. - /// The line2 start. - /// The line2 end. - /// - /// A describing the point that the 2 lines cross or if they do not. - /// - private static Vector2 FindIntersection(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End) - { - // do bounding boxes overlap, if not then the lines can't and return fast. - if (!BoundingBoxesIntersect(line1Start, line1End, line2Start, line2End)) - { - return MaxVector; - } - - Vector2 line1Diff = line1End - line1Start; - Vector2 line2Diff = line2End - line2Start; - - Vector2 point; - if (line1Diff.X == 0) - { - float slope = line2Diff.Y / line2Diff.X; - float yinter = line2Start.Y - (slope * line2Start.X); - float y = (line1Start.X * slope) + yinter; - point = new Vector2(line1Start.X, y); - - // horizontal and vertical lines - } - else if (line2Diff.X == 0) - { - float slope = line1Diff.Y / line1Diff.X; - float yinter = line1Start.Y - (slope * line1Start.X); - float y = (line2Start.X * slope) + yinter; - point = new Vector2(line2Start.X, y); - - // horizontal and vertical lines - } - else - { - float slope1 = line1Diff.Y / line1Diff.X; - float slope2 = line2Diff.Y / line2Diff.X; - - float yinter1 = line1Start.Y - (slope1 * line1Start.X); - float yinter2 = line2Start.Y - (slope2 * line2Start.X); - - if (slope1 == slope2 && yinter1 != yinter2) - { - return MaxVector; - } - - float x = (yinter2 - yinter1) / (slope1 - slope2); - float y = (slope1 * x) + yinter1; - - point = new Vector2(x, y); - } - - if (BoundingBoxesIntersect(line1Start, line1End, point, point)) - { - return point; - } - else if (BoundingBoxesIntersect(line2Start, line2End, point, point)) - { - return point; - } - - return MaxVector; - } - - /// - /// Simplifies the collection of segments. - /// - /// The segments. - /// - /// The . - /// - private static Vector2[] Simplify(ILineSegment[] segments) - { - List simplified = new List(); - foreach (ILineSegment seg in segments) - { - simplified.AddRange(seg.AsSimpleLinearPath()); - } - - return simplified.ToArray(); - } - - /// - /// Returns the length of the path. - /// - /// - /// The . - /// - private float CalculateLength() - { - float length = 0; - int polyCorners = this.points.Length; - - if (!this.closedPath) - { - polyCorners -= 1; - } - - for (int i = 0; i < polyCorners; i++) - { - int next = i + 1; - if (this.closedPath && next == polyCorners) - { - next = 0; - } - - length += Vector2.Distance(this.points[i], this.points[next]); - } - - return length; - } - - /// - /// Calculate the constants. - /// - private void CalculateConstants() - { - // http://alienryderflex.com/polygon/ source for point in polygon logic - if (this.calculated) - { - return; - } - - lock (Locker) - { - if (this.calculated) - { - return; - } - - Vector2[] poly = this.points; - int polyCorners = poly.Length; - this.constant = new float[polyCorners]; - this.multiple = new float[polyCorners]; - this.distance = new float[polyCorners]; - int i, j = polyCorners - 1; - - this.distance[0] = 0; - - for (i = 0; i < polyCorners; i++) - { - this.distance[j] = this.distance[i] + Vector2.Distance(poly[i], poly[j]); - if (poly[j].Y == poly[i].Y) - { - this.constant[i] = poly[i].X; - this.multiple[i] = 0; - } - else - { - Vector2 subtracted = poly[j] - poly[i]; - this.constant[i] = (poly[i].X - ((poly[i].Y * poly[j].X) / subtracted.Y)) + ((poly[i].Y * poly[i].X) / subtracted.Y); - this.multiple[i] = subtracted.X / subtracted.Y; - } - - j = i; - } - - this.calculated = true; - } - } - - /// - /// Calculate any shorter distances along the path. - /// - /// The start position. - /// The end position. - /// The current point. - /// The info. - /// - /// The . - /// - private bool CalculateShorterDistance(Vector2 start, Vector2 end, Vector2 point, ref PointInfoInternal info) - { - Vector2 diffEnds = end - start; - - float lengthSquared = diffEnds.LengthSquared(); - Vector2 diff = point - start; - - Vector2 multiplied = diff * diffEnds; - float u = (multiplied.X + multiplied.Y) / lengthSquared; - - if (u > 1) - { - u = 1; - } - else if (u < 0) - { - u = 0; - } - - Vector2 multipliedByU = diffEnds * u; - - Vector2 pointOnLine = start + multipliedByU; - - Vector2 d = pointOnLine - point; - - float dist = d.LengthSquared(); - - if (info.DistanceSquared > dist) - { - info.DistanceSquared = dist; - info.PointOnLine = pointOnLine; - return true; - } - - return false; - } - - /// - /// Contains information about the current point. - /// - private struct PointInfoInternal - { - /// - /// The distance squared. - /// - public float DistanceSquared; - - /// - /// The point on the current line. - /// - public Vector2 PointOnLine; - } - } -} diff --git a/src/ImageSharp.Drawing/Paths/LinearLineSegment.cs b/src/ImageSharp.Drawing/Paths/LinearLineSegment.cs deleted file mode 100644 index 6942ce5a5..000000000 --- a/src/ImageSharp.Drawing/Paths/LinearLineSegment.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Linq; - using System.Numerics; - - /// - /// Represents a series of control points that will be joined by straight lines - /// - /// - public class LinearLineSegment : ILineSegment - { - /// - /// The collection of points. - /// - private readonly Vector2[] points; - - /// - /// Initializes a new instance of the class. - /// - /// The start. - /// The end. - public LinearLineSegment(Vector2 start, Vector2 end) - : this(new[] { start, end }) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The points. - public LinearLineSegment(params Vector2[] points) - { - Guard.NotNull(points, nameof(points)); - Guard.MustBeGreaterThanOrEqualTo(points.Count(), 2, nameof(points)); - - this.points = points; - } - - /// - /// Converts the into a simple linear path.. - /// - /// - /// Returns the current as simple linear path. - /// - public Vector2[] AsSimpleLinearPath() - { - return this.points; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Paths/Path.cs b/src/ImageSharp.Drawing/Paths/Path.cs deleted file mode 100644 index eb2ab5e85..000000000 --- a/src/ImageSharp.Drawing/Paths/Path.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// A aggregate of s making a single logical path - /// - /// - public class Path : IPath - { - /// - /// The inner path. - /// - private readonly InternalPath innerPath; - - /// - /// Initializes a new instance of the class. - /// - /// The segment. - public Path(params ILineSegment[] segment) - { - this.innerPath = new InternalPath(segment, false); - } - - /// - public RectangleF Bounds => this.innerPath.Bounds; - - /// - public bool IsClosed => false; - - /// - public float Length => this.innerPath.Length; - - /// - public Vector2[] AsSimpleLinearPath() - { - return this.innerPath.Points; - } - - /// - public PointInfo Distance(Vector2 point) - { - return this.innerPath.DistanceFromPath(point); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Paths/PointInfo.cs b/src/ImageSharp.Drawing/Paths/PointInfo.cs deleted file mode 100644 index a2cbf1e73..000000000 --- a/src/ImageSharp.Drawing/Paths/PointInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// Returns meta data about the nearest point on a path from a vector - /// - public struct PointInfo - { - /// - /// The search point - /// - public Vector2 SearchPoint; - - /// - /// The distance along path is away from the start of the path - /// - public float DistanceAlongPath; - - /// - /// The distance is away from . - /// - public float DistanceFromPath; - - /// - /// The closest point to that lies on the path. - /// - public Vector2 ClosestPointOnPath; - } -} diff --git a/src/ImageSharp.Drawing/Pens/IPen.cs b/src/ImageSharp.Drawing/Pens/IPen.cs index 0cf473427..72a5ffc36 100644 --- a/src/ImageSharp.Drawing/Pens/IPen.cs +++ b/src/ImageSharp.Drawing/Pens/IPen.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Drawing.Pens /// /// The type of the color. public interface IPen - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Creates the applicator for applying this pen to an Image diff --git a/src/ImageSharp.Drawing/Pens/Pens{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pens{TColor}.cs index 94a282659..49eed370d 100644 --- a/src/ImageSharp.Drawing/Pens/Pens{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pens{TColor}.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Drawing.Pens /// /// The type of the color. public class Pens - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private static readonly float[] DashDotPattern = new[] { 3f, 1f, 1f, 1f }; private static readonly float[] DashDotDotPattern = new[] { 3f, 1f, 1f, 1f, 1f, 1f }; diff --git a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index a08c7a7fa..79a5d6b15 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs @@ -9,7 +9,6 @@ namespace ImageSharp.Drawing.Pens using System.Numerics; using ImageSharp.Drawing.Brushes; - using ImageSharp.Drawing.Paths; using Processors; /// @@ -25,7 +24,7 @@ namespace ImageSharp.Drawing.Pens /// the the pattern will imidiatly repeat without gap. /// public class Pen : IPen - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private static readonly float[] EmptyPattern = new float[0]; private readonly float[] pattern; @@ -145,10 +144,10 @@ namespace ImageSharp.Drawing.Pens this.brush.Dispose(); } - public override ColoredPointInfo GetColor(PointInfo info) + public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { var result = default(ColoredPointInfo); - result.Color = this.brush.GetColor(info.SearchPoint); + result.Color = this.brush[x, y]; if (info.DistanceFromPath < this.halfWidth) { @@ -198,7 +197,7 @@ namespace ImageSharp.Drawing.Pens this.brush.Dispose(); } - public override ColoredPointInfo GetColor(PointInfo info) + public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { var infoResult = default(ColoredPointInfo); infoResult.DistanceFromElement = float.MaxValue; // is really outside the element @@ -208,7 +207,7 @@ namespace ImageSharp.Drawing.Pens // we can treat the DistanceAlongPath and DistanceFromPath as x,y coords for the pattern // we need to calcualte the distance from the outside edge of the pattern // and set them on the ColoredPointInfo along with the color. - infoResult.Color = this.brush.GetColor(info.SearchPoint); + infoResult.Color = this.brush[x, y]; float distanceWAway = 0; diff --git a/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs b/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs index 494f0f4e4..d042bdccb 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Drawing.Processors /// /// The type of the color. public struct ColoredPointInfo - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The color diff --git a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs index e07b96949..8cdb04b45 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs @@ -6,14 +6,14 @@ namespace ImageSharp.Drawing.Processors { using System; - using Paths; + using System.Numerics; /// /// primitive that converts a into a color and a distance away from the drawable part of the path. /// /// The type of the color. public abstract class PenApplicator : IDisposable - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the required region. @@ -23,16 +23,18 @@ namespace ImageSharp.Drawing.Processors /// public abstract RectangleF RequiredRegion { get; } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// + /// public abstract void Dispose(); /// /// Gets a from a point represented by a . /// + /// The x. + /// The y. /// The information to extract color details about. - /// Returns the color details and distance from a solid bit of the line. - public abstract ColoredPointInfo GetColor(PointInfo info); + /// + /// Returns the color details and distance from a solid bit of the line. + /// + public abstract ColoredPointInfo GetColor(int x, int y, PointInfo info); } } diff --git a/src/ImageSharp.Drawing/PointInfo.cs b/src/ImageSharp.Drawing/PointInfo.cs new file mode 100644 index 000000000..7eff24fac --- /dev/null +++ b/src/ImageSharp.Drawing/PointInfo.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + /// + /// Returns details about how far away from the inside of a shape and the color the pixel could be. + /// + public struct PointInfo + { + /// + /// The distance along path + /// + public float DistanceAlongPath; + + /// + /// The distance from path + /// + public float DistanceFromPath; + } +} diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index 81b40e655..1c1de45cb 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Drawing.Processors /// /// The pixel format. public class DrawImageProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index f7bdcb689..95f4ab472 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -6,14 +6,11 @@ namespace ImageSharp.Drawing.Processors { using System; - using System.Linq; using System.Numerics; using System.Threading.Tasks; + using ImageSharp.Processing; - using Paths; using Pens; - using Shapes; - using Rectangle = ImageSharp.Rectangle; /// /// Draws a path using the processor pipeline @@ -21,72 +18,46 @@ namespace ImageSharp.Drawing.Processors /// The type of the color. /// public class DrawPathProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private const float AntialiasFactor = 1f; private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor - private readonly IPen pen; - private readonly IPath[] paths; - private readonly RectangleF region; - private readonly GraphicsOptions options; - /// /// Initializes a new instance of the class. /// - /// The pen. - /// The shape. - /// The options. - public DrawPathProcessor(IPen pen, IShape shape, GraphicsOptions options) - : this(pen, shape.ToArray(), options) + /// The details how to draw the outline/path. + /// The details of the paths and outlines to draw. + /// The drawing configuration options. + public DrawPathProcessor(IPen pen, Drawable drawable, GraphicsOptions options) { + this.Path = drawable; + this.Pen = pen; + this.Options = options; } /// - /// Initializes a new instance of the class. + /// Gets the graphics options. /// - /// The pen. - /// The path. - /// The options. - public DrawPathProcessor(IPen pen, IPath path, GraphicsOptions options) - : this(pen, new[] { path }, options) - { - } + public GraphicsOptions Options { get; } /// - /// Initializes a new instance of the class. + /// Gets the pen. /// - /// The pen. - /// The paths. - /// The options. - public DrawPathProcessor(IPen pen, IPath[] paths, GraphicsOptions options) - { - this.paths = paths; - this.pen = pen; - this.options = options; - - if (paths.Length != 1) - { - var maxX = paths.Max(x => x.Bounds.Right); - var minX = paths.Min(x => x.Bounds.Left); - var maxY = paths.Max(x => x.Bounds.Bottom); - var minY = paths.Min(x => x.Bounds.Top); + public IPen Pen { get; } - this.region = new RectangleF(minX, minY, maxX - minX, maxY - minY); - } - else - { - this.region = paths[0].Bounds; - } - } + /// + /// Gets the path. + /// + public Drawable Path { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.pen.CreateApplicator(sourcePixels, this.region)) + using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds)) { - var rect = RectangleF.Ceiling(applicator.RequiredRegion); + Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion); int polyStartY = rect.Y - PaddingFactor; int polyEndY = rect.Bottom + PaddingFactor; @@ -116,69 +87,52 @@ namespace ImageSharp.Drawing.Processors } Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - polyStartY; - var currentPoint = default(Vector2); - for (int x = minX; x < maxX; x++) + minY, + maxY, + this.ParallelOptions, + y => { - int offsetX = x - startX; - currentPoint.X = offsetX; - currentPoint.Y = offsetY; + int offsetY = y - polyStartY; - var dist = this.Closest(currentPoint); - - var color = applicator.GetColor(dist); - - var opacity = this.Opacity(color.DistanceFromElement); - - if (opacity > Constants.Epsilon) + for (int x = minX; x < maxX; x++) { - int offsetColorX = x - minX; + // TODO add find intersections code to skip and scan large regions of this. + int offsetX = x - startX; + PointInfo info = this.Path.GetPointInfo(offsetX, offsetY); - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = color.Color.ToVector4(); + ColoredPointInfo color = applicator.GetColor(offsetX, offsetY, info); - var finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; + float opacity = this.Opacity(color.DistanceFromElement); - TColor packed = default(TColor); - packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; - } - } - }); - } - } + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); + Vector4 sourceVector = color.Color.ToVector4(); - private PointInfo Closest(Vector2 point) - { - PointInfo result = default(PointInfo); - float distance = float.MaxValue; + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - for (int i = 0; i < this.paths.Length; i++) - { - var p = this.paths[i].Distance(point); - if (p.DistanceFromPath < distance) - { - distance = p.DistanceFromPath; - result = p; - } + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + sourcePixels[offsetX, offsetY] = packed; + } + } + }); } - - return result; } + /// + /// Returns the correct opacity for the given distance. + /// + /// Thw distance from the central point. + /// The private float Opacity(float distance) { if (distance <= 0) { return 1; } - else if (this.options.Antialias && distance < AntialiasFactor) + + if (this.Options.Antialias && distance < AntialiasFactor) { return 1 - (distance / AntialiasFactor); } diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index dc87e6da6..9fa01075d 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Drawing.Processors /// /// The pixel format. public class FillProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The brush. @@ -71,17 +71,12 @@ namespace ImageSharp.Drawing.Processors y => { int offsetY = y - startY; - - Vector2 currentPoint = default(Vector2); for (int x = minX; x < maxX; x++) { int offsetX = x - startX; - int offsetColorX = x - minX; - currentPoint.X = offsetX; - currentPoint.Y = offsetY; Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); + Vector4 sourceVector = applicator[offsetX, offsetY].ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1); diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs similarity index 72% rename from src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs rename to src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 4696b8613..4f468c707 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -11,45 +11,58 @@ namespace ImageSharp.Drawing.Processors using System.Threading.Tasks; using Drawing; using ImageSharp.Processing; - using Shapes; - using Rectangle = ImageSharp.Rectangle; /// /// Usinf a brsuh and a shape fills shape with contents of brush the /// /// The type of the color. /// - public class FillShapeProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + public class FillRegionProcessor : ImageProcessor + where TColor : struct, IPixel { private const float AntialiasFactor = 1f; private const int DrawPadding = 1; - private readonly IBrush fillColor; - private readonly IShape poly; - private readonly GraphicsOptions options; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The brush. - /// The shape. - /// The options. - public FillShapeProcessor(IBrush brush, IShape shape, GraphicsOptions options) + /// The details how to fill the region of interest. + /// The region of interest to be filled. + /// The configuration options. + public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options) { - this.poly = shape; - this.fillColor = brush; - this.options = options; + this.Region = region; + this.Brush = brush; + this.Options = options; } + /// + /// Gets the brush. + /// + public IBrush Brush { get; } + + /// + /// Gets the region that this processor applies to. + /// + public Region Region { get; } + + /// + /// Gets the options. + /// + /// + /// The options. + /// + public GraphicsOptions Options { get; } + /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - Rectangle rect = RectangleF.Ceiling(this.poly.Bounds); // rounds the points out away from the center + Rectangle rect = this.Region.Bounds; - int polyStartY = rect.Y - DrawPadding; - int polyEndY = rect.Bottom + DrawPadding; - int startX = rect.X - DrawPadding; - int endX = rect.Right + DrawPadding; + int polyStartY = sourceRectangle.Y - DrawPadding; + int polyEndY = sourceRectangle.Bottom + DrawPadding; + int startX = sourceRectangle.X - DrawPadding; + int endX = sourceRectangle.Right + DrawPadding; int minX = Math.Max(sourceRectangle.Left, startX); int maxX = Math.Min(sourceRectangle.Right - 1, endX); @@ -62,53 +75,47 @@ namespace ImageSharp.Drawing.Processors minY = Math.Max(0, minY); maxY = Math.Min(source.Height, maxY); - ArrayPool arrayPool = ArrayPool.Shared; + ArrayPool arrayPool = ArrayPool.Shared; - int maxIntersections = this.poly.MaxIntersections; + int maxIntersections = this.Region.MaxIntersections; using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) + using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect)) { Parallel.For( minY, maxY, this.ParallelOptions, - y => + (int y) => { - Vector2[] buffer = arrayPool.Rent(maxIntersections); + float[] buffer = arrayPool.Rent(maxIntersections); try { - Vector2 left = new Vector2(startX, y); - Vector2 right = new Vector2(endX, y); + float right = endX; // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); + int pointsFound = this.Region.ScanY(y, buffer, maxIntersections, 0); if (pointsFound == 0) { - // nothign on this line skip + // nothing on this line skip return; } - QuickSortX(buffer, pointsFound); + QuickSort(buffer, pointsFound); int currentIntersection = 0; - float nextPoint = buffer[0].X; + float nextPoint = buffer[0]; float lastPoint = float.MinValue; bool isInside = false; - // every odd point is the start of a line - Vector2 currentPoint = default(Vector2); - for (int x = minX; x < maxX; x++) { - currentPoint.X = x; - currentPoint.Y = y; if (!isInside) { if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding)) { - if (nextPoint == right.X) + if (nextPoint == right) { // we are in the ends run skip it x = maxX; @@ -129,11 +136,11 @@ namespace ImageSharp.Drawing.Processors lastPoint = nextPoint; if (currentIntersection == pointsFound) { - nextPoint = right.X; + nextPoint = right; } else { - nextPoint = buffer[currentIntersection].X; + nextPoint = buffer[currentIntersection]; // double point from a corner flip the bit back and move on again if (nextPoint == lastPoint) @@ -143,11 +150,11 @@ namespace ImageSharp.Drawing.Processors currentIntersection++; if (currentIntersection == pointsFound) { - nextPoint = right.X; + nextPoint = right; } else { - nextPoint = buffer[currentIntersection].X; + nextPoint = buffer[currentIntersection]; } } } @@ -158,7 +165,7 @@ namespace ImageSharp.Drawing.Processors float opacity = 1; if (!isInside && !onCorner) { - if (this.options.Antialias) + if (this.Options.Antialias) { float distance = float.MaxValue; if (x == lastPoint || x == nextPoint) @@ -192,10 +199,9 @@ namespace ImageSharp.Drawing.Processors if (opacity > Constants.Epsilon) { Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); + Vector4 sourceVector = applicator[x, y].ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; TColor packed = default(TColor); packed.PackFromVector4(finalColor); @@ -209,49 +215,44 @@ namespace ImageSharp.Drawing.Processors } }); - if (this.options.Antialias) + if (this.Options.Antialias) { // we only need to do the X can for antialiasing purposes Parallel.For( minX, maxX, this.ParallelOptions, - x => + (int x) => { - Vector2[] buffer = arrayPool.Rent(maxIntersections); + float[] buffer = arrayPool.Rent(maxIntersections); try { - Vector2 left = new Vector2(x, polyStartY); - Vector2 right = new Vector2(x, polyEndY); + float left = polyStartY; + float right = polyEndY; // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); + int pointsFound = this.Region.ScanX(x, buffer, maxIntersections, 0); if (pointsFound == 0) { // nothign on this line skip return; } - QuickSortY(buffer, pointsFound); + QuickSort(buffer, pointsFound); int currentIntersection = 0; - float nextPoint = buffer[0].Y; - float lastPoint = left.Y; + float nextPoint = buffer[0]; + float lastPoint = left; bool isInside = false; - // every odd point is the start of a line - Vector2 currentPoint = default(Vector2); - for (int y = minY; y < maxY; y++) { - currentPoint.X = x; - currentPoint.Y = y; if (!isInside) { if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding)) { - if (nextPoint == right.Y) + if (nextPoint == right) { // we are in the ends run skip it y = maxY; @@ -266,7 +267,7 @@ namespace ImageSharp.Drawing.Processors { if (y < nextPoint - DrawPadding) { - if (nextPoint == right.Y) + if (nextPoint == right) { // we are in the ends run skip it y = maxY; @@ -286,11 +287,11 @@ namespace ImageSharp.Drawing.Processors lastPoint = nextPoint; if (currentIntersection == pointsFound) { - nextPoint = right.Y; + nextPoint = right; } else { - nextPoint = buffer[currentIntersection].Y; + nextPoint = buffer[currentIntersection]; // double point from a corner flip the bit back and move on again if (nextPoint == lastPoint) @@ -300,11 +301,11 @@ namespace ImageSharp.Drawing.Processors currentIntersection++; if (currentIntersection == pointsFound) { - nextPoint = right.Y; + nextPoint = right; } else { - nextPoint = buffer[currentIntersection].Y; + nextPoint = buffer[currentIntersection]; } } } @@ -315,7 +316,7 @@ namespace ImageSharp.Drawing.Processors float opacity = 1; if (!isInside && !onCorner) { - if (this.options.Antialias) + if (this.Options.Antialias) { float distance = float.MaxValue; if (y == lastPoint || y == nextPoint) @@ -350,8 +351,7 @@ namespace ImageSharp.Drawing.Processors if (opacity > Constants.Epsilon && opacity < 1) { Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); - + Vector4 sourceVector = applicator[x, y].ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); finalColor.W = backgroundVector.W; @@ -370,76 +370,32 @@ namespace ImageSharp.Drawing.Processors } } - private static void Swap(Vector2[] data, int left, int right) + private static void Swap(float[] data, int left, int right) { - Vector2 tmp = data[left]; + float tmp = data[left]; data[left] = data[right]; data[right] = tmp; } - private static void QuickSortY(Vector2[] data, int size) + private static void QuickSort(float[] data, int size) { int hi = Math.Min(data.Length - 1, size - 1); - QuickSortY(data, 0, hi); + QuickSort(data, 0, hi); } - private static void QuickSortY(Vector2[] data, int lo, int hi) + private static void QuickSort(float[] data, int lo, int hi) { if (lo < hi) { - int p = PartitionY(data, lo, hi); - QuickSortY(data, lo, p); - QuickSortY(data, p + 1, hi); - } - } - - private static void QuickSortX(Vector2[] data, int size) - { - int hi = Math.Min(data.Length - 1, size - 1); - QuickSortX(data, 0, hi); - } - - private static void QuickSortX(Vector2[] data, int lo, int hi) - { - if (lo < hi) - { - int p = PartitionX(data, lo, hi); - QuickSortX(data, lo, p); - QuickSortX(data, p + 1, hi); - } - } - - private static int PartitionX(Vector2[] data, int lo, int hi) - { - float pivot = data[lo].X; - int i = lo - 1; - int j = hi + 1; - while (true) - { - do - { - i = i + 1; - } - while (data[i].X < pivot && i < hi); - - do - { - j = j - 1; - } - while (data[j].X > pivot && j > lo); - - if (i >= j) - { - return j; - } - - Swap(data, i, j); + int p = Partition(data, lo, hi); + QuickSort(data, lo, p); + QuickSort(data, p + 1, hi); } } - private static int PartitionY(Vector2[] data, int lo, int hi) + private static int Partition(float[] data, int lo, int hi) { - float pivot = data[lo].Y; + float pivot = data[lo]; int i = lo - 1; int j = hi + 1; while (true) @@ -448,13 +404,13 @@ namespace ImageSharp.Drawing.Processors { i = i + 1; } - while (data[i].Y < pivot && i < hi); + while (data[i] < pivot && i < hi); do { j = j - 1; } - while (data[j].Y > pivot && j > lo); + while (data[j] > pivot && j > lo); if (i >= j) { diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs new file mode 100644 index 000000000..fe1dc5222 --- /dev/null +++ b/src/ImageSharp.Drawing/Region.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + /// + /// Represents a region of an image. + /// + public abstract class Region + { + /// + /// Gets the maximum number of intersections to could be returned. + /// + public abstract int MaxIntersections { get; } + + /// + /// Gets the bounding box that entirely surrounds this region. + /// + /// + /// This should always contains all possible points returned from either or . + /// + public abstract Rectangle Bounds { get; } + + /// + /// Scans the X axis for intersections. + /// + /// The position along the X axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + public abstract int ScanX(int x, float[] buffer, int length, int offset); + + /// + /// Scans the Y axis for intersections. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + public abstract int ScanY(int y, float[] buffer, int length, int offset); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs deleted file mode 100644 index b69ded207..000000000 --- a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System.Collections; - using System.Collections.Generic; - using System.Numerics; - using Paths; - - /// - /// Represents a polygon made up exclusivly of a single close cubic Bezier curve. - /// - public sealed class BezierPolygon : IShape - { - private Polygon innerPolygon; - - /// - /// Initializes a new instance of the class. - /// - /// The points. - public BezierPolygon(params Vector2[] points) - { - this.innerPolygon = new Polygon(new BezierLineSegment(points)); - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds => this.innerPolygon.Bounds; - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections => this.innerPolygon.MaxIntersections; - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// The distance from the shape. - /// - public float Distance(Vector2 point) => this.innerPolygon.Distance(point); - - /// - /// Based on a line described by and - /// populate a buffer for all points on the polygon that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - return this.innerPolygon.FindIntersections(start, end, buffer, count, offset); - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return this.innerPolygon.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.innerPolygon.GetEnumerator(); - } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs deleted file mode 100644 index fd709719c..000000000 --- a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs +++ /dev/null @@ -1,246 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - - using Paths; - using PolygonClipper; - - /// - /// Represents a complex polygon made up of one or more outline - /// polygons and one or more holes to punch out of them. - /// - /// - public sealed class ComplexPolygon : IShape - { - private const float ClipperScaleFactor = 100f; - private IShape[] shapes; - private IEnumerable paths; - - /// - /// Initializes a new instance of the class. - /// - /// The outline. - /// The holes. - public ComplexPolygon(IShape outline, params IShape[] holes) - : this(new[] { outline }, holes) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The outlines. - /// The holes. - public ComplexPolygon(IShape[] outlines, IShape[] holes) - { - Guard.NotNull(outlines, nameof(outlines)); - Guard.MustBeGreaterThanOrEqualTo(outlines.Length, 1, nameof(outlines)); - - this.MaxIntersections = this.FixAndSetShapes(outlines, holes); - - float minX = this.shapes.Min(x => x.Bounds.Left); - float maxX = this.shapes.Max(x => x.Bounds.Right); - float minY = this.shapes.Min(x => x.Bounds.Top); - float maxY = this.shapes.Max(x => x.Bounds.Bottom); - - this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY); - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds { get; } - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections { get; } - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// Returns the distance from thr shape to the point - /// - /// - /// Due to the clipping we did during construction we know that out shapes do not overlap at there edges - /// therefore for apoint to be in more that one we must be in a hole of another, theoretically this could - /// then flip again to be in a outlin inside a hole inside an outline :) - /// - float IShape.Distance(Vector2 point) - { - float dist = float.MaxValue; - bool inside = false; - foreach (IShape shape in this.shapes) - { - float d = shape.Distance(point); - - if (d <= 0) - { - // we are inside a poly - d = -d; // flip the sign - inside ^= true; // flip the inside flag - } - - if (d < dist) - { - dist = d; - } - } - - if (inside) - { - return -dist; - } - - return dist; - } - - /// - /// Based on a line described by and - /// populate a buffer for all points on all the polygons, that make up this complex shape, - /// that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - int totalAdded = 0; - for (int i = 0; i < this.shapes.Length; i++) - { - int added = this.shapes[i].FindIntersections(start, end, buffer, count, offset); - count -= added; - offset += added; - totalAdded += added; - } - - return totalAdded; - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return this.paths.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - private void AddPoints(Clipper clipper, IShape shape, PolyType polyType) - { - // if the path is already the shape use it directly and skip the path loop. - if (shape is IPath) - { - clipper.AddPath( - (IPath)shape, - polyType); - } - else - { - foreach (IPath path in shape) - { - clipper.AddPath( - path, - polyType); - } - } - } - - private void AddPoints(Clipper clipper, IEnumerable shapes, PolyType polyType) - { - foreach (IShape shape in shapes) - { - this.AddPoints(clipper, shape, polyType); - } - } - - private void ExtractOutlines(PolyNode tree, List shapes, List paths) - { - if (tree.Contour.Any()) - { - // if the source path is set then we clipper retained the full path intact thus we can freely - // use it and get any shape optimisations that are availible. - if (tree.SourcePath != null) - { - shapes.Add((IShape)tree.SourcePath); - paths.Add(tree.SourcePath); - } - else - { - // convert the Clipper Contour from scaled ints back down to the origional size (this is going to be lossy but not significantly) - Polygon polygon = new Polygon(new Paths.LinearLineSegment(tree.Contour.ToArray())); - - shapes.Add(polygon); - paths.Add(polygon); - } - } - - foreach (PolyNode c in tree.Children) - { - this.ExtractOutlines(c, shapes, paths); - } - } - - private int FixAndSetShapes(IEnumerable outlines, IEnumerable holes) - { - Clipper clipper = new Clipper(); - - // add the outlines and the holes to clipper, scaling up from the float source to the int based system clipper uses - this.AddPoints(clipper, outlines, PolyType.Subject); - this.AddPoints(clipper, holes, PolyType.Clip); - - PolyTree tree = clipper.Execute(); - - List shapes = new List(); - List paths = new List(); - - // convert the 'tree' back to paths - this.ExtractOutlines(tree, shapes, paths); - this.shapes = shapes.ToArray(); - this.paths = paths.ToArray(); - - int intersections = 0; - foreach (IShape s in this.shapes) - { - intersections += s.MaxIntersections; - } - - return intersections; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/IShape.cs b/src/ImageSharp.Drawing/Shapes/IShape.cs deleted file mode 100644 index 242e3bd8e..000000000 --- a/src/ImageSharp.Drawing/Shapes/IShape.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System.Collections.Generic; - using System.Numerics; - using Paths; - - /// - /// Represents a closed set of paths making up a single shape. - /// - public interface IShape : IEnumerable - { - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - RectangleF Bounds { get; } - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - int MaxIntersections { get; } - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// Returns the distance from the shape to the point - /// - float Distance(Vector2 point); - - /// - /// Based on a line described by and - /// populate a buffer for all points on the polygon that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset); - } -} diff --git a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs b/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs deleted file mode 100644 index 30a30c20f..000000000 --- a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System.Collections; - using System.Collections.Generic; - using System.Numerics; - using Paths; - - /// - /// Represents a polygon made up exclusivly of a single Linear path. - /// - public sealed class LinearPolygon : IShape - { - private Polygon innerPolygon; - - /// - /// Initializes a new instance of the class. - /// - /// The points. - public LinearPolygon(params Vector2[] points) - { - this.innerPolygon = new Polygon(new LinearLineSegment(points)); - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds => this.innerPolygon.Bounds; - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections - { - get - { - return this.innerPolygon.MaxIntersections; - } - } - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// Returns the distance from the shape to the point - /// - public float Distance(Vector2 point) => this.innerPolygon.Distance(point); - - /// - /// Based on a line described by and - /// populate a buffer for all points on the polygon that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - return this.innerPolygon.FindIntersections(start, end, buffer, count, offset); - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() => this.innerPolygon.GetEnumerator(); - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() => this.innerPolygon.GetEnumerator(); - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Polygon.cs b/src/ImageSharp.Drawing/Shapes/Polygon.cs deleted file mode 100644 index 86c3c9ee4..000000000 --- a/src/ImageSharp.Drawing/Shapes/Polygon.cs +++ /dev/null @@ -1,156 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System.Collections; - using System.Collections.Generic; - using System.Numerics; - - using Paths; - - /// - /// A shape made up of a single path made up of one of more s - /// - public sealed class Polygon : IShape, IPath - { - private readonly InternalPath innerPath; - private readonly IEnumerable pathCollection; - - /// - /// Initializes a new instance of the class. - /// - /// The segments. - public Polygon(params ILineSegment[] segments) - { - this.innerPath = new InternalPath(segments, true); - this.pathCollection = new[] { this }; - } - - /// - /// Initializes a new instance of the class. - /// - /// The segment. - public Polygon(ILineSegment segment) - { - this.innerPath = new InternalPath(segment, true); - this.pathCollection = new[] { this }; - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds => this.innerPath.Bounds; - - /// - /// Gets the length of the path - /// - /// - /// The length. - /// - public float Length => this.innerPath.Length; - - /// - /// Gets a value indicating whether this instance is closed. - /// - /// - /// true if this instance is closed; otherwise, false. - /// - public bool IsClosed => true; - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections => this.innerPath.Points.Length; - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// The distance of the point away from the shape - /// - public float Distance(Vector2 point) - { - bool isInside = this.innerPath.PointInPolygon(point); - - float distance = this.innerPath.DistanceFromPath(point).DistanceFromPath; - if (isInside) - { - return -distance; - } - - return distance; - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return this.pathCollection.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.pathCollection.GetEnumerator(); - } - - /// - /// Calcualtes the distance along and away from the path for a specified point. - /// - /// The point along the path. - /// - /// distance metadata about the point. - /// - PointInfo IPath.Distance(Vector2 point) - { - return this.innerPath.DistanceFromPath(point); - } - - /// - /// Returns the current shape as a simple linear path. - /// - /// - /// Returns the current as simple linear path. - /// - public Vector2[] AsSimpleLinearPath() - { - return this.innerPath.Points; - } - - /// - /// Based on a line described by and - /// populate a buffer for all points on the polygon that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - return this.innerPath.FindIntersections(start, end, buffer, count, offset); - } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Clipper.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Clipper.cs deleted file mode 100644 index c13acec8f..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Clipper.cs +++ /dev/null @@ -1,3860 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Library to clip polygons. - /// - internal class Clipper - { - private const double HorizontalDeltaLimit = -3.4E+38; - private const int Skip = -2; - private const int Unassigned = -1; // InitOptions that can be passed to the constructor ... - - private Maxima maxima; - private TEdge sortedEdges; - private List intersectList; - private IComparer intersectNodeComparer = new IntersectNodeSort(); - private bool executeLocked; - - private List joins; - private List ghostJoins; - private bool usingPolyTree; - private LocalMinima minimaList = null; - private LocalMinima currentLM = null; - private List> edges = new List>(); - private Scanbeam scanbeam; - private List polyOuts; - private TEdge activeEdges; - - /// - /// Initializes a new instance of the class. - /// - public Clipper() - { - this.scanbeam = null; - this.maxima = null; - this.activeEdges = null; - this.sortedEdges = null; - this.intersectList = new List(); - this.executeLocked = false; - this.usingPolyTree = false; - this.polyOuts = new List(); - this.joins = new List(); - this.ghostJoins = new List(); - } - - /// - /// Node types - /// - private enum NodeType - { - /// - /// Any - /// - Any, - - /// - /// The open - /// - Open, - - /// - /// The closed - /// - Closed - } - - /// - /// Adds the path. - /// - /// The path. - /// Type of the poly. - /// True if the path was added. - /// AddPath: Open paths have been disabled. - public bool AddPath(IPath path, PolyType polyType) - { - var pg = path.AsSimpleLinearPath(); - - int highI = pg.Length - 1; - while (highI > 0 && (pg[highI] == pg[0])) - { - --highI; - } - - while (highI > 0 && (pg[highI] == pg[highI - 1])) - { - --highI; - } - - if (highI < 2) - { - return false; - } - - // create a new edge array ... - List edges = new List(highI + 1); - for (int i = 0; i <= highI; i++) - { - edges.Add(new TEdge() { SourcePath = path }); - } - - bool isFlat = true; - - // 1. Basic (first) edge initialization ... - edges[1].Curr = pg[1]; - - InitEdge(edges[0], edges[1], edges[highI], pg[0]); - InitEdge(edges[highI], edges[0], edges[highI - 1], pg[highI]); - for (int i = highI - 1; i >= 1; --i) - { - InitEdge(edges[i], edges[i + 1], edges[i - 1], pg[i]); - } - - TEdge eStart = edges[0]; - - // 2. Remove duplicate vertices, and (when closed) collinear edges ... - TEdge edge = eStart, eLoopStop = eStart; - while (true) - { - // nb: allows matching start and end points when not Closed ... - if (edge.Curr == edge.Next.Curr) - { - if (edge == edge.Next) - { - break; - } - - if (edge == eStart) - { - eStart = edge.Next; - } - - edge = RemoveEdge(edge); - eLoopStop = edge; - continue; - } - - if (edge.Prev == edge.Next) - { - break; // only two vertices - } - else if (SlopesEqual(edge.Prev.Curr, edge.Curr, edge.Next.Curr)) - { - // Collinear edges are allowed for open paths but in closed paths - // the default is to merge adjacent collinear edges into a single edge. - // However, if the PreserveCollinear property is enabled, only overlapping - // collinear edges (ie spikes) will be removed from closed paths. - if (edge == eStart) - { - eStart = edge.Next; - } - - edge = RemoveEdge(edge); - edge = edge.Prev; - eLoopStop = edge; - continue; - } - - edge = edge.Next; - if (edge == eLoopStop) - { - break; - } - } - - if (edge.Prev == edge.Next) - { - return false; - } - - // 3. Do second stage of edge initialization ... - edge = eStart; - do - { - this.InitEdge2(edge, polyType); - edge = edge.Next; - if (isFlat && edge.Curr.Y != eStart.Curr.Y) - { - isFlat = false; - } - } - while (edge != eStart); - - // 4. Finally, add edge bounds to LocalMinima list ... - // Totally flat paths must be handled differently when adding them - // to LocalMinima list to avoid endless loops etc ... - if (isFlat) - { - return false; - } - - this.edges.Add(edges); - bool leftBoundIsForward; - TEdge emIn = null; - - // workaround to avoid an endless loop in the while loop below when - // open paths have matching start and end points ... - if (edge.Prev.Bot == edge.Prev.Top) - { - edge = edge.Next; - } - - while (true) - { - edge = FindNextLocMin(edge); - if (edge == emIn) - { - break; - } - else if (emIn == null) - { - emIn = edge; - } - - // E and E.Prev now share a local minima (left aligned if horizontal). - // Compare their slopes to find which starts which bound ... - LocalMinima locMin = new LocalMinima(); - locMin.Next = null; - locMin.Y = edge.Bot.Y; - if (edge.Dx < edge.Prev.Dx) - { - locMin.LeftBound = edge.Prev; - locMin.RightBound = edge; - leftBoundIsForward = false; // Q.nextInLML = Q.prev - } - else - { - locMin.LeftBound = edge; - locMin.RightBound = edge.Prev; - leftBoundIsForward = true; // Q.nextInLML = Q.next - } - - locMin.LeftBound.Side = EdgeSide.Left; - locMin.RightBound.Side = EdgeSide.Right; - - if (locMin.LeftBound.Next == locMin.RightBound) - { - locMin.LeftBound.WindDelta = -1; - } - else - { - locMin.LeftBound.WindDelta = 1; - } - - locMin.RightBound.WindDelta = -locMin.LeftBound.WindDelta; - - edge = this.ProcessBound(locMin.LeftBound, leftBoundIsForward); - if (edge.OutIdx == Skip) - { - edge = this.ProcessBound(edge, leftBoundIsForward); - } - - TEdge edge2 = this.ProcessBound(locMin.RightBound, !leftBoundIsForward); - if (edge2.OutIdx == Skip) - { - edge2 = this.ProcessBound(edge2, !leftBoundIsForward); - } - - if (locMin.LeftBound.OutIdx == Skip) - { - locMin.LeftBound = null; - } - else if (locMin.RightBound.OutIdx == Skip) - { - locMin.RightBound = null; - } - - this.InsertLocalMinima(locMin); - if (!leftBoundIsForward) - { - edge = edge2; - } - } - - return true; - } - - /// - /// Executes the specified clip type. - /// - /// - /// Returns the polytree containing the converted polygons. - /// - public PolyTree Execute() - { - PolyTree polytree = new PolyTree(); - - if (this.executeLocked) - { - return null; - } - - this.executeLocked = true; - this.usingPolyTree = true; - bool succeeded; - try - { - succeeded = this.ExecuteInternal(); - - // build the return polygons ... - if (succeeded) - { - this.BuildResult2(polytree); - } - } - finally - { - this.DisposeAllPolyPts(); - this.executeLocked = false; - } - - if (succeeded) - { - return polytree; - } - - return null; - } - - private static float Round(double value) - { - return value < 0 ? (float)(value - 0.5) : (float)(value + 0.5); - } - - private static float TopX(TEdge edge, float currentY) - { - if (currentY == edge.Top.Y) - { - return edge.Top.X; - } - - return edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y)); - } - - private static void AddPolyNodeToPaths(PolyNode polynode, NodeType nt, List> paths) - { - bool match = true; - switch (nt) - { - case NodeType.Open: return; - case NodeType.Closed: match = !polynode.IsOpen; break; - default: break; - } - - if (polynode.Polygon.Count > 0 && match) - { - paths.Add(polynode.Polygon); - } - - foreach (PolyNode pn in polynode.Children) - { - AddPolyNodeToPaths(pn, nt, paths); - } - } - - private static double DistanceFromLineSqrd(Vector2 pt, Vector2 ln1, Vector2 ln2) - { - // The equation of a line in general form (Ax + By + C = 0) - // given 2 points (x¹,y¹) & (x²,y²) is ... - // (y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 - // A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ - // perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) - // see http://en.wikipedia.org/wiki/Perpendicular_distance - double a = ln1.Y - ln2.Y; - double b = ln2.X - ln1.X; - double c = (a * ln1.X) + (b * ln1.Y); - c = (a * pt.X) + (b * pt.Y) - c; - return (c * c) / ((a * a) + (b * b)); - } - - private static bool SlopesNearCollinear(Vector2 pt1, Vector2 pt2, Vector2 pt3, double distSqrd) - { - // this function is more accurate when the point that's GEOMETRICALLY - // between the other 2 points is the one that's tested for distance. - // nb: with 'spikes', either pt1 or pt3 is geometrically between the other pts - if (Math.Abs(pt1.X - pt2.X) > Math.Abs(pt1.Y - pt2.Y)) - { - if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) - { - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - } - else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) - { - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - } - else - { - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } - } - else - { - if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) - { - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - } - else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) - { - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - } - else - { - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } - } - } - - private static bool PointsAreClose(Vector2 pt1, Vector2 pt2, double distSqrd) - { - return Vector2.DistanceSquared(pt1, pt2) <= distSqrd; - } - - private static OutPt ExcludeOp(OutPt op) - { - OutPt result = op.Prev; - result.Next = op.Next; - op.Next.Prev = result; - result.Idx = 0; - return result; - } - - private static void FixHoleLinkage(OutRec outRec) - { - // skip if an outermost polygon or - // already already points to the correct FirstLeft ... - if (outRec.FirstLeft == null || - (outRec.IsHole != outRec.FirstLeft.IsHole && - outRec.FirstLeft.Pts != null)) - { - return; - } - - OutRec orfl = outRec.FirstLeft; - while (orfl != null && ((orfl.IsHole == outRec.IsHole) || orfl.Pts == null)) - { - orfl = orfl.FirstLeft; - } - - outRec.FirstLeft = orfl; - } - - // See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos - // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf - private static int PointInPolygon(Vector2 pt, OutPt op) - { - // returns 0 if false, +1 if true, -1 if pt ON polygon boundary - int result = 0; - OutPt startOp = op; - float ptx = pt.X, pty = pt.Y; - float poly0x = op.Pt.X, poly0y = op.Pt.Y; - do - { - op = op.Next; - float poly1x = op.Pt.X, poly1y = op.Pt.Y; - - if (poly1y == pty) - { - if ((poly1x == ptx) || (poly0y == pty && - ((poly1x > ptx) == (poly0x < ptx)))) - { - return -1; - } - } - - if ((poly0y < pty) != (poly1y < pty)) - { - if (poly0x >= ptx) - { - if (poly1x > ptx) - { - result = 1 - result; - } - else - { - double d = (double)((poly0x - ptx) * (poly1y - pty)) - - (double)((poly1x - ptx) * (poly0y - pty)); - if (d == 0) - { - return -1; - } - - if ((d > 0) == (poly1y > poly0y)) - { - result = 1 - result; - } - } - } - else - { - if (poly1x > ptx) - { - double d = (double)((poly0x - ptx) * (poly1y - pty)) - (double)((poly1x - ptx) * (poly0y - pty)); - if (d == 0) - { - return -1; - } - - if ((d > 0) == (poly1y > poly0y)) - { - result = 1 - result; - } - } - } - } - - poly0x = poly1x; - poly0y = poly1y; - } - while (startOp != op); - - return result; - } - - private static bool Poly2ContainsPoly1(OutPt outPt1, OutPt outPt2) - { - OutPt op = outPt1; - do - { - // nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon - int res = PointInPolygon(op.Pt, outPt2); - if (res >= 0) - { - return res > 0; - } - - op = op.Next; - } - while (op != outPt1); - return true; - } - - private static void SwapSides(TEdge edge1, TEdge edge2) - { - EdgeSide side = edge1.Side; - edge1.Side = edge2.Side; - edge2.Side = side; - } - - private static void SwapPolyIndexes(TEdge edge1, TEdge edge2) - { - int outIdx = edge1.OutIdx; - edge1.OutIdx = edge2.OutIdx; - edge2.OutIdx = outIdx; - } - - private static double GetDx(Vector2 pt1, Vector2 pt2) - { - if (pt1.Y == pt2.Y) - { - return HorizontalDeltaLimit; - } - else - { - return (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); - } - } - - private static bool HorzSegmentsOverlap(float seg1a, float seg1b, float seg2a, float seg2b) - { - if (seg1a > seg1b) - { - Swap(ref seg1a, ref seg1b); - } - - if (seg2a > seg2b) - { - Swap(ref seg2a, ref seg2b); - } - - return (seg1a < seg2b) && (seg2a < seg1b); - } - - private static TEdge FindNextLocMin(TEdge edge) - { - TEdge edge2; - while (true) - { - while (edge.Bot != edge.Prev.Bot || edge.Curr == edge.Top) - { - edge = edge.Next; - } - - if (edge.Dx != HorizontalDeltaLimit && edge.Prev.Dx != HorizontalDeltaLimit) - { - break; - } - - while (edge.Prev.Dx == HorizontalDeltaLimit) - { - edge = edge.Prev; - } - - edge2 = edge; - while (edge.Dx == HorizontalDeltaLimit) - { - edge = edge.Next; - } - - if (edge.Top.Y == edge.Prev.Bot.Y) - { - continue; // ie just an intermediate horz. - } - - if (edge2.Prev.Bot.X < edge.Bot.X) - { - edge = edge2; - } - - break; - } - - return edge; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Swap(ref float val1, ref float val2) - { - float tmp = val1; - val1 = val2; - val2 = tmp; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsHorizontal(TEdge e) - { - return e.Delta.Y == 0; - } - - private static bool SlopesEqual(TEdge e1, TEdge e2) - { - return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y; - } - - private static bool SlopesEqual(Vector2 pt1, Vector2 pt2, Vector2 pt3) - { - var dif12 = pt1 - pt2; - var dif23 = pt2 - pt3; - return (dif12.Y * dif23.X) - (dif12.X * dif23.Y) == 0; - } - - private static bool SlopesEqual(Vector2 pt1, Vector2 pt2, Vector2 pt3, Vector2 pt4) - { - var dif12 = pt1 - pt2; - var dif34 = pt3 - pt4; - - return (dif12.Y * dif34.X) - (dif12.X * dif34.Y) == 0; - } - - private static void InitEdge(TEdge e, TEdge eNext, TEdge ePrev, Vector2 pt) - { - e.Next = eNext; - e.Prev = ePrev; - e.Curr = pt; - e.OutIdx = Unassigned; - } - - private static OutRec ParseFirstLeft(OutRec firstLeft) - { - while (firstLeft != null && firstLeft.Pts == null) - { - firstLeft = firstLeft.FirstLeft; - } - - return firstLeft; - } - - private static bool Pt2IsBetweenPt1AndPt3(Vector2 pt1, Vector2 pt2, Vector2 pt3) - { - if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) - { - return false; - } - else if (pt1.X != pt3.X) - { - return (pt2.X > pt1.X) == (pt2.X < pt3.X); - } - else - { - return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); - } - } - - private static TEdge RemoveEdge(TEdge e) - { - // removes e from double_linked_list (but without removing from memory) - e.Prev.Next = e.Next; - e.Next.Prev = e.Prev; - TEdge result = e.Next; - e.Prev = null; // flag as removed (see ClipperBase.Clear) - return result; - } - - private static void ReverseHorizontal(TEdge e) - { - // swap horizontal edges' top and bottom x's so they follow the natural - // progression of the bounds - ie so their xbots will align with the - // adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - Swap(ref e.Top.X, ref e.Bot.X); - } - - private void InsertMaxima(float x) - { - // double-linked list: sorted ascending, ignoring dups. - Maxima newMax = new Maxima(); - newMax.X = x; - if (this.maxima == null) - { - this.maxima = newMax; - this.maxima.Next = null; - this.maxima.Prev = null; - } - else if (x < this.maxima.X) - { - newMax.Next = this.maxima; - newMax.Prev = null; - this.maxima = newMax; - } - else - { - Maxima m = this.maxima; - while (m.Next != null && (x >= m.Next.X)) - { - m = m.Next; - } - - if (x == m.X) - { - return; // ie ignores duplicates (& CG to clean up newMax) - } - - // insert newMax between m and m.Next ... - newMax.Next = m.Next; - newMax.Prev = m; - if (m.Next != null) - { - m.Next.Prev = newMax; - } - - m.Next = newMax; - } - } - - private bool ExecuteInternal() - { - try - { - this.Reset(); - this.sortedEdges = null; - this.maxima = null; - - float botY, topY; - if (!this.PopScanbeam(out botY)) - { - return false; - } - - this.InsertLocalMinimaIntoAEL(botY); - while (this.PopScanbeam(out topY) || this.LocalMinimaPending()) - { - this.ProcessHorizontals(); - this.ghostJoins.Clear(); - if (!this.ProcessIntersections(topY)) - { - return false; - } - - this.ProcessEdgesAtTopOfScanbeam(topY); - botY = topY; - this.InsertLocalMinimaIntoAEL(botY); - } - - // fix orientations ... - foreach (OutRec outRec in this.polyOuts) - { - if (outRec.Pts == null || outRec.IsOpen) - { - continue; - } - } - - this.JoinCommonEdges(); - - foreach (OutRec outRec in this.polyOuts) - { - if (outRec.Pts == null) - { - continue; - } - else if (outRec.IsOpen) - { - this.FixupOutPolyline(outRec); - } - else - { - this.FixupOutPolygon(outRec); - } - } - - return true; - } - finally - { - this.joins.Clear(); - this.ghostJoins.Clear(); - } - } - - private void DisposeAllPolyPts() - { - for (int i = 0; i < this.polyOuts.Count; ++i) - { - this.DisposeOutRec(i); - } - - this.polyOuts.Clear(); - } - - private void AddJoin(OutPt op1, OutPt op2, Vector2 offPt) - { - Join j = new Join(); - j.OutPt1 = op1; - j.OutPt2 = op2; - j.OffPt = offPt; - this.joins.Add(j); - } - - private void AddGhostJoin(OutPt op, Vector2 offPt) - { - Join j = new Join(); - j.OutPt1 = op; - j.OffPt = offPt; - this.ghostJoins.Add(j); - } - - private void InsertLocalMinimaIntoAEL(float botY) - { - LocalMinima lm; - while (this.PopLocalMinima(botY, out lm)) - { - TEdge lb = lm.LeftBound; - TEdge rb = lm.RightBound; - - OutPt op1 = null; - if (lb == null) - { - this.InsertEdgeIntoAEL(rb, null); - this.SetWindingCount(rb); - if (this.IsContributing(rb)) - { - op1 = this.AddOutPt(rb, rb.Bot); - } - } - else if (rb == null) - { - this.InsertEdgeIntoAEL(lb, null); - this.SetWindingCount(lb); - if (this.IsContributing(lb)) - { - op1 = this.AddOutPt(lb, lb.Bot); - } - - this.InsertScanbeam(lb.Top.Y); - } - else - { - this.InsertEdgeIntoAEL(lb, null); - this.InsertEdgeIntoAEL(rb, lb); - this.SetWindingCount(lb); - rb.WindCnt = lb.WindCnt; - rb.WindCnt2 = lb.WindCnt2; - if (this.IsContributing(lb)) - { - op1 = this.AddLocalMinPoly(lb, rb, lb.Bot); - } - - this.InsertScanbeam(lb.Top.Y); - } - - if (rb != null) - { - if (IsHorizontal(rb)) - { - if (rb.NextInLML != null) - { - this.InsertScanbeam(rb.NextInLML.Top.Y); - } - - this.AddEdgeToSEL(rb); - } - else - { - this.InsertScanbeam(rb.Top.Y); - } - } - - if (lb == null || rb == null) - { - continue; - } - - // if output polygons share an Edge with a horizontal rb, they'll need joining later ... - if (op1 != null && IsHorizontal(rb) && - this.ghostJoins.Count > 0 && rb.WindDelta != 0) - { - for (int i = 0; i < this.ghostJoins.Count; i++) - { - // if the horizontal Rb and a 'ghost' horizontal overlap, then convert - // the 'ghost' join to a real join ready for later ... - Join j = this.ghostJoins[i]; - if (HorzSegmentsOverlap(j.OutPt1.Pt.X, j.OffPt.X, rb.Bot.X, rb.Top.X)) - { - this.AddJoin(j.OutPt1, op1, j.OffPt); - } - } - } - - if (lb.OutIdx >= 0 && lb.PrevInAEL != null && - lb.PrevInAEL.Curr.X == lb.Bot.X && - lb.PrevInAEL.OutIdx >= 0 && - SlopesEqual(lb.PrevInAEL.Curr, lb.PrevInAEL.Top, lb.Curr, lb.Top) && - lb.WindDelta != 0 && lb.PrevInAEL.WindDelta != 0) - { - OutPt op2 = this.AddOutPt(lb.PrevInAEL, lb.Bot); - this.AddJoin(op1, op2, lb.Top); - } - - if (lb.NextInAEL != rb) - { - if (rb.OutIdx >= 0 && rb.PrevInAEL.OutIdx >= 0 && - SlopesEqual(rb.PrevInAEL.Curr, rb.PrevInAEL.Top, rb.Curr, rb.Top) && - rb.WindDelta != 0 && rb.PrevInAEL.WindDelta != 0) - { - OutPt op2 = this.AddOutPt(rb.PrevInAEL, rb.Bot); - this.AddJoin(op1, op2, rb.Top); - } - - TEdge e = lb.NextInAEL; - if (e != null) - { - while (e != rb) - { - // nb: For calculating winding counts etc, IntersectEdges() assumes - // that param1 will be to the right of param2 ABOVE the intersection ... - this.IntersectEdges(rb, e, lb.Curr); // order important here - e = e.NextInAEL; - } - } - } - } - } - - private void InsertEdgeIntoAEL(TEdge edge, TEdge startEdge) - { - if (this.activeEdges == null) - { - edge.PrevInAEL = null; - edge.NextInAEL = null; - this.activeEdges = edge; - } - else if (startEdge == null && this.E2InsertsBeforeE1(this.activeEdges, edge)) - { - edge.PrevInAEL = null; - edge.NextInAEL = this.activeEdges; - this.activeEdges.PrevInAEL = edge; - this.activeEdges = edge; - } - else - { - if (startEdge == null) - { - startEdge = this.activeEdges; - } - - while (startEdge.NextInAEL != null && - !this.E2InsertsBeforeE1(startEdge.NextInAEL, edge)) - { - startEdge = startEdge.NextInAEL; - } - - edge.NextInAEL = startEdge.NextInAEL; - if (startEdge.NextInAEL != null) - { - startEdge.NextInAEL.PrevInAEL = edge; - } - - edge.PrevInAEL = startEdge; - startEdge.NextInAEL = edge; - } - } - - private bool E2InsertsBeforeE1(TEdge e1, TEdge e2) - { - if (e2.Curr.X == e1.Curr.X) - { - if (e2.Top.Y > e1.Top.Y) - { - return e2.Top.X < TopX(e1, e2.Top.Y); - } - else - { - return e1.Top.X > TopX(e2, e1.Top.Y); - } - } - else - { - return e2.Curr.X < e1.Curr.X; - } - } - - private bool IsContributing(TEdge edge) - { - // return false if a subj line has been flagged as inside a subj polygon - if (edge.WindDelta == 0 && edge.WindCnt != 1) - { - return false; - } - - if (edge.PolyTyp == PolyType.Subject) - { - return edge.WindCnt2 == 0; - } - else - { - return edge.WindCnt2 != 0; - } - } - - private void SetWindingCount(TEdge edge) - { - TEdge e = edge.PrevInAEL; - - // find the edge of the same polytype that immediately preceeds 'edge' in AEL - while (e != null && ((e.PolyTyp != edge.PolyTyp) || (e.WindDelta == 0))) - { - e = e.PrevInAEL; - } - - if (e == null) - { - if (edge.WindDelta == 0) - { - edge.WindCnt = 1; - } - else - { - edge.WindCnt = edge.WindDelta; - } - - edge.WindCnt2 = 0; - e = this.activeEdges; // ie get ready to calc WindCnt2 - } - else if (edge.WindDelta == 0) - { - edge.WindCnt = 1; - edge.WindCnt2 = e.WindCnt2; - e = e.NextInAEL; // ie get ready to calc WindCnt2 - } - else - { - // EvenOdd filling ... - if (edge.WindDelta == 0) - { - // are we inside a subj polygon ... - bool inside = true; - TEdge e2 = e.PrevInAEL; - while (e2 != null) - { - if (e2.PolyTyp == e.PolyTyp && e2.WindDelta != 0) - { - inside = !inside; - } - - e2 = e2.PrevInAEL; - } - - edge.WindCnt = inside ? 0 : 1; - } - else - { - edge.WindCnt = edge.WindDelta; - } - - edge.WindCnt2 = e.WindCnt2; - e = e.NextInAEL; // ie get ready to calc WindCnt2 - } - - // update WindCnt2 ... - // EvenOdd filling ... - while (e != edge) - { - if (e.WindDelta != 0) - { - edge.WindCnt2 = edge.WindCnt2 == 0 ? 1 : 0; - } - - e = e.NextInAEL; - } - } - - private void AddEdgeToSEL(TEdge edge) - { - // SEL pointers in PEdge are use to build transient lists of horizontal edges. - // However, since we don't need to worry about processing order, all additions - // are made to the front of the list ... - if (this.sortedEdges == null) - { - this.sortedEdges = edge; - edge.PrevInSEL = null; - edge.NextInSEL = null; - } - else - { - edge.NextInSEL = this.sortedEdges; - edge.PrevInSEL = null; - this.sortedEdges.PrevInSEL = edge; - this.sortedEdges = edge; - } - } - - private bool PopEdgeFromSEL(out TEdge e) - { - // Pop edge from front of SEL (ie SEL is a FILO list) - e = this.sortedEdges; - if (e == null) - { - return false; - } - - TEdge oldE = e; - this.sortedEdges = e.NextInSEL; - if (this.sortedEdges != null) - { - this.sortedEdges.PrevInSEL = null; - } - - oldE.NextInSEL = null; - oldE.PrevInSEL = null; - return true; - } - - private void CopyAELToSEL() - { - TEdge e = this.activeEdges; - this.sortedEdges = e; - while (e != null) - { - e.PrevInSEL = e.PrevInAEL; - e.NextInSEL = e.NextInAEL; - e = e.NextInAEL; - } - } - - private void SwapPositionsInSEL(TEdge edge1, TEdge edge2) - { - if (edge1.NextInSEL == null && edge1.PrevInSEL == null) - { - return; - } - - if (edge2.NextInSEL == null && edge2.PrevInSEL == null) - { - return; - } - - if (edge1.NextInSEL == edge2) - { - TEdge next = edge2.NextInSEL; - if (next != null) - { - next.PrevInSEL = edge1; - } - - TEdge prev = edge1.PrevInSEL; - if (prev != null) - { - prev.NextInSEL = edge2; - } - - edge2.PrevInSEL = prev; - edge2.NextInSEL = edge1; - edge1.PrevInSEL = edge2; - edge1.NextInSEL = next; - } - else if (edge2.NextInSEL == edge1) - { - TEdge next = edge1.NextInSEL; - if (next != null) - { - next.PrevInSEL = edge2; - } - - TEdge prev = edge2.PrevInSEL; - if (prev != null) - { - prev.NextInSEL = edge1; - } - - edge1.PrevInSEL = prev; - edge1.NextInSEL = edge2; - edge2.PrevInSEL = edge1; - edge2.NextInSEL = next; - } - else - { - TEdge next = edge1.NextInSEL; - TEdge prev = edge1.PrevInSEL; - edge1.NextInSEL = edge2.NextInSEL; - if (edge1.NextInSEL != null) - { - edge1.NextInSEL.PrevInSEL = edge1; - } - - edge1.PrevInSEL = edge2.PrevInSEL; - if (edge1.PrevInSEL != null) - { - edge1.PrevInSEL.NextInSEL = edge1; - } - - edge2.NextInSEL = next; - if (edge2.NextInSEL != null) - { - edge2.NextInSEL.PrevInSEL = edge2; - } - - edge2.PrevInSEL = prev; - if (edge2.PrevInSEL != null) - { - edge2.PrevInSEL.NextInSEL = edge2; - } - } - - if (edge1.PrevInSEL == null) - { - this.sortedEdges = edge1; - } - else if (edge2.PrevInSEL == null) - { - this.sortedEdges = edge2; - } - } - - private void AddLocalMaxPoly(TEdge e1, TEdge e2, Vector2 pt) - { - this.AddOutPt(e1, pt); - if (e2.WindDelta == 0) - { - this.AddOutPt(e2, pt); - } - - if (e1.OutIdx == e2.OutIdx) - { - e1.OutIdx = Unassigned; - e2.OutIdx = Unassigned; - } - else if (e1.OutIdx < e2.OutIdx) - { - this.AppendPolygon(e1, e2); - } - else - { - this.AppendPolygon(e2, e1); - } - } - - private OutPt AddLocalMinPoly(TEdge e1, TEdge e2, Vector2 pt) - { - OutPt result; - TEdge e, prevE; - if (IsHorizontal(e2) || (e1.Dx > e2.Dx)) - { - result = this.AddOutPt(e1, pt); - e2.OutIdx = e1.OutIdx; - e1.Side = EdgeSide.Left; - e2.Side = EdgeSide.Right; - e = e1; - if (e.PrevInAEL == e2) - { - prevE = e2.PrevInAEL; - } - else - { - prevE = e.PrevInAEL; - } - } - else - { - result = this.AddOutPt(e2, pt); - e1.OutIdx = e2.OutIdx; - e1.Side = EdgeSide.Right; - e2.Side = EdgeSide.Left; - e = e2; - if (e.PrevInAEL == e1) - { - prevE = e1.PrevInAEL; - } - else - { - prevE = e.PrevInAEL; - } - } - - if (prevE != null && prevE.OutIdx >= 0) - { - float xPrev = TopX(prevE, pt.Y); - float xE = TopX(e, pt.Y); - if ((xPrev == xE) && - (e.WindDelta != 0) && - (prevE.WindDelta != 0) && - SlopesEqual(new Vector2(xPrev, pt.Y), prevE.Top, new Vector2(xE, pt.Y), e.Top)) - { - OutPt outPt = this.AddOutPt(prevE, pt); - this.AddJoin(result, outPt, e.Top); - } - } - - return result; - } - - private OutPt AddOutPt(TEdge e, Vector2 pt) - { - if (e.OutIdx < 0) - { - OutRec outRec = this.CreateOutRec(); - outRec.SourcePath = e.SourcePath; // copy source from edge to outrec - outRec.IsOpen = e.WindDelta == 0; - OutPt newOp = new OutPt(); - outRec.Pts = newOp; - newOp.Idx = outRec.Idx; - newOp.Pt = pt; - newOp.Next = newOp; - newOp.Prev = newOp; - if (!outRec.IsOpen) - { - this.SetHoleState(e, outRec); - } - - e.OutIdx = outRec.Idx; // nb: do this after SetZ ! - return newOp; - } - else - { - OutRec outRec = this.polyOuts[e.OutIdx]; - - if (outRec.SourcePath != e.SourcePath) - { - // this edge was from a different/unknown source - outRec.SourcePath = null; // drop source form output - } - - // OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' - OutPt op = outRec.Pts; - bool toFront = e.Side == EdgeSide.Left; - if (toFront && pt == op.Pt) - { - return op; - } - else if (!toFront && pt == op.Prev.Pt) - { - return op.Prev; - } - - // do we need to move the source to the point??? - OutPt newOp = new OutPt(); - newOp.Idx = outRec.Idx; - newOp.Pt = pt; - newOp.Next = op; - newOp.Prev = op.Prev; - newOp.Prev.Next = newOp; - op.Prev = newOp; - if (toFront) - { - outRec.Pts = newOp; - } - - return newOp; - } - } - - private OutPt GetLastOutPt(TEdge e) - { - OutRec outRec = this.polyOuts[e.OutIdx]; - if (e.Side == EdgeSide.Left) - { - return outRec.Pts; - } - else - { - return outRec.Pts.Prev; - } - } - - private void SetHoleState(TEdge e, OutRec outRec) - { - TEdge e2 = e.PrevInAEL; - TEdge eTmp = null; - while (e2 != null) - { - if (e2.OutIdx >= 0 && e2.WindDelta != 0) - { - if (eTmp == null) - { - eTmp = e2; - } - else if (eTmp.OutIdx == e2.OutIdx) - { - eTmp = null; // paired - } - } - - e2 = e2.PrevInAEL; - } - - if (eTmp == null) - { - outRec.FirstLeft = null; - outRec.IsHole = false; - } - else - { - outRec.FirstLeft = this.polyOuts[eTmp.OutIdx]; - outRec.IsHole = !outRec.FirstLeft.IsHole; - } - } - - private bool FirstIsBottomPt(OutPt btmPt1, OutPt btmPt2) - { - OutPt p = btmPt1.Prev; - while ((p.Pt == btmPt1.Pt) && (p != btmPt1)) - { - p = p.Prev; - } - - double dx1p = Math.Abs(GetDx(btmPt1.Pt, p.Pt)); - p = btmPt1.Next; - while ((p.Pt == btmPt1.Pt) && (p != btmPt1)) - { - p = p.Next; - } - - double dx1n = Math.Abs(GetDx(btmPt1.Pt, p.Pt)); - - p = btmPt2.Prev; - while ((p.Pt == btmPt2.Pt) && (p != btmPt2)) - { - p = p.Prev; - } - - double dx2p = Math.Abs(GetDx(btmPt2.Pt, p.Pt)); - p = btmPt2.Next; - while ((p.Pt == btmPt2.Pt) && (p != btmPt2)) - { - p = p.Next; - } - - double dx2n = Math.Abs(GetDx(btmPt2.Pt, p.Pt)); - - if (Math.Max(dx1p, dx1n) == Math.Max(dx2p, dx2n) && - Math.Min(dx1p, dx1n) == Math.Min(dx2p, dx2n)) - { - return this.Area(btmPt1) > 0; // if otherwise identical use orientation - } - else - { - return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); - } - } - - private OutPt GetBottomPt(OutPt pp) - { - OutPt dups = null; - OutPt p = pp.Next; - while (p != pp) - { - if (p.Pt.Y > pp.Pt.Y) - { - pp = p; - dups = null; - } - else if (p.Pt.Y == pp.Pt.Y && p.Pt.X <= pp.Pt.X) - { - if (p.Pt.X < pp.Pt.X) - { - dups = null; - pp = p; - } - else - { - if (p.Next != pp && p.Prev != pp) - { - dups = p; - } - } - } - - p = p.Next; - } - - if (dups != null) - { - // there appears to be at least 2 vertices at bottomPt so ... - while (dups != p) - { - if (!this.FirstIsBottomPt(p, dups)) - { - pp = dups; - } - - dups = dups.Next; - while (dups.Pt != pp.Pt) - { - dups = dups.Next; - } - } - } - - return pp; - } - - private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) - { - // work out which polygon fragment has the correct hole state ... - if (outRec1.BottomPt == null) - { - outRec1.BottomPt = this.GetBottomPt(outRec1.Pts); - } - - if (outRec2.BottomPt == null) - { - outRec2.BottomPt = this.GetBottomPt(outRec2.Pts); - } - - OutPt bPt1 = outRec1.BottomPt; - OutPt bPt2 = outRec2.BottomPt; - if (bPt1.Pt.Y > bPt2.Pt.Y) - { - return outRec1; - } - else if (bPt1.Pt.Y < bPt2.Pt.Y) - { - return outRec2; - } - else if (bPt1.Pt.X < bPt2.Pt.X) - { - return outRec1; - } - else if (bPt1.Pt.X > bPt2.Pt.X) - { - return outRec2; - } - else if (bPt1.Next == bPt1) - { - return outRec2; - } - else if (bPt2.Next == bPt2) - { - return outRec1; - } - else if (this.FirstIsBottomPt(bPt1, bPt2)) - { - return outRec1; - } - else - { - return outRec2; - } - } - - private bool OutRec1RightOfOutRec2(OutRec outRec1, OutRec outRec2) - { - do - { - outRec1 = outRec1.FirstLeft; - if (outRec1 == outRec2) - { - return true; - } - } - while (outRec1 != null); - - return false; - } - - private OutRec GetOutRec(int idx) - { - OutRec outrec = this.polyOuts[idx]; - while (outrec != this.polyOuts[outrec.Idx]) - { - outrec = this.polyOuts[outrec.Idx]; - } - - return outrec; - } - - private void AppendPolygon(TEdge e1, TEdge e2) - { - OutRec outRec1 = this.polyOuts[e1.OutIdx]; - OutRec outRec2 = this.polyOuts[e2.OutIdx]; - - OutRec holeStateRec; - if (this.OutRec1RightOfOutRec2(outRec1, outRec2)) - { - holeStateRec = outRec2; - } - else if (this.OutRec1RightOfOutRec2(outRec2, outRec1)) - { - holeStateRec = outRec1; - } - else - { - holeStateRec = this.GetLowermostRec(outRec1, outRec2); - } - - // get the start and ends of both output polygons and - // join E2 poly onto E1 poly and delete pointers to E2 ... - OutPt p1_lft = outRec1.Pts; - OutPt p1_rt = p1_lft.Prev; - OutPt p2_lft = outRec2.Pts; - OutPt p2_rt = p2_lft.Prev; - - // join e2 poly onto e1 poly and delete pointers to e2 ... - if (e1.Side == EdgeSide.Left) - { - if (e2.Side == EdgeSide.Left) - { - // z y x a b c - this.ReversePolyPtLinks(p2_lft); - p2_lft.Next = p1_lft; - p1_lft.Prev = p2_lft; - p1_rt.Next = p2_rt; - p2_rt.Prev = p1_rt; - outRec1.Pts = p2_rt; - } - else - { - // x y z a b c - p2_rt.Next = p1_lft; - p1_lft.Prev = p2_rt; - p2_lft.Prev = p1_rt; - p1_rt.Next = p2_lft; - outRec1.Pts = p2_lft; - } - } - else - { - if (e2.Side == EdgeSide.Right) - { - // a b c z y x - this.ReversePolyPtLinks(p2_lft); - p1_rt.Next = p2_rt; - p2_rt.Prev = p1_rt; - p2_lft.Next = p1_lft; - p1_lft.Prev = p2_lft; - } - else - { - // a b c x y z - p1_rt.Next = p2_lft; - p2_lft.Prev = p1_rt; - p1_lft.Prev = p2_rt; - p2_rt.Next = p1_lft; - } - } - - outRec1.BottomPt = null; - if (holeStateRec == outRec2) - { - if (outRec2.FirstLeft != outRec1) - { - outRec1.FirstLeft = outRec2.FirstLeft; - } - - outRec1.IsHole = outRec2.IsHole; - } - - outRec2.Pts = null; - outRec2.BottomPt = null; - - outRec2.FirstLeft = outRec1; - - int okIdx = e1.OutIdx; - int obsoleteIdx = e2.OutIdx; - - e1.OutIdx = Unassigned; // nb: safe because we only get here via AddLocalMaxPoly - e2.OutIdx = Unassigned; - - TEdge e = this.activeEdges; - while (e != null) - { - if (e.OutIdx == obsoleteIdx) - { - e.OutIdx = okIdx; - e.Side = e1.Side; - break; - } - - e = e.NextInAEL; - } - - outRec2.Idx = outRec1.Idx; - } - - private void ReversePolyPtLinks(OutPt pp) - { - if (pp == null) - { - return; - } - - OutPt pp1; - OutPt pp2; - pp1 = pp; - do - { - pp2 = pp1.Next; - pp1.Next = pp1.Prev; - pp1.Prev = pp2; - pp1 = pp2; - } - while (pp1 != pp); - } - - private void IntersectEdges(TEdge e1, TEdge e2, Vector2 pt) - { - // e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before - // e2 in AEL except when e1 is being inserted at the intersection point ... - bool e1Contributing = e1.OutIdx >= 0; - bool e2Contributing = e2.OutIdx >= 0; - - // update winding counts... - // assumes that e1 will be to the Right of e2 ABOVE the intersection - if (e1.PolyTyp == e2.PolyTyp) - { - int oldE1WindCnt = e1.WindCnt; - e1.WindCnt = e2.WindCnt; - e2.WindCnt = oldE1WindCnt; - } - else - { - e1.WindCnt2 = (e1.WindCnt2 == 0) ? 1 : 0; - e2.WindCnt2 = (e2.WindCnt2 == 0) ? 1 : 0; - } - - int e1Wc, e2Wc; - e1Wc = Math.Abs(e1.WindCnt); - e2Wc = Math.Abs(e2.WindCnt); - - if (e1Contributing && e2Contributing) - { - if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || - (e1.PolyTyp != e2.PolyTyp)) - { - this.AddLocalMaxPoly(e1, e2, pt); - } - else - { - this.AddOutPt(e1, pt); - this.AddOutPt(e2, pt); - SwapSides(e1, e2); - SwapPolyIndexes(e1, e2); - } - } - else if (e1Contributing) - { - if (e2Wc == 0 || e2Wc == 1) - { - this.AddOutPt(e1, pt); - SwapSides(e1, e2); - SwapPolyIndexes(e1, e2); - } - } - else if (e2Contributing) - { - if (e1Wc == 0 || e1Wc == 1) - { - this.AddOutPt(e2, pt); - SwapSides(e1, e2); - SwapPolyIndexes(e1, e2); - } - } - else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) - { - // neither edge is currently contributing ... - float e1Wc2, e2Wc2; - - e1Wc2 = Math.Abs(e1.WindCnt2); - e2Wc2 = Math.Abs(e2.WindCnt2); - - if (e1.PolyTyp != e2.PolyTyp) - { - this.AddLocalMinPoly(e1, e2, pt); - } - else if (e1Wc == 1 && e2Wc == 1) - { - if (((e1.PolyTyp == PolyType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((e1.PolyTyp == PolyType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - { - this.AddLocalMinPoly(e1, e2, pt); - } - } - else - { - SwapSides(e1, e2); - } - } - } - - private void ProcessHorizontals() - { - TEdge horzEdge; // m_SortedEdges; - while (this.PopEdgeFromSEL(out horzEdge)) - { - this.ProcessHorizontal(horzEdge); - } - } - - private void GetHorzDirection(TEdge horzEdge, out Direction dir, out float left, out float right) - { - if (horzEdge.Bot.X < horzEdge.Top.X) - { - left = horzEdge.Bot.X; - right = horzEdge.Top.X; - dir = Direction.LeftToRight; - } - else - { - left = horzEdge.Top.X; - right = horzEdge.Bot.X; - dir = Direction.RightToLeft; - } - } - - private void ProcessHorizontal(TEdge horzEdge) - { - Direction dir; - float horzLeft, horzRight; - bool isOpen = horzEdge.WindDelta == 0; - - this.GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight); - - TEdge eLastHorz = horzEdge, eMaxPair = null; - while (eLastHorz.NextInLML != null && IsHorizontal(eLastHorz.NextInLML)) - { - eLastHorz = eLastHorz.NextInLML; - } - - if (eLastHorz.NextInLML == null) - { - eMaxPair = this.GetMaximaPair(eLastHorz); - } - - Maxima currMax = this.maxima; - if (currMax != null) - { - // get the first maxima in range (X) ... - if (dir == Direction.LeftToRight) - { - while (currMax != null && currMax.X <= horzEdge.Bot.X) - { - currMax = currMax.Next; - } - - if (currMax != null && currMax.X >= eLastHorz.Top.X) - { - currMax = null; - } - } - else - { - while (currMax.Next != null && currMax.Next.X < horzEdge.Bot.X) - { - currMax = currMax.Next; - } - - if (currMax.X <= eLastHorz.Top.X) - { - currMax = null; - } - } - } - - OutPt op1 = null; - - // loop through consec. horizontal edges - while (true) - { - bool isLastHorz = horzEdge == eLastHorz; - TEdge e = this.GetNextInAEL(horzEdge, dir); - while (e != null) - { - // this code block inserts extra coords into horizontal edges (in output - // polygons) whereever maxima touch these horizontal edges. This helps - // 'simplifying' polygons (ie if the Simplify property is set). - if (currMax != null) - { - if (dir == Direction.LeftToRight) - { - while (currMax != null && currMax.X < e.Curr.X) - { - if (horzEdge.OutIdx >= 0 && !isOpen) - { - this.AddOutPt(horzEdge, new Vector2(currMax.X, horzEdge.Bot.Y)); - } - - currMax = currMax.Next; - } - } - else - { - while (currMax != null && currMax.X > e.Curr.X) - { - if (horzEdge.OutIdx >= 0 && !isOpen) - { - this.AddOutPt(horzEdge, new Vector2(currMax.X, horzEdge.Bot.Y)); - } - - currMax = currMax.Prev; - } - } - } - - if ((dir == Direction.LeftToRight && e.Curr.X > horzRight) || - (dir == Direction.RightToLeft && e.Curr.X < horzLeft)) - { - break; - } - - // Also break if we've got to the end of an intermediate horizontal edge ... - // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. - if (e.Curr.X == horzEdge.Top.X && horzEdge.NextInLML != null && - e.Dx < horzEdge.NextInLML.Dx) - { - break; - } - - // note: may be done multiple times - if (horzEdge.OutIdx >= 0 && !isOpen) - { - op1 = this.AddOutPt(horzEdge, e.Curr); - TEdge eNextHorz = this.sortedEdges; - while (eNextHorz != null) - { - if (eNextHorz.OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge.Bot.X, horzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X)) - { - OutPt op2 = this.GetLastOutPt(eNextHorz); - this.AddJoin(op2, op1, eNextHorz.Top); - } - - eNextHorz = eNextHorz.NextInSEL; - } - - this.AddGhostJoin(op1, horzEdge.Bot); - } - - // OK, so far we're still in range of the horizontal Edge but make sure - // we're at the last of consec. horizontals when matching with eMaxPair - if (e == eMaxPair && isLastHorz) - { - if (horzEdge.OutIdx >= 0) - { - this.AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge.Top); - } - - this.DeleteFromAEL(horzEdge); - this.DeleteFromAEL(eMaxPair); - return; - } - - if (dir == Direction.LeftToRight) - { - Vector2 pt = new Vector2(e.Curr.X, horzEdge.Curr.Y); - this.IntersectEdges(horzEdge, e, pt); - } - else - { - Vector2 pt = new Vector2(e.Curr.X, horzEdge.Curr.Y); - this.IntersectEdges(e, horzEdge, pt); - } - - TEdge eNext = this.GetNextInAEL(e, dir); - this.SwapPositionsInAEL(horzEdge, e); - e = eNext; - } // end while(e != null) - - // Break out of loop if HorzEdge.NextInLML is not also horizontal ... - if (horzEdge.NextInLML == null || !IsHorizontal(horzEdge.NextInLML)) - { - break; - } - - this.UpdateEdgeIntoAEL(ref horzEdge); - if (horzEdge.OutIdx >= 0) - { - this.AddOutPt(horzEdge, horzEdge.Bot); - } - - this.GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight); - } - - if (horzEdge.OutIdx >= 0 && op1 == null) - { - op1 = this.GetLastOutPt(horzEdge); - TEdge eNextHorz = this.sortedEdges; - while (eNextHorz != null) - { - if (eNextHorz.OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge.Bot.X, horzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X)) - { - OutPt op2 = this.GetLastOutPt(eNextHorz); - this.AddJoin(op2, op1, eNextHorz.Top); - } - - eNextHorz = eNextHorz.NextInSEL; - } - - this.AddGhostJoin(op1, horzEdge.Top); - } - - if (horzEdge.NextInLML != null) - { - if (horzEdge.OutIdx >= 0) - { - op1 = this.AddOutPt(horzEdge, horzEdge.Top); - - this.UpdateEdgeIntoAEL(ref horzEdge); - if (horzEdge.WindDelta == 0) - { - return; - } - - // nb: HorzEdge is no longer horizontal here - TEdge ePrev = horzEdge.PrevInAEL; - TEdge eNext = horzEdge.NextInAEL; - if (ePrev != null && ePrev.Curr.X == horzEdge.Bot.X && - ePrev.Curr.Y == horzEdge.Bot.Y && ePrev.WindDelta != 0 && - (ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y && - SlopesEqual(horzEdge, ePrev))) - { - OutPt op2 = this.AddOutPt(ePrev, horzEdge.Bot); - this.AddJoin(op1, op2, horzEdge.Top); - } - else if (eNext != null && eNext.Curr.X == horzEdge.Bot.X && - eNext.Curr.Y == horzEdge.Bot.Y && eNext.WindDelta != 0 && - eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y && - SlopesEqual(horzEdge, eNext)) - { - OutPt op2 = this.AddOutPt(eNext, horzEdge.Bot); - this.AddJoin(op1, op2, horzEdge.Top); - } - } - else - { - this.UpdateEdgeIntoAEL(ref horzEdge); - } - } - else - { - if (horzEdge.OutIdx >= 0) - { - this.AddOutPt(horzEdge, horzEdge.Top); - } - - this.DeleteFromAEL(horzEdge); - } - } - - private TEdge GetNextInAEL(TEdge e, Direction direction) - { - return direction == Direction.LeftToRight ? e.NextInAEL : e.PrevInAEL; - } - - private bool IsMaxima(TEdge e, double y) - { - return e != null && e.Top.Y == y && e.NextInLML == null; - } - - private bool IsIntermediate(TEdge e, double y) - { - return e.Top.Y == y && e.NextInLML != null; - } - - private TEdge GetMaximaPair(TEdge e) - { - if ((e.Next.Top == e.Top) && e.Next.NextInLML == null) - { - return e.Next; - } - else if ((e.Prev.Top == e.Top) && e.Prev.NextInLML == null) - { - return e.Prev; - } - else - { - return null; - } - } - - private TEdge GetMaximaPairEx(TEdge e) - { - // as above but returns null if MaxPair isn't in AEL (unless it's horizontal) - TEdge result = this.GetMaximaPair(e); - if (result == null || result.OutIdx == Skip || - ((result.NextInAEL == result.PrevInAEL) && !IsHorizontal(result))) - { - return null; - } - - return result; - } - - private bool ProcessIntersections(float topY) - { - if (this.activeEdges == null) - { - return true; - } - - try - { - this.BuildIntersectList(topY); - if (this.intersectList.Count == 0) - { - return true; - } - - if (this.intersectList.Count == 1 || this.FixupIntersectionOrder()) - { - this.ProcessIntersectList(); - } - else - { - return false; - } - } - catch - { - this.sortedEdges = null; - this.intersectList.Clear(); - throw new ClipperException("ProcessIntersections error"); - } - - this.sortedEdges = null; - return true; - } - - private void BuildIntersectList(float topY) - { - if (this.activeEdges == null) - { - return; - } - - // prepare for sorting ... - TEdge e = this.activeEdges; - this.sortedEdges = e; - while (e != null) - { - e.PrevInSEL = e.PrevInAEL; - e.NextInSEL = e.NextInAEL; - e.Curr.X = TopX(e, topY); - e = e.NextInAEL; - } - - // bubblesort ... - bool isModified = true; - while (isModified && this.sortedEdges != null) - { - isModified = false; - e = this.sortedEdges; - while (e.NextInSEL != null) - { - TEdge eNext = e.NextInSEL; - Vector2 pt; - if (e.Curr.X > eNext.Curr.X) - { - this.IntersectPoint(e, eNext, out pt); - if (pt.Y < topY) - { - pt = new Vector2(TopX(e, topY), topY); - } - - IntersectNode newNode = new IntersectNode(); - newNode.Edge1 = e; - newNode.Edge2 = eNext; - newNode.Pt = pt; - this.intersectList.Add(newNode); - - this.SwapPositionsInSEL(e, eNext); - isModified = true; - } - else - { - e = eNext; - } - } - - if (e.PrevInSEL != null) - { - e.PrevInSEL.NextInSEL = null; - } - else - { - break; - } - } - - this.sortedEdges = null; - } - - private bool EdgesAdjacent(IntersectNode inode) - { - return (inode.Edge1.NextInSEL == inode.Edge2) || - (inode.Edge1.PrevInSEL == inode.Edge2); - } - - private bool FixupIntersectionOrder() - { - // pre-condition: intersections are sorted bottom-most first. - // Now it's crucial that intersections are made only between adjacent edges, - // so to ensure this the order of intersections may need adjusting ... - this.intersectList.Sort(this.intersectNodeComparer); - - this.CopyAELToSEL(); - int cnt = this.intersectList.Count; - for (int i = 0; i < cnt; i++) - { - if (!this.EdgesAdjacent(this.intersectList[i])) - { - int j = i + 1; - while (j < cnt && !this.EdgesAdjacent(this.intersectList[j])) - { - j++; - } - - if (j == cnt) - { - return false; - } - - IntersectNode tmp = this.intersectList[i]; - this.intersectList[i] = this.intersectList[j]; - this.intersectList[j] = tmp; - } - - this.SwapPositionsInSEL(this.intersectList[i].Edge1, this.intersectList[i].Edge2); - } - - return true; - } - - private void ProcessIntersectList() - { - for (int i = 0; i < this.intersectList.Count; i++) - { - IntersectNode iNode = this.intersectList[i]; - { - this.IntersectEdges(iNode.Edge1, iNode.Edge2, iNode.Pt); - this.SwapPositionsInAEL(iNode.Edge1, iNode.Edge2); - } - } - - this.intersectList.Clear(); - } - - private void IntersectPoint(TEdge edge1, TEdge edge2, out Vector2 ip) - { - ip = default(Vector2); - double b1, b2; - - // nb: with very large coordinate values, it's possible for SlopesEqual() to - // return false but for the edge.Dx value be equal due to double precision rounding. - if (edge1.Dx == edge2.Dx) - { - ip.Y = edge1.Curr.Y; - ip.X = TopX(edge1, ip.Y); - return; - } - - if (edge1.Delta.X == 0) - { - ip.X = edge1.Bot.X; - if (IsHorizontal(edge2)) - { - ip.Y = edge2.Bot.Y; - } - else - { - b2 = edge2.Bot.Y - (edge2.Bot.X / edge2.Dx); - ip.Y = Round((ip.X / edge2.Dx) + b2); - } - } - else if (edge2.Delta.X == 0) - { - ip.X = edge2.Bot.X; - if (IsHorizontal(edge1)) - { - ip.Y = edge1.Bot.Y; - } - else - { - b1 = edge1.Bot.Y - (edge1.Bot.X / edge1.Dx); - ip.Y = Round((ip.X / edge1.Dx) + b1); - } - } - else - { - b1 = edge1.Bot.X - (edge1.Bot.Y * edge1.Dx); - b2 = edge2.Bot.X - (edge2.Bot.Y * edge2.Dx); - double q = (b2 - b1) / (edge1.Dx - edge2.Dx); - ip.Y = Round(q); - if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx)) - { - ip.X = Round((edge1.Dx * q) + b1); - } - else - { - ip.X = Round((edge2.Dx * q) + b2); - } - } - - if (ip.Y < edge1.Top.Y || ip.Y < edge2.Top.Y) - { - if (edge1.Top.Y > edge2.Top.Y) - { - ip.Y = edge1.Top.Y; - } - else - { - ip.Y = edge2.Top.Y; - } - - if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx)) - { - ip.X = TopX(edge1, ip.Y); - } - else - { - ip.X = TopX(edge2, ip.Y); - } - } - - // finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... - if (ip.Y > edge1.Curr.Y) - { - ip.Y = edge1.Curr.Y; - - // better to use the more vertical edge to derive X ... - if (Math.Abs(edge1.Dx) > Math.Abs(edge2.Dx)) - { - ip.X = TopX(edge2, ip.Y); - } - else - { - ip.X = TopX(edge1, ip.Y); - } - } - } - - private void ProcessEdgesAtTopOfScanbeam(float topY) - { - TEdge e = this.activeEdges; - while (e != null) - { - // 1. process maxima, treating them as if they're 'bent' horizontal edges, - // but exclude maxima with horizontal edges. nb: e can't be a horizontal. - bool isMaximaEdge = this.IsMaxima(e, topY); - - if (isMaximaEdge) - { - TEdge eMaxPair = this.GetMaximaPairEx(e); - isMaximaEdge = eMaxPair == null || !IsHorizontal(eMaxPair); - } - - if (isMaximaEdge) - { - TEdge ePrev = e.PrevInAEL; - this.DoMaxima(e); - if (ePrev == null) - { - e = this.activeEdges; - } - else - { - e = ePrev.NextInAEL; - } - } - else - { - // 2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... - if (this.IsIntermediate(e, topY) && IsHorizontal(e.NextInLML)) - { - this.UpdateEdgeIntoAEL(ref e); - if (e.OutIdx >= 0) - { - this.AddOutPt(e, e.Bot); - } - - this.AddEdgeToSEL(e); - } - else - { - e.Curr.X = TopX(e, topY); - e.Curr.Y = topY; - } - - e = e.NextInAEL; - } - } - - // 3. Process horizontals at the Top of the scanbeam ... - this.ProcessHorizontals(); - this.maxima = null; - - // 4. Promote intermediate vertices ... - e = this.activeEdges; - while (e != null) - { - if (this.IsIntermediate(e, topY)) - { - OutPt op = null; - if (e.OutIdx >= 0) - { - op = this.AddOutPt(e, e.Top); - } - - this.UpdateEdgeIntoAEL(ref e); - - // if output polygons share an edge, they'll need joining later ... - TEdge ePrev = e.PrevInAEL; - TEdge eNext = e.NextInAEL; - if (ePrev != null && ePrev.Curr.X == e.Bot.X && - ePrev.Curr.Y == e.Bot.Y && op != null && - ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y && - SlopesEqual(e.Curr, e.Top, ePrev.Curr, ePrev.Top) && - (e.WindDelta != 0) && (ePrev.WindDelta != 0)) - { - OutPt op2 = this.AddOutPt(ePrev, e.Bot); - this.AddJoin(op, op2, e.Top); - } - else if (eNext != null && eNext.Curr.X == e.Bot.X && - eNext.Curr.Y == e.Bot.Y && op != null && - eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y && - SlopesEqual(e.Curr, e.Top, eNext.Curr, eNext.Top) && - (e.WindDelta != 0) && (eNext.WindDelta != 0)) - { - OutPt op2 = this.AddOutPt(eNext, e.Bot); - this.AddJoin(op, op2, e.Top); - } - } - - e = e.NextInAEL; - } - } - - private void DoMaxima(TEdge e) - { - TEdge eMaxPair = this.GetMaximaPairEx(e); - if (eMaxPair == null) - { - if (e.OutIdx >= 0) - { - this.AddOutPt(e, e.Top); - } - - this.DeleteFromAEL(e); - return; - } - - TEdge eNext = e.NextInAEL; - while (eNext != null && eNext != eMaxPair) - { - this.IntersectEdges(e, eNext, e.Top); - this.SwapPositionsInAEL(e, eNext); - eNext = e.NextInAEL; - } - - if (e.OutIdx == Unassigned && eMaxPair.OutIdx == Unassigned) - { - this.DeleteFromAEL(e); - this.DeleteFromAEL(eMaxPair); - } - else if (e.OutIdx >= 0 && eMaxPair.OutIdx >= 0) - { - if (e.OutIdx >= 0) - { - this.AddLocalMaxPoly(e, eMaxPair, e.Top); - } - - this.DeleteFromAEL(e); - this.DeleteFromAEL(eMaxPair); - } - else - { - throw new ClipperException("DoMaxima error"); - } - } - - private int PointCount(OutPt pts) - { - if (pts == null) - { - return 0; - } - - int result = 0; - OutPt p = pts; - do - { - result++; - p = p.Next; - } - while (p != pts); - return result; - } - - private void BuildResult2(PolyTree polytree) - { - polytree.Clear(); - - // add each output polygon/contour to polytree ... - polytree.AllPolys.Capacity = this.polyOuts.Count; - for (int i = 0; i < this.polyOuts.Count; i++) - { - OutRec outRec = this.polyOuts[i]; - int cnt = this.PointCount(outRec.Pts); - if ((outRec.IsOpen && cnt < 2) || - (!outRec.IsOpen && cnt < 3)) - { - continue; - } - - FixHoleLinkage(outRec); - PolyNode pn = new PolyNode(); - pn.SourcePath = outRec.SourcePath; - polytree.AllPolys.Add(pn); - outRec.PolyNode = pn; - pn.Polygon.Capacity = cnt; - OutPt op = outRec.Pts.Prev; - for (int j = 0; j < cnt; j++) - { - pn.Polygon.Add(op.Pt); - op = op.Prev; - } - } - - // fixup PolyNode links etc ... - polytree.Children.Capacity = this.polyOuts.Count; - for (int i = 0; i < this.polyOuts.Count; i++) - { - OutRec outRec = this.polyOuts[i]; - if (outRec.PolyNode == null) - { - continue; - } - else if (outRec.IsOpen) - { - outRec.PolyNode.IsOpen = true; - polytree.AddChild(outRec.PolyNode); - } - else if (outRec.FirstLeft != null && - outRec.FirstLeft.PolyNode != null) - { - outRec.FirstLeft.PolyNode.AddChild(outRec.PolyNode); - } - else - { - polytree.AddChild(outRec.PolyNode); - } - } - } - - private void FixupOutPolyline(OutRec outrec) - { - OutPt pp = outrec.Pts; - OutPt lastPP = pp.Prev; - while (pp != lastPP) - { - pp = pp.Next; - if (pp.Pt == pp.Prev.Pt) - { - if (pp == lastPP) - { - lastPP = pp.Prev; - } - - OutPt tmpPP = pp.Prev; - tmpPP.Next = pp.Next; - pp.Next.Prev = tmpPP; - pp = tmpPP; - } - } - - if (pp == pp.Prev) - { - outrec.Pts = null; - } - } - - private void FixupOutPolygon(OutRec outRec) - { - // FixupOutPolygon() - removes duplicate points and simplifies consecutive - // parallel edges by removing the middle vertex. - OutPt lastOK = null; - outRec.BottomPt = null; - OutPt pp = outRec.Pts; - while (true) - { - if (pp.Prev == pp || pp.Prev == pp.Next) - { - outRec.Pts = null; - return; - } - - // test for duplicate points and collinear edges ... - if ((pp.Pt == pp.Next.Pt) || (pp.Pt == pp.Prev.Pt) || - SlopesEqual(pp.Prev.Pt, pp.Pt, pp.Next.Pt)) - { - lastOK = null; - pp.Prev.Next = pp.Next; - pp.Next.Prev = pp.Prev; - pp = pp.Prev; - } - else if (pp == lastOK) - { - break; - } - else - { - if (lastOK == null) - { - lastOK = pp; - } - - pp = pp.Next; - } - } - - outRec.Pts = pp; - } - - private OutPt DupOutPt(OutPt outPt, bool insertAfter) - { - OutPt result = new OutPt(); - result.Pt = outPt.Pt; - result.Idx = outPt.Idx; - if (insertAfter) - { - result.Next = outPt.Next; - result.Prev = outPt; - outPt.Next.Prev = result; - outPt.Next = result; - } - else - { - result.Prev = outPt.Prev; - result.Next = outPt; - outPt.Prev.Next = result; - outPt.Prev = result; - } - - return result; - } - - private bool GetOverlap(float a1, float a2, float b1, float b2, out float left, out float right) - { - if (a1 < a2) - { - if (b1 < b2) - { - left = Math.Max(a1, b1); - right = Math.Min(a2, b2); - } - else - { - left = Math.Max(a1, b2); - right = Math.Min(a2, b1); - } - } - else - { - if (b1 < b2) - { - left = Math.Max(a2, b1); - right = Math.Min(a1, b2); - } - else - { - left = Math.Max(a2, b2); - right = Math.Min(a1, b1); - } - } - - return left < right; - } - - private bool JoinHorz(OutPt op1, OutPt op1b, OutPt op2, OutPt op2b, Vector2 pt, bool discardLeft) - { - Direction dir1 = op1.Pt.X > op1b.Pt.X ? Direction.RightToLeft : Direction.LeftToRight; - Direction dir2 = op2.Pt.X > op2b.Pt.X ? Direction.RightToLeft : Direction.LeftToRight; - if (dir1 == dir2) - { - return false; - } - - // When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we - // want Op1b to be on the Right. (And likewise with Op2 and Op2b.) - // So, to facilitate this while inserting Op1b and Op2b ... - // when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, - // otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) - if (dir1 == Direction.LeftToRight) - { - while (op1.Next.Pt.X <= pt.X && - op1.Next.Pt.X >= op1.Pt.X && op1.Next.Pt.Y == pt.Y) - { - op1 = op1.Next; - } - - if (discardLeft && (op1.Pt.X != pt.X)) - { - op1 = op1.Next; - } - - op1b = this.DupOutPt(op1, !discardLeft); - if (op1b.Pt != pt) - { - op1 = op1b; - op1.Pt = pt; - op1b = this.DupOutPt(op1, !discardLeft); - } - } - else - { - while (op1.Next.Pt.X >= pt.X && - op1.Next.Pt.X <= op1.Pt.X && - op1.Next.Pt.Y == pt.Y) - { - op1 = op1.Next; - } - - if (!discardLeft && (op1.Pt.X != pt.X)) - { - op1 = op1.Next; - } - - op1b = this.DupOutPt(op1, discardLeft); - if (op1b.Pt != pt) - { - op1 = op1b; - op1.Pt = pt; - op1b = this.DupOutPt(op1, discardLeft); - } - } - - if (dir2 == Direction.LeftToRight) - { - while (op2.Next.Pt.X <= pt.X && - op2.Next.Pt.X >= op2.Pt.X && - op2.Next.Pt.Y == pt.Y) - { - op2 = op2.Next; - } - - if (discardLeft && (op2.Pt.X != pt.X)) - { - op2 = op2.Next; - } - - op2b = this.DupOutPt(op2, !discardLeft); - if (op2b.Pt != pt) - { - op2 = op2b; - op2.Pt = pt; - op2b = this.DupOutPt(op2, !discardLeft); - } - } - else - { - while (op2.Next.Pt.X >= pt.X && - op2.Next.Pt.X <= op2.Pt.X && - op2.Next.Pt.Y == pt.Y) - { - op2 = op2.Next; - } - - if (!discardLeft && (op2.Pt.X != pt.X)) - { - op2 = op2.Next; - } - - op2b = this.DupOutPt(op2, discardLeft); - if (op2b.Pt != pt) - { - op2 = op2b; - op2.Pt = pt; - op2b = this.DupOutPt(op2, discardLeft); - } - } - - if ((dir1 == Direction.LeftToRight) == discardLeft) - { - op1.Prev = op2; - op2.Next = op1; - op1b.Next = op2b; - op2b.Prev = op1b; - } - else - { - op1.Next = op2; - op2.Prev = op1; - op1b.Prev = op2b; - op2b.Next = op1b; - } - - return true; - } - - private bool JoinPoints(Join j, OutRec outRec1, OutRec outRec2) - { - OutPt op1 = j.OutPt1, op1b; - OutPt op2 = j.OutPt2, op2b; - - // There are 3 kinds of joins for output polygons ... - // 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere - // along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). - // 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same - // location at the Bottom of the overlapping segment (& Join.OffPt is above). - // 3. StrictlySimple joins where edges touch but are not collinear and where - // Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. - bool isHorizontal = j.OutPt1.Pt.Y == j.OffPt.Y; - - if (isHorizontal && (j.OffPt == j.OutPt1.Pt) && (j.OffPt == j.OutPt2.Pt)) - { - // Strictly Simple join ... - if (outRec1 != outRec2) - { - return false; - } - - op1b = j.OutPt1.Next; - while (op1b != op1 && (op1b.Pt == j.OffPt)) - { - op1b = op1b.Next; - } - - bool reverse1 = op1b.Pt.Y > j.OffPt.Y; - op2b = j.OutPt2.Next; - while (op2b != op2 && (op2b.Pt == j.OffPt)) - { - op2b = op2b.Next; - } - - bool reverse2 = op2b.Pt.Y > j.OffPt.Y; - if (reverse1 == reverse2) - { - return false; - } - - if (reverse1) - { - op1b = this.DupOutPt(op1, false); - op2b = this.DupOutPt(op2, true); - op1.Prev = op2; - op2.Next = op1; - op1b.Next = op2b; - op2b.Prev = op1b; - j.OutPt1 = op1; - j.OutPt2 = op1b; - return true; - } - else - { - op1b = this.DupOutPt(op1, true); - op2b = this.DupOutPt(op2, false); - op1.Next = op2; - op2.Prev = op1; - op1b.Prev = op2b; - op2b.Next = op1b; - j.OutPt1 = op1; - j.OutPt2 = op1b; - return true; - } - } - else if (isHorizontal) - { - // treat horizontal joins differently to non-horizontal joins since with - // them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt - // may be anywhere along the horizontal edge. - op1b = op1; - while (op1.Prev.Pt.Y == op1.Pt.Y && op1.Prev != op1b && op1.Prev != op2) - { - op1 = op1.Prev; - } - - while (op1b.Next.Pt.Y == op1b.Pt.Y && op1b.Next != op1 && op1b.Next != op2) - { - op1b = op1b.Next; - } - - if (op1b.Next == op1 || op1b.Next == op2) - { - return false; // a flat 'polygon' - } - - op2b = op2; - while (op2.Prev.Pt.Y == op2.Pt.Y && op2.Prev != op2b && op2.Prev != op1b) - { - op2 = op2.Prev; - } - - while (op2b.Next.Pt.Y == op2b.Pt.Y && op2b.Next != op2 && op2b.Next != op1) - { - op2b = op2b.Next; - } - - if (op2b.Next == op2 || op2b.Next == op1) - { - return false; // a flat 'polygon' - } - - float left, right; - - // Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal edges - if (!this.GetOverlap(op1.Pt.X, op1b.Pt.X, op2.Pt.X, op2b.Pt.X, out left, out right)) - { - return false; - } - - // DiscardLeftSide: when overlapping edges are joined, a spike will created - // which needs to be cleaned up. However, we don't want Op1 or Op2 caught up - // on the discard Side as either may still be needed for other joins ... - Vector2 pt; - bool discardLeftSide; - if (op1.Pt.X >= left && op1.Pt.X <= right) - { - pt = op1.Pt; - discardLeftSide = op1.Pt.X > op1b.Pt.X; - } - else if (op2.Pt.X >= left && op2.Pt.X <= right) - { - pt = op2.Pt; - discardLeftSide = op2.Pt.X > op2b.Pt.X; - } - else if (op1b.Pt.X >= left && op1b.Pt.X <= right) - { - pt = op1b.Pt; - discardLeftSide = op1b.Pt.X > op1.Pt.X; - } - else - { - pt = op2b.Pt; - discardLeftSide = op2b.Pt.X > op2.Pt.X; - } - - j.OutPt1 = op1; - j.OutPt2 = op2; - return this.JoinHorz(op1, op1b, op2, op2b, pt, discardLeftSide); - } - else - { - // nb: For non-horizontal joins ... - // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y - // 2. Jr.OutPt1.Pt > Jr.OffPt.Y - - // make sure the polygons are correctly oriented ... - op1b = op1.Next; - while ((op1b.Pt == op1.Pt) && (op1b != op1)) - { - op1b = op1b.Next; - } - - bool reverse1 = (op1b.Pt.Y > op1.Pt.Y) || !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt); - if (reverse1) - { - op1b = op1.Prev; - while ((op1b.Pt == op1.Pt) && (op1b != op1)) - { - op1b = op1b.Prev; - } - - if ((op1b.Pt.Y > op1.Pt.Y) || - !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt)) - { - return false; - } - } - - op2b = op2.Next; - while ((op2b.Pt == op2.Pt) && (op2b != op2)) - { - op2b = op2b.Next; - } - - bool reverse2 = (op2b.Pt.Y > op2.Pt.Y) || !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt); - if (reverse2) - { - op2b = op2.Prev; - while ((op2b.Pt == op2.Pt) && (op2b != op2)) - { - op2b = op2b.Prev; - } - - if ((op2b.Pt.Y > op2.Pt.Y) || - !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt)) - { - return false; - } - } - - if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || - ((outRec1 == outRec2) && (reverse1 == reverse2))) - { - return false; - } - - if (reverse1) - { - op1b = this.DupOutPt(op1, false); - op2b = this.DupOutPt(op2, true); - op1.Prev = op2; - op2.Next = op1; - op1b.Next = op2b; - op2b.Prev = op1b; - j.OutPt1 = op1; - j.OutPt2 = op1b; - return true; - } - else - { - op1b = this.DupOutPt(op1, true); - op2b = this.DupOutPt(op2, false); - op1.Next = op2; - op2.Prev = op1; - op1b.Prev = op2b; - op2b.Next = op1b; - j.OutPt1 = op1; - j.OutPt2 = op1b; - return true; - } - } - } - - private void FixupFirstLefts1(OutRec oldOutRec, OutRec newOutRec) - { - foreach (OutRec outRec in this.polyOuts) - { - OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); - if (outRec.Pts != null && firstLeft == oldOutRec) - { - if (Poly2ContainsPoly1(outRec.Pts, newOutRec.Pts)) - { - outRec.FirstLeft = newOutRec; - } - } - } - } - - private void FixupFirstLefts2(OutRec innerOutRec, OutRec outerOutRec) - { - // A polygon has split into two such that one is now the inner of the other. - // It's possible that these polygons now wrap around other polygons, so check - // every polygon that's also contained by OuterOutRec's FirstLeft container - // (including nil) to see if they've become inner to the new inner polygon ... - OutRec orfl = outerOutRec.FirstLeft; - foreach (OutRec outRec in this.polyOuts) - { - if (outRec.Pts == null || outRec == outerOutRec || outRec == innerOutRec) - { - continue; - } - - OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); - if (firstLeft != orfl && firstLeft != innerOutRec && firstLeft != outerOutRec) - { - continue; - } - - if (Poly2ContainsPoly1(outRec.Pts, innerOutRec.Pts)) - { - outRec.FirstLeft = innerOutRec; - } - else if (Poly2ContainsPoly1(outRec.Pts, outerOutRec.Pts)) - { - outRec.FirstLeft = outerOutRec; - } - else if (outRec.FirstLeft == innerOutRec || outRec.FirstLeft == outerOutRec) - { - outRec.FirstLeft = orfl; - } - } - } - - private void FixupFirstLefts3(OutRec oldOutRec, OutRec newOutRec) - { - // same as FixupFirstLefts1 but doesn't call Poly2ContainsPoly1() - foreach (OutRec outRec in this.polyOuts) - { - OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); - if (outRec.Pts != null && outRec.FirstLeft == oldOutRec) - { - outRec.FirstLeft = newOutRec; - } - } - } - - private void JoinCommonEdges() - { - for (int i = 0; i < this.joins.Count; i++) - { - Join join = this.joins[i]; - - OutRec outRec1 = this.GetOutRec(join.OutPt1.Idx); - OutRec outRec2 = this.GetOutRec(join.OutPt2.Idx); - - if (outRec1.Pts == null || outRec2.Pts == null) - { - continue; - } - - if (outRec1.IsOpen || outRec2.IsOpen) - { - continue; - } - - // get the polygon fragment with the correct hole state (FirstLeft) - // before calling JoinPoints() ... - OutRec holeStateRec; - if (outRec1 == outRec2) - { - holeStateRec = outRec1; - } - else if (this.OutRec1RightOfOutRec2(outRec1, outRec2)) - { - holeStateRec = outRec2; - } - else if (this.OutRec1RightOfOutRec2(outRec2, outRec1)) - { - holeStateRec = outRec1; - } - else - { - holeStateRec = this.GetLowermostRec(outRec1, outRec2); - } - - if (!this.JoinPoints(join, outRec1, outRec2)) - { - continue; - } - - if (outRec1 == outRec2) - { - // instead of joining two polygons, we've just created a new one by - // splitting one polygon into two. - outRec1.Pts = join.OutPt1; - outRec1.BottomPt = null; - outRec2 = this.CreateOutRec(); - outRec2.Pts = join.OutPt2; - - // update all OutRec2.Pts Idx's ... - this.UpdateOutPtIdxs(outRec2); - - if (Poly2ContainsPoly1(outRec2.Pts, outRec1.Pts)) - { - // outRec1 contains outRec2 ... - outRec2.IsHole = !outRec1.IsHole; - outRec2.FirstLeft = outRec1; - - if (this.usingPolyTree) - { - this.FixupFirstLefts2(outRec2, outRec1); - } - } - else if (Poly2ContainsPoly1(outRec1.Pts, outRec2.Pts)) - { - // outRec2 contains outRec1 ... - outRec2.IsHole = outRec1.IsHole; - outRec1.IsHole = !outRec2.IsHole; - outRec2.FirstLeft = outRec1.FirstLeft; - outRec1.FirstLeft = outRec2; - - if (this.usingPolyTree) - { - this.FixupFirstLefts2(outRec1, outRec2); - } - } - else - { - // the 2 polygons are completely separate ... - outRec2.IsHole = outRec1.IsHole; - outRec2.FirstLeft = outRec1.FirstLeft; - - // fixup FirstLeft pointers that may need reassigning to OutRec2 - if (this.usingPolyTree) - { - this.FixupFirstLefts1(outRec1, outRec2); - } - } - } - else - { - // joined 2 polygons together ... - outRec2.Pts = null; - outRec2.BottomPt = null; - outRec2.Idx = outRec1.Idx; - - outRec1.IsHole = holeStateRec.IsHole; - if (holeStateRec == outRec2) - { - outRec1.FirstLeft = outRec2.FirstLeft; - } - - outRec2.FirstLeft = outRec1; - - // fixup FirstLeft pointers that may need reassigning to OutRec1 - if (this.usingPolyTree) - { - this.FixupFirstLefts3(outRec2, outRec1); - } - } - } - } - - private void UpdateOutPtIdxs(OutRec outrec) - { - OutPt op = outrec.Pts; - do - { - op.Idx = outrec.Idx; - op = op.Prev; - } - while (op != outrec.Pts); - } - - private void DoSimplePolygons() - { - int i = 0; - while (i < this.polyOuts.Count) - { - OutRec outrec = this.polyOuts[i++]; - OutPt op = outrec.Pts; - if (op == null || outrec.IsOpen) - { - continue; - } - - do - { - // for each Pt in Polygon until duplicate found do ... - OutPt op2 = op.Next; - while (op2 != outrec.Pts) - { - if ((op.Pt == op2.Pt) && op2.Next != op && op2.Prev != op) - { - // split the polygon into two ... - OutPt op3 = op.Prev; - OutPt op4 = op2.Prev; - op.Prev = op4; - op4.Next = op; - op2.Prev = op3; - op3.Next = op2; - - outrec.Pts = op; - OutRec outrec2 = this.CreateOutRec(); - outrec2.Pts = op2; - this.UpdateOutPtIdxs(outrec2); - if (Poly2ContainsPoly1(outrec2.Pts, outrec.Pts)) - { - // OutRec2 is contained by OutRec1 ... - outrec2.IsHole = !outrec.IsHole; - outrec2.FirstLeft = outrec; - if (this.usingPolyTree) - { - this.FixupFirstLefts2(outrec2, outrec); - } - } - else - if (Poly2ContainsPoly1(outrec.Pts, outrec2.Pts)) - { - // OutRec1 is contained by OutRec2 ... - outrec2.IsHole = outrec.IsHole; - outrec.IsHole = !outrec2.IsHole; - outrec2.FirstLeft = outrec.FirstLeft; - outrec.FirstLeft = outrec2; - if (this.usingPolyTree) - { - this.FixupFirstLefts2(outrec, outrec2); - } - } - else - { - // the 2 polygons are separate ... - outrec2.IsHole = outrec.IsHole; - outrec2.FirstLeft = outrec.FirstLeft; - if (this.usingPolyTree) - { - this.FixupFirstLefts1(outrec, outrec2); - } - } - - op2 = op; // ie get ready for the next iteration - } - - op2 = op2.Next; - } - - op = op.Next; - } - while (op != outrec.Pts); - } - } - - private double Area(OutRec outRec) - { - return this.Area(outRec.Pts); - } - - private double Area(OutPt op) - { - OutPt opFirst = op; - if (op == null) - { - return 0; - } - - double a = 0; - do - { - a = a + ((op.Prev.Pt.X + op.Pt.X) * (op.Prev.Pt.Y - op.Pt.Y)); - op = op.Next; - } - while (op != opFirst); - - return a * 0.5; - } - - private void SetDx(TEdge e) - { - e.Delta.X = e.Top.X - e.Bot.X; - e.Delta.Y = e.Top.Y - e.Bot.Y; - if (e.Delta.Y == 0) - { - e.Dx = HorizontalDeltaLimit; - } - else - { - e.Dx = e.Delta.X / e.Delta.Y; - } - } - - private void InsertLocalMinima(LocalMinima newLm) - { - if (this.minimaList == null) - { - this.minimaList = newLm; - } - else if (newLm.Y >= this.minimaList.Y) - { - newLm.Next = this.minimaList; - this.minimaList = newLm; - } - else - { - LocalMinima tmpLm = this.minimaList; - while (tmpLm.Next != null && (newLm.Y < tmpLm.Next.Y)) - { - tmpLm = tmpLm.Next; - } - - newLm.Next = tmpLm.Next; - tmpLm.Next = newLm; - } - } - - private bool PopLocalMinima(float y, out LocalMinima current) - { - current = this.currentLM; - if (this.currentLM != null && this.currentLM.Y == y) - { - this.currentLM = this.currentLM.Next; - return true; - } - - return false; - } - - private void Reset() - { - this.currentLM = this.minimaList; - if (this.currentLM == null) - { - return; // ie nothing to process - } - - // reset all edges ... - this.scanbeam = null; - LocalMinima lm = this.minimaList; - while (lm != null) - { - this.InsertScanbeam(lm.Y); - TEdge e = lm.LeftBound; - if (e != null) - { - e.Curr = e.Bot; - e.OutIdx = Unassigned; - } - - e = lm.RightBound; - if (e != null) - { - e.Curr = e.Bot; - e.OutIdx = Unassigned; - } - - lm = lm.Next; - } - - this.activeEdges = null; - } - - private void InsertScanbeam(float y) - { - // single-linked list: sorted descending, ignoring dups. - if (this.scanbeam == null) - { - this.scanbeam = new Scanbeam(); - this.scanbeam.Next = null; - this.scanbeam.Y = y; - } - else if (y > this.scanbeam.Y) - { - Scanbeam newSb = new Scanbeam(); - newSb.Y = y; - newSb.Next = this.scanbeam; - this.scanbeam = newSb; - } - else - { - Scanbeam sb2 = this.scanbeam; - while (sb2.Next != null && (y <= sb2.Next.Y)) - { - sb2 = sb2.Next; - } - - if (y == sb2.Y) - { - return; // ie ignores duplicates - } - - Scanbeam newSb = new Scanbeam(); - newSb.Y = y; - newSb.Next = sb2.Next; - sb2.Next = newSb; - } - } - - private bool PopScanbeam(out float y) - { - if (this.scanbeam == null) - { - y = 0; - return false; - } - - y = this.scanbeam.Y; - this.scanbeam = this.scanbeam.Next; - return true; - } - - private bool LocalMinimaPending() - { - return this.currentLM != null; - } - - private OutRec CreateOutRec() - { - OutRec result = new OutRec(); - result.Idx = Unassigned; - result.IsHole = false; - result.IsOpen = false; - result.FirstLeft = null; - result.Pts = null; - result.BottomPt = null; - result.PolyNode = null; - this.polyOuts.Add(result); - result.Idx = this.polyOuts.Count - 1; - return result; - } - - private void DisposeOutRec(int index) - { - OutRec outRec = this.polyOuts[index]; - outRec.Pts = null; - outRec = null; - this.polyOuts[index] = null; - } - - private void UpdateEdgeIntoAEL(ref TEdge e) - { - if (e.NextInLML == null) - { - throw new ClipperException("UpdateEdgeIntoAEL: invalid call"); - } - - TEdge aelPrev = e.PrevInAEL; - TEdge aelNext = e.NextInAEL; - e.NextInLML.OutIdx = e.OutIdx; - if (aelPrev != null) - { - aelPrev.NextInAEL = e.NextInLML; - } - else - { - this.activeEdges = e.NextInLML; - } - - if (aelNext != null) - { - aelNext.PrevInAEL = e.NextInLML; - } - - e.NextInLML.Side = e.Side; - e.NextInLML.WindDelta = e.WindDelta; - e.NextInLML.WindCnt = e.WindCnt; - e.NextInLML.WindCnt2 = e.WindCnt2; - e = e.NextInLML; - e.Curr = e.Bot; - e.PrevInAEL = aelPrev; - e.NextInAEL = aelNext; - if (!IsHorizontal(e)) - { - this.InsertScanbeam(e.Top.Y); - } - } - - private void SwapPositionsInAEL(TEdge edge1, TEdge edge2) - { - // check that one or other edge hasn't already been removed from AEL ... - if (edge1.NextInAEL == edge1.PrevInAEL || - edge2.NextInAEL == edge2.PrevInAEL) - { - return; - } - - if (edge1.NextInAEL == edge2) - { - TEdge next = edge2.NextInAEL; - if (next != null) - { - next.PrevInAEL = edge1; - } - - TEdge prev = edge1.PrevInAEL; - if (prev != null) - { - prev.NextInAEL = edge2; - } - - edge2.PrevInAEL = prev; - edge2.NextInAEL = edge1; - edge1.PrevInAEL = edge2; - edge1.NextInAEL = next; - } - else if (edge2.NextInAEL == edge1) - { - TEdge next = edge1.NextInAEL; - if (next != null) - { - next.PrevInAEL = edge2; - } - - TEdge prev = edge2.PrevInAEL; - if (prev != null) - { - prev.NextInAEL = edge1; - } - - edge1.PrevInAEL = prev; - edge1.NextInAEL = edge2; - edge2.PrevInAEL = edge1; - edge2.NextInAEL = next; - } - else - { - TEdge next = edge1.NextInAEL; - TEdge prev = edge1.PrevInAEL; - edge1.NextInAEL = edge2.NextInAEL; - if (edge1.NextInAEL != null) - { - edge1.NextInAEL.PrevInAEL = edge1; - } - - edge1.PrevInAEL = edge2.PrevInAEL; - if (edge1.PrevInAEL != null) - { - edge1.PrevInAEL.NextInAEL = edge1; - } - - edge2.NextInAEL = next; - if (edge2.NextInAEL != null) - { - edge2.NextInAEL.PrevInAEL = edge2; - } - - edge2.PrevInAEL = prev; - if (edge2.PrevInAEL != null) - { - edge2.PrevInAEL.NextInAEL = edge2; - } - } - - if (edge1.PrevInAEL == null) - { - this.activeEdges = edge1; - } - else if (edge2.PrevInAEL == null) - { - this.activeEdges = edge2; - } - } - - private void DeleteFromAEL(TEdge e) - { - TEdge aelPrev = e.PrevInAEL; - TEdge aelNext = e.NextInAEL; - if (aelPrev == null && aelNext == null && (e != this.activeEdges)) - { - return; // already deleted - } - - if (aelPrev != null) - { - aelPrev.NextInAEL = aelNext; - } - else - { - this.activeEdges = aelNext; - } - - if (aelNext != null) - { - aelNext.PrevInAEL = aelPrev; - } - - e.NextInAEL = null; - e.PrevInAEL = null; - } - - private void InitEdge2(TEdge e, PolyType polyType) - { - if (e.Curr.Y >= e.Next.Curr.Y) - { - e.Bot = e.Curr; - e.Top = e.Next.Curr; - } - else - { - e.Top = e.Curr; - e.Bot = e.Next.Curr; - } - - this.SetDx(e); - e.PolyTyp = polyType; - } - - private TEdge ProcessBound(TEdge edge, bool leftBoundIsForward) - { - TEdge eStart, result = edge; - TEdge horz; - - if (result.OutIdx == Skip) - { - // check if there are edges beyond the skip edge in the bound and if so - // create another LocMin and calling ProcessBound once more ... - edge = result; - if (leftBoundIsForward) - { - while (edge.Top.Y == edge.Next.Bot.Y) - { - edge = edge.Next; - } - - while (edge != result && edge.Dx == HorizontalDeltaLimit) - { - edge = edge.Prev; - } - } - else - { - while (edge.Top.Y == edge.Prev.Bot.Y) - { - edge = edge.Prev; - } - - while (edge != result && edge.Dx == HorizontalDeltaLimit) - { - edge = edge.Next; - } - } - - if (edge == result) - { - if (leftBoundIsForward) - { - result = edge.Next; - } - else - { - result = edge.Prev; - } - } - else - { - // there are more edges in the bound beyond result starting with E - if (leftBoundIsForward) - { - edge = result.Next; - } - else - { - edge = result.Prev; - } - - LocalMinima locMin = new LocalMinima(); - locMin.Next = null; - locMin.Y = edge.Bot.Y; - locMin.LeftBound = null; - locMin.RightBound = edge; - edge.WindDelta = 0; - result = this.ProcessBound(edge, leftBoundIsForward); - this.InsertLocalMinima(locMin); - } - - return result; - } - - if (edge.Dx == HorizontalDeltaLimit) - { - // We need to be careful with open paths because this may not be a - // true local minima (ie E may be following a skip edge). - // Also, consecutive horz. edges may start heading left before going right. - if (leftBoundIsForward) - { - eStart = edge.Prev; - } - else - { - eStart = edge.Next; - } - - // ie an adjoining horizontal skip edge - if (eStart.Dx == HorizontalDeltaLimit) - { - if (eStart.Bot.X != edge.Bot.X && eStart.Top.X != edge.Bot.X) - { - ReverseHorizontal(edge); - } - } - else if (eStart.Bot.X != edge.Bot.X) - { - ReverseHorizontal(edge); - } - } - - eStart = edge; - if (leftBoundIsForward) - { - while (result.Top.Y == result.Next.Bot.Y && result.Next.OutIdx != Skip) - { - result = result.Next; - } - - if (result.Dx == HorizontalDeltaLimit && result.Next.OutIdx != Skip) - { - // nb: at the top of a bound, horizontals are added to the bound - // only when the preceding edge attaches to the horizontal's left vertex - // unless a Skip edge is encountered when that becomes the top divide - horz = result; - while (horz.Prev.Dx == HorizontalDeltaLimit) - { - horz = horz.Prev; - } - - if (horz.Prev.Top.X > result.Next.Top.X) - { - result = horz.Prev; - } - } - - while (edge != result) - { - edge.NextInLML = edge.Next; - if (edge.Dx == HorizontalDeltaLimit && edge != eStart && edge.Bot.X != edge.Prev.Top.X) - { - ReverseHorizontal(edge); - } - - edge = edge.Next; - } - - if (edge.Dx == HorizontalDeltaLimit && edge != eStart && edge.Bot.X != edge.Prev.Top.X) - { - ReverseHorizontal(edge); - } - - result = result.Next; // move to the edge just beyond current bound - } - else - { - while (result.Top.Y == result.Prev.Bot.Y && result.Prev.OutIdx != Skip) - { - result = result.Prev; - } - - if (result.Dx == HorizontalDeltaLimit && result.Prev.OutIdx != Skip) - { - horz = result; - while (horz.Next.Dx == HorizontalDeltaLimit) - { - horz = horz.Next; - } - - if (horz.Next.Top.X == result.Prev.Top.X || horz.Next.Top.X > result.Prev.Top.X) - { - result = horz.Next; - } - } - - while (edge != result) - { - edge.NextInLML = edge.Prev; - if (edge.Dx == HorizontalDeltaLimit && edge != eStart && edge.Bot.X != edge.Next.Top.X) - { - ReverseHorizontal(edge); - } - - edge = edge.Prev; - } - - if (edge.Dx == HorizontalDeltaLimit && edge != eStart && edge.Bot.X != edge.Next.Top.X) - { - ReverseHorizontal(edge); - } - - result = result.Prev; // move to the edge just beyond current bound - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/ClipperException.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/ClipperException.cs deleted file mode 100644 index cefd268af..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/ClipperException.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Clipper Exception - /// - /// - internal class ClipperException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// The description. - public ClipperException(string description) - : base(description) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Direction.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Direction.cs deleted file mode 100644 index 5fa877fd4..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Direction.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ??? - /// - internal enum Direction - { - /// - /// The right to left - /// - RightToLeft, - - /// - /// The left to right - /// - LeftToRight - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/EdgeSide.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/EdgeSide.cs deleted file mode 100644 index 5093958d1..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/EdgeSide.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal enum EdgeSide - { - /// - /// The left - /// - Left, - - /// - /// The right - /// - Right - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNode.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNode.cs deleted file mode 100644 index 7cd0562b0..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNode.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class IntersectNode - { -#pragma warning disable SA1401 // Field must be private - /// - /// The edge1 - /// - internal TEdge Edge1; - - /// - /// The edge2 - /// - internal TEdge Edge2; - - /// - /// The pt - /// - internal System.Numerics.Vector2 Pt; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNodeSort.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNodeSort.cs deleted file mode 100644 index f4524fa9b..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNodeSort.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Compares s - /// - internal class IntersectNodeSort : IComparer - { - /// - /// Compares the specified node1. - /// - /// The node1. - /// The node2. - /// - /// 1 if node2 %gt; node1 - /// -1 if node2 $lt; node1 - /// 0 if same - /// - public int Compare(IntersectNode node1, IntersectNode node2) - { - float i = node2.Pt.Y - node1.Pt.Y; - if (i > 0) - { - return 1; - } - else if (i < 0) - { - return -1; - } - else - { - return 0; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Join.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Join.cs deleted file mode 100644 index be948fbf7..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Join.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class Join - { -#pragma warning disable SA1401 // Field must be private - /// - /// The out PT1 - /// - internal OutPt OutPt1; - - /// - /// The out PT2 - /// - internal OutPt OutPt2; - - /// - /// The off pt - /// - internal System.Numerics.Vector2 OffPt; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/LocalMinima.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/LocalMinima.cs deleted file mode 100644 index b48a53cab..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/LocalMinima.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class LocalMinima - { -#pragma warning disable SA1401 // Field must be private - /// - /// The y - /// - internal float Y; - - /// - /// The left bound - /// - internal TEdge LeftBound; - - /// - /// The right bound - /// - internal TEdge RightBound; - - /// - /// The next - /// - internal LocalMinima Next; - -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Maxima.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Maxima.cs deleted file mode 100644 index 85168e8e8..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Maxima.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class Maxima - { -#pragma warning disable SA1401 // Field must be private - /// - /// The x - /// - internal float X; - - /// - /// The next - /// - internal Maxima Next; - - /// - /// The previous - /// - internal Maxima Prev; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutPt.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutPt.cs deleted file mode 100644 index 8dae5780a..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutPt.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class OutPt - { -#pragma warning disable SA1401 // Field must be private - /// - /// The index - /// - internal int Idx; - - /// - /// The pt - /// - internal System.Numerics.Vector2 Pt; - - /// - /// The next - /// - internal OutPt Next; - - /// - /// The previous - /// - internal OutPt Prev; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutRec.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutRec.cs deleted file mode 100644 index 7c2d41a72..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutRec.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// OutRec: contains a path in the clipping solution. Edges in the AEL will - /// carry a pointer to an OutRec when they are part of the clipping solution. - /// - internal class OutRec - { -#pragma warning disable SA1401 // Field must be private - /// - /// The source path - /// - internal IPath SourcePath; - - /// - /// The index - /// - internal int Idx; - - /// - /// The is hole - /// - internal bool IsHole; - - /// - /// The is open - /// - internal bool IsOpen; - - /// - /// The first left - /// - internal OutRec FirstLeft; - - /// - /// The PTS - /// - internal OutPt Pts; - - /// - /// The bottom pt - /// - internal OutPt BottomPt; - - /// - /// The poly node - /// - internal PolyNode PolyNode; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyNode.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyNode.cs deleted file mode 100644 index 9d9c35504..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyNode.cs +++ /dev/null @@ -1,179 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Poly Node - /// - internal class PolyNode - { -#pragma warning disable SA1401 // Field must be private - /// - /// The polygon - /// - internal List Polygon = new List(); - - /// - /// The index - /// - internal int Index; - - /// - /// The childs - /// - protected List children = new List(); - - private PolyNode parent; -#pragma warning restore SA1401 // Field must be private - - /// - /// Gets the child count. - /// - /// - /// The child count. - /// - public int ChildCount - { - get { return this.children.Count; } - } - - /// - /// Gets the contour. - /// - /// - /// The contour. - /// - public List Contour - { - get { return this.Polygon; } - } - - /// - /// Gets the childs. - /// - /// - /// The childs. - /// - public List Children - { - get { return this.children; } - } - - /// - /// Gets or sets the parent. - /// - /// - /// The parent. - /// - public PolyNode Parent - { - get { return this.parent; } - internal set { this.parent = value; } - } - - /// - /// Gets a value indicating whether this instance is hole. - /// - /// - /// true if this instance is hole; otherwise, false. - /// - public bool IsHole - { - get { return this.IsHoleNode(); } - } - - /// - /// Gets or sets a value indicating whether this instance is open. - /// - /// - /// true if this instance is open; otherwise, false. - /// - public bool IsOpen { get; set; } - - /// - /// Gets or sets the source path. - /// - /// - /// The source path. - /// - public IPath SourcePath { get; internal set; } - - /// - /// Gets the next. - /// - /// The next node - public PolyNode GetNext() - { - if (this.children.Count > 0) - { - return this.children[0]; - } - else - { - return this.GetNextSiblingUp(); - } - } - - /// - /// Adds the child. - /// - /// The child. - internal void AddChild(PolyNode child) - { - int cnt = this.children.Count; - this.children.Add(child); - child.parent = this; - child.Index = cnt; - } - - /// - /// Gets the next sibling up. - /// - /// The next sibling up - internal PolyNode GetNextSiblingUp() - { - if (this.parent == null) - { - return null; - } - else if (this.Index == this.parent.children.Count - 1) - { - return this.parent.GetNextSiblingUp(); - } - else - { - return this.parent.Children[this.Index + 1]; - } - } - - /// - /// Determines whether [is hole node]. - /// - /// - /// true if [is hole node]; otherwise, false. - /// - private bool IsHoleNode() - { - bool result = true; - PolyNode node = this.parent; - while (node != null) - { - result = !result; - node = node.parent; - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyTree.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyTree.cs deleted file mode 100644 index 3c35f389c..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyTree.cs +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Poly Tree - /// - /// - internal class PolyTree : PolyNode - { -#pragma warning disable SA1401 // Field must be private - /// - /// All polys - /// - internal List AllPolys = new List(); -#pragma warning restore SA1401 // Field must be private - - /// - /// Gets the total. - /// - /// - /// The total. - /// - public int Total - { - get - { - int result = this.AllPolys.Count; - - // with negative offsets, ignore the hidden outer polygon ... - if (result > 0 && this.Children[0] != this.AllPolys[0]) - { - result--; - } - - return result; - } - } - - /// - /// Clears this instance. - /// - public void Clear() - { - for (int i = 0; i < this.AllPolys.Count; i++) - { - this.AllPolys[i] = null; - } - - this.AllPolys.Clear(); - this.Children.Clear(); - } - - /// - /// Gets the first. - /// - /// the first node - public PolyNode GetFirst() - { - if (this.Children.Count > 0) - { - return this.Children[0]; - } - else - { - return null; - } - } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyType.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyType.cs deleted file mode 100644 index 2a130f509..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyType.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Poly Type - /// - internal enum PolyType - { - /// - /// The subject - /// - Subject, - - /// - /// The clip - /// - Clip - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/README.md b/src/ImageSharp.Drawing/Shapes/PolygonClipper/README.md deleted file mode 100644 index c0f2ff65f..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Clipper - -License details for code in this folder, this is code original written by **Angus Johnson** - -The license header onthe original file which has now be split across multiple files in this folder. - -``` -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.4.0 * -* Date : 2 July 2015 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2015 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ -``` \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Scanbeam.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Scanbeam.cs deleted file mode 100644 index 28b341004..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Scanbeam.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Scanbeam - /// - internal class Scanbeam // would this work as a struct? - { -#pragma warning disable SA1401 // Field must be private - /// - /// The y - /// - internal float Y; - - /// - /// The next - /// - internal Scanbeam Next; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/TEdge.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/TEdge.cs deleted file mode 100644 index 97f5b2ec7..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/TEdge.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// TEdge - /// - internal class TEdge - { -#pragma warning disable SA1401 // Field must be private - /// - /// The source path, see if we can link this back later - /// - internal IPath SourcePath; - - /// - /// The bot - /// - internal System.Numerics.Vector2 Bot; - - /// - /// The current (updated for every new scanbeam) - /// - internal System.Numerics.Vector2 Curr; - - /// - /// The top - /// - internal System.Numerics.Vector2 Top; - - /// - /// The delta - /// - internal System.Numerics.Vector2 Delta; - - /// - /// The dx - /// - internal double Dx; - - /// - /// The poly type - /// - internal PolyType PolyTyp; - - /// - /// Side only refers to current side of solution poly - /// - internal EdgeSide Side; - - /// - /// 1 or -1 depending on winding direction - /// - internal int WindDelta; - - /// - /// The winding count - /// - internal int WindCnt; - - /// - /// The winding count of the opposite polytype - /// - internal int WindCnt2; - - /// - /// The out index - /// - internal int OutIdx; - - /// - /// The next - /// - internal TEdge Next; - - /// - /// The previous - /// - internal TEdge Prev; - - /// - /// The next in LML - /// - internal TEdge NextInLML; - - /// - /// The next in ael - /// - internal TEdge NextInAEL; - - /// - /// The previous in ael - /// - internal TEdge PrevInAEL; - - /// - /// The next in sel - /// - internal TEdge NextInSEL; - - /// - /// The previous in sel - /// - internal TEdge PrevInSEL; -#pragma warning restore SA1401 // Field must be - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs deleted file mode 100644 index 5002bee40..000000000 --- a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs +++ /dev/null @@ -1,281 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Threading.Tasks; - using Paths; - - /// - /// A way of optermising drawing rectangles. - /// - /// - public class RectangularPolygon : IShape, IPath - { - private readonly RectangleF rectangle; - private readonly Vector2 topLeft; - private readonly Vector2 bottomRight; - private readonly Vector2[] points; - private readonly IEnumerable pathCollection; - private readonly float halfLength; - - /// - /// Initializes a new instance of the class. - /// - /// The rect. - public RectangularPolygon(ImageSharp.Rectangle rect) - : this((RectangleF)rect) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The rect. - public RectangularPolygon(ImageSharp.RectangleF rect) - { - this.rectangle = rect; - this.points = new Vector2[4] - { - this.topLeft = new Vector2(rect.Left, rect.Top), - new Vector2(rect.Right, rect.Top), - this.bottomRight = new Vector2(rect.Right, rect.Bottom), - new Vector2(rect.Left, rect.Bottom) - }; - - this.halfLength = this.rectangle.Width + this.rectangle.Height; - this.Length = this.halfLength * 2; - this.pathCollection = new[] { this }; - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds => this.rectangle; - - /// - /// Gets a value indicating whether this instance is closed. - /// - /// - /// true if this instance is closed; otherwise, false. - /// - public bool IsClosed => true; - - /// - /// Gets the length of the path - /// - /// - /// The length. - /// - public float Length { get; } - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections => 4; - - /// - /// Calculates the distance along and away from the path for a specified point. - /// - /// The point along the path. - /// - /// Returns details about the point and its distance away from the path. - /// - PointInfo IPath.Distance(Vector2 point) - { - bool inside; // dont care about inside/outside for paths just distance - return this.Distance(point, false, out inside); - } - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// Returns the distance from the shape to the point - /// - public float Distance(Vector2 point) - { - bool insidePoly; - PointInfo result = this.Distance(point, true, out insidePoly); - - // invert the distance from path when inside - return insidePoly ? -result.DistanceFromPath : result.DistanceFromPath; - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return this.pathCollection.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.pathCollection.GetEnumerator(); - } - - /// - /// Converts the into a simple linear path.. - /// - /// - /// Returns the current as simple linear path. - /// - public Vector2[] AsSimpleLinearPath() - { - return this.points; - } - - /// - /// Based on a line described by and - /// populate a buffer for all points on the edges of the - /// that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - int discovered = 0; - Vector2 startPoint = Vector2.Clamp(start, this.topLeft, this.bottomRight); - Vector2 endPoint = Vector2.Clamp(end, this.topLeft, this.bottomRight); - - if (startPoint == Vector2.Clamp(startPoint, start, end)) - { - // if start closest is within line then its a valid point - discovered++; - buffer[offset++] = startPoint; - } - - if (endPoint == Vector2.Clamp(endPoint, start, end)) - { - // if start closest is within line then its a valid point - discovered++; - buffer[offset++] = endPoint; - } - - return discovered; - } - - private PointInfo Distance(Vector2 point, bool getDistanceAwayOnly, out bool isInside) - { - // point in rectangle - // if after its clamped by the extreams its still the same then it must be inside :) - Vector2 clamped = Vector2.Clamp(point, this.topLeft, this.bottomRight); - isInside = clamped == point; - - float distanceFromEdge = float.MaxValue; - float distanceAlongEdge = 0f; - - if (isInside) - { - // get the absolute distances from the extreams - Vector2 topLeftDist = Vector2.Abs(point - this.topLeft); - Vector2 bottomRightDist = Vector2.Abs(point - this.bottomRight); - - // get the min components - Vector2 minDists = Vector2.Min(topLeftDist, bottomRightDist); - - // and then the single smallest (dont have to worry about direction) - distanceFromEdge = Math.Min(minDists.X, minDists.Y); - - if (!getDistanceAwayOnly) - { - // we need to make clamped the closest point - if (this.topLeft.X + distanceFromEdge == point.X) - { - // closer to lhf - clamped.X = this.topLeft.X; // y is already the same - - // distance along edge is length minus the amout down we are from the top of the rect - distanceAlongEdge = this.Length - (clamped.Y - this.topLeft.Y); - } - else if (this.topLeft.Y + distanceFromEdge == point.Y) - { - // closer to top - clamped.Y = this.topLeft.Y; // x is already the same - - distanceAlongEdge = clamped.X - this.topLeft.X; - } - else if (this.bottomRight.Y - distanceFromEdge == point.Y) - { - // closer to bottom - clamped.Y = this.bottomRight.Y; // x is already the same - - distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength; - } - else if (this.bottomRight.X - distanceFromEdge == point.X) - { - // closer to rhs - clamped.X = this.bottomRight.X; // x is already the same - - distanceAlongEdge = (this.bottomRight.Y - clamped.Y) + this.rectangle.Width; - } - } - } - else - { - // clamped is the point on the path thats closest no matter what - distanceFromEdge = (clamped - point).Length(); - - if (!getDistanceAwayOnly) - { - // we need to figure out whats the cloests edge now and thus what distance/poitn is closest - if (this.topLeft.X == clamped.X) - { - // distance along edge is length minus the amout down we are from the top of the rect - distanceAlongEdge = this.Length - (clamped.Y - this.topLeft.Y); - } - else if (this.topLeft.Y == clamped.Y) - { - distanceAlongEdge = clamped.X - this.topLeft.X; - } - else if (this.bottomRight.Y == clamped.Y) - { - distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength; - } - else if (this.bottomRight.X == clamped.X) - { - distanceAlongEdge = (this.bottomRight.Y - clamped.Y) + this.rectangle.Width; - } - } - } - - return new PointInfo - { - SearchPoint = point, - DistanceFromPath = distanceFromEdge, - ClosestPointOnPath = clamped, - DistanceAlongPath = distanceAlongEdge - }; - } - } -} diff --git a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs index ae7e6c72a..9f490a3a9 100644 --- a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs @@ -26,9 +26,9 @@ namespace ImageSharp.Formats public class BmpDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { + public void Decode(Image image, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel + { Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); diff --git a/src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs b/src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs index 562417601..a75031ea1 100644 --- a/src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs @@ -58,7 +58,7 @@ namespace ImageSharp.Formats /// is null. /// public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.currentStream = stream; @@ -212,7 +212,7 @@ namespace ImageSharp.Formats /// The number of bits per pixel. /// Whether the bitmap is inverted. private void ReadRgbPalette(PixelAccessor pixels, byte[] colors, int width, int height, int bits, bool inverted) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -267,7 +267,7 @@ namespace ImageSharp.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb16(PixelAccessor pixels, int width, int height, bool inverted) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // We divide here as we will store the colors in our floating point format. const int ScaleR = 8; // 256/32 @@ -309,7 +309,7 @@ namespace ImageSharp.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb24(PixelAccessor pixels, int width, int height, bool inverted) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int padding = CalculatePadding(width, 3); using (PixelArea row = new PixelArea(width, ComponentOrder.Zyx, padding)) @@ -333,7 +333,7 @@ namespace ImageSharp.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32(PixelAccessor pixels, int width, int height, bool inverted) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int padding = CalculatePadding(width, 4); using (PixelArea row = new PixelArea(width, ComponentOrder.Zyxw, padding)) diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs index 2ed6b3b98..d0a3550f6 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs @@ -14,17 +14,27 @@ namespace ImageSharp.Formats /// The encoder can currently only write 24-bit rgb images to streams. public class BmpEncoder : IImageEncoder { + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel + { + IBmpEncoderOptions bmpOptions = BmpEncoderOptions.Create(options); + + this.Encode(image, stream, bmpOptions); + } + /// - /// Gets or sets the number of bits per pixel. + /// Encodes the image to the specified stream from the . /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - - /// - public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { - BmpEncoderCore encoder = new BmpEncoderCore(); - encoder.Encode(image, stream, this.BitsPerPixel); + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The options for the encoder. + public void Encode(Image image, Stream stream, IBmpEncoderOptions options) + where TColor : struct, IPixel + { + BmpEncoderCore encoder = new BmpEncoderCore(options); + encoder.Encode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs index 94f2b3a38..df62fb6f4 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs @@ -16,34 +16,40 @@ namespace ImageSharp.Formats internal sealed class BmpEncoderCore { /// - /// The number of bits per pixel. + /// The options for the encoder. /// - private BmpBitsPerPixel bmpBitsPerPixel; + private readonly IBmpEncoderOptions options; /// /// The amount to pad each row by. /// private int padding; + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + public BmpEncoderCore(IBmpEncoderOptions options) + { + this.options = options ?? new BmpEncoderOptions(); + } + /// /// Encodes the image to the specified stream from the . /// /// The pixel format. /// The to encode from. /// The to encode the image data to. - /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where TColor : struct, IPackedPixel, IEquatable - { + public void Encode(ImageBase image, Stream stream) + where TColor : struct, IPixel + { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.bmpBitsPerPixel = bitsPerPixel; - // Cast to int will get the bytes per pixel - short bpp = (short)(8 * (int)bitsPerPixel); + short bpp = (short)(8 * (int)this.options.BitsPerPixel); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); - this.padding = bytesPerLine - (image.Width * (int)bitsPerPixel); + this.padding = bytesPerLine - (image.Width * (int)this.options.BitsPerPixel); // Do not use IDisposable pattern here as we want to preserve the stream. EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); @@ -119,23 +125,23 @@ namespace ImageSharp.Formats /// Writes the pixel data to the binary stream. /// /// The pixel format. - /// The containing the stream to write to. + /// The containing the stream to write to. /// /// The containing pixel data. /// private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where TColor : struct, IPackedPixel, IEquatable - { + where TColor : struct, IPixel + { using (PixelAccessor pixels = image.Lock()) { - switch (this.bmpBitsPerPixel) + switch (this.options.BitsPerPixel) { case BmpBitsPerPixel.Pixel32: - this.Write32Bit(writer, pixels); + this.Write32Bit(writer, pixels); break; case BmpBitsPerPixel.Pixel24: - this.Write24Bit(writer, pixels); + this.Write24Bit(writer, pixels); break; } } @@ -145,11 +151,11 @@ namespace ImageSharp.Formats /// Writes the 32bit color palette to the stream. /// /// The pixel format. - /// The containing the stream to write to. + /// The containing the stream to write to. /// The containing pixel data. private void Write32Bit(EndianBinaryWriter writer, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable - { + where TColor : struct, IPixel + { using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyxw, this.padding)) { for (int y = pixels.Height - 1; y >= 0; y--) @@ -164,11 +170,11 @@ namespace ImageSharp.Formats /// Writes the 24bit color palette to the stream. /// /// The pixel format. - /// The containing the stream to write to. + /// The containing the stream to write to. /// The containing pixel data. private void Write24Bit(EndianBinaryWriter writer, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable - { + where TColor : struct, IPixel + { using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyx, this.padding)) { for (int y = pixels.Height - 1; y >= 0; y--) diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs new file mode 100644 index 000000000..a0f9ff8e0 --- /dev/null +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public sealed class BmpEncoderOptions : EncoderOptions, IBmpEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public BmpEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private BmpEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + /// Converts the options to a instance with a cast + /// or by creating a new instance with the specfied options. + /// + /// The options for the encoder. + /// The options for the . + internal static IBmpEncoderOptions Create(IEncoderOptions options) + { + return options as IBmpEncoderOptions ?? new BmpEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs b/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs new file mode 100644 index 000000000..6cf37cbae --- /dev/null +++ b/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public interface IBmpEncoderOptions : IEncoderOptions + { + /// + /// Gets the number of bits per pixel. + /// + BmpBitsPerPixel BitsPerPixel { get; } + } +} diff --git a/src/ImageSharp.Formats.Bmp/ImageExtensions.cs b/src/ImageSharp.Formats.Bmp/ImageExtensions.cs index 8bbae8487..5b92b90d6 100644 --- a/src/ImageSharp.Formats.Bmp/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Bmp/ImageExtensions.cs @@ -26,7 +26,7 @@ namespace ImageSharp /// The . /// public static Image SaveAsBmp(this Image source, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel => source.Save(stream, new BmpEncoder()); } } diff --git a/src/ImageSharp.Formats.Gif/GifConstants.cs b/src/ImageSharp.Formats.Gif/GifConstants.cs index 5334bcba3..4af291c2b 100644 --- a/src/ImageSharp.Formats.Gif/GifConstants.cs +++ b/src/ImageSharp.Formats.Gif/GifConstants.cs @@ -5,10 +5,12 @@ namespace ImageSharp.Formats { + using System.Text; + /// /// Constants that define specific points within a gif. /// - internal sealed class GifConstants + internal static class GifConstants { /// /// The file type. @@ -50,6 +52,11 @@ namespace ImageSharp.Formats /// public const byte CommentLabel = 0xFE; + /// + /// The name of the property inside the image properties for the comments. + /// + public const string Comments = "Comments"; + /// /// The maximum comment length. /// @@ -79,5 +86,10 @@ namespace ImageSharp.Formats /// The end introducer trailer ;. /// public const byte EndIntroducer = 0x3B; + + /// + /// Gets the default encoding to use when reading comments. + /// + public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII"); } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index ce8c0c06e..b1e8ba928 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -14,10 +14,25 @@ namespace ImageSharp.Formats public class GifDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { - new GifDecoderCore().Decode(image, stream); + public void Decode(Image image, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel + { + IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); + + this.Decode(image, stream, gifOptions); + } + + /// + /// Decodes the image from the specified stream to the . + /// + /// The pixel format. + /// The to decode to. + /// The containing image data. + /// The options for the decoder. + public void Decode(Image image, Stream stream, IGifDecoderOptions options) + where TColor : struct, IPixel + { + new GifDecoderCore(options).Decode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index 2be8aed37..ab1edc2c7 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -8,19 +8,25 @@ namespace ImageSharp.Formats using System; using System.Buffers; using System.IO; + using System.Text; /// /// Performs the gif decoding operation. /// /// The pixel format. internal class GifDecoderCore - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The temp buffer used to reduce allocations. /// private readonly byte[] buffer = new byte[16]; + /// + /// The decoder options. + /// + private readonly IGifDecoderOptions options; + /// /// The image to decode the information to. /// @@ -61,6 +67,15 @@ namespace ImageSharp.Formats /// private GifGraphicsControlExtension graphicsControlExtension; + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public GifDecoderCore(IGifDecoderOptions options) + { + this.options = options ?? new GifDecoderOptions(); + } + /// /// Decodes the stream to the image. /// @@ -225,25 +240,32 @@ namespace ImageSharp.Formats /// private void ReadComments() { - int flag; + int length; - while ((flag = this.currentStream.ReadByte()) != 0) + while ((length = this.currentStream.ReadByte()) != 0) { - if (flag > GifConstants.MaxCommentLength) + if (length > GifConstants.MaxCommentLength) + { + throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'"); + } + + if (this.options.IgnoreMetadata) { - throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'"); + this.currentStream.Seek(length, SeekOrigin.Current); + continue; } - byte[] flagBuffer = ArrayPool.Shared.Rent(flag); + byte[] commentsBuffer = ArrayPool.Shared.Rent(length); try { - this.currentStream.Read(flagBuffer, 0, flag); - this.decodedImage.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(flagBuffer, 0, flag))); + this.currentStream.Read(commentsBuffer, 0, length); + string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length); + this.decodedImage.MetaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); } finally { - ArrayPool.Shared.Return(flagBuffer); + ArrayPool.Shared.Return(commentsBuffer); } } } @@ -321,12 +343,14 @@ namespace ImageSharp.Formats if (this.previousFrame == null) { - image = this.decodedImage; - - image.Quality = colorTableLength / 3; + this.decodedImage.MetaData.Quality = colorTableLength / 3; // This initializes the image to become fully transparent because the alpha channel is zero. - image.InitPixels(imageWidth, imageHeight); + this.decodedImage.InitPixels(imageWidth, imageHeight); + + this.SetFrameDelay(this.decodedImage.MetaData); + + image = this.decodedImage; } else { @@ -338,6 +362,8 @@ namespace ImageSharp.Formats currentFrame = this.previousFrame.Clone(); + this.SetFrameDelay(currentFrame.MetaData); + image = currentFrame; this.RestoreToBackground(image); @@ -345,11 +371,6 @@ namespace ImageSharp.Formats this.decodedImage.Frames.Add(currentFrame); } - if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) - { - image.FrameDelay = this.graphicsControlExtension.DelayTime; - } - int i = 0; int interlacePass = 0; // The interlace pass int interlaceIncrement = 8; // The interlacing line increment @@ -465,5 +486,17 @@ namespace ImageSharp.Formats this.restoreArea = null; } + + /// + /// Sets the frame delay in the metadata. + /// + /// The meta data. + private void SetFrameDelay(IMetaData metaData) + { + if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + { + metaData.FrameDelay = this.graphicsControlExtension.DelayTime; + } + } } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs new file mode 100644 index 000000000..bc7709f75 --- /dev/null +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the options for the . + /// + public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public GifDecoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the decoder. + private GifDecoderOptions(IDecoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the encoding that should be used when reading comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + /// Converts the options to a instance with a cast + /// or by creating a new instance with the specfied options. + /// + /// The options for the decoder. + /// The options for the . + internal static IGifDecoderOptions Create(IDecoderOptions options) + { + return options as IGifDecoderOptions ?? new GifDecoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Gif/GifEncoder.cs b/src/ImageSharp.Formats.Gif/GifEncoder.cs index 2446f0f68..cc8516ed9 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoder.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoder.cs @@ -8,40 +8,31 @@ namespace ImageSharp.Formats using System; using System.IO; - using ImageSharp.Quantizers; - /// /// Image encoder for writing image data to a stream in gif format. /// public class GifEncoder : IImageEncoder { - /// - /// Gets or sets the quality of output for images. - /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel + { + IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options); - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; + this.Encode(image, stream, gifOptions); + } /// - /// Gets or sets the quantizer for reducing the color count. + /// Encodes the image to the specified stream from the . /// - public IQuantizer Quantizer { get; set; } - - /// - public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { - GifEncoderCore encoder = new GifEncoderCore - { - Quality = this.Quality, - Quantizer = this.Quantizer, - Threshold = this.Threshold - }; - + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The options for the encoder. + public void Encode(Image image, Stream stream, IGifEncoderOptions options) + where TColor : struct, IPixel + { + GifEncoderCore encoder = new GifEncoderCore(options); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs index e5b8ba08a..38cbba850 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs @@ -24,20 +24,23 @@ namespace ImageSharp.Formats private readonly byte[] buffer = new byte[16]; /// - /// The number of bits requires to store the image palette. + /// The options for the encoder. /// - private int bitDepth; + private readonly IGifEncoderOptions options; /// - /// Gets or sets the quality of output for images. + /// The number of bits requires to store the image palette. /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } + private int bitDepth; /// - /// Gets or sets the transparency threshold. + /// Initializes a new instance of the class. /// - public byte Threshold { get; set; } = 128; + /// The options for the encoder. + public GifEncoderCore(IGifEncoderOptions options) + { + this.options = options ?? new GifEncoderOptions(); + } /// /// Gets or sets the quantizer for reducing the color count. @@ -51,28 +54,25 @@ namespace ImageSharp.Formats /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - if (this.Quantizer == null) - { - this.Quantizer = new OctreeQuantizer(); - } + this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer(); // Do not use IDisposable pattern here as we want to preserve the stream. EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.Quality; - this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; + int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + quality = quality > 0 ? quality.Clamp(1, 256) : 256; // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); // Quantize the image returning a palette. - QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, quality); int index = this.GetTransparentIndex(quantized); @@ -84,6 +84,7 @@ namespace ImageSharp.Formats // Write the first frame. this.WriteGraphicalControlExtension(image, writer, index); + this.WriteComments(image, writer); this.WriteImageDescriptor(image, writer); this.WriteColorTable(quantized, writer); this.WriteImageData(quantized, writer); @@ -91,13 +92,13 @@ namespace ImageSharp.Formats // Write additional frames. if (image.Frames.Any()) { - this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); + this.WriteApplicationExtension(writer, image.MetaData.RepeatCount, image.Frames.Count); // ReSharper disable once ForCanBeConvertedToForeach for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; - QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, this.Quality); + QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, quality); this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteImageDescriptor(frame, writer); @@ -106,7 +107,7 @@ namespace ImageSharp.Formats } } - // TODO: Write Comments extension etc + // TODO: Write extension etc writer.Write(GifConstants.EndIntroducer); } @@ -121,7 +122,7 @@ namespace ImageSharp.Formats /// The . /// private int GetTransparentIndex(QuantizedImage quantized) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Find the lowest alpha value and make it the transparent index. int index = 255; @@ -171,7 +172,7 @@ namespace ImageSharp.Formats /// The writer to write to the stream with. /// The transparency index to set the default background index to. private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor { @@ -229,15 +230,75 @@ namespace ImageSharp.Formats } } + /// + /// Writes the image comments to the stream. + /// + /// The pixel format. + /// The to be encoded. + /// The stream to write to. + private void WriteComments(Image image, EndianBinaryWriter writer) + where TColor : struct, IPixel + { + if (this.options.IgnoreMetadata == true) + { + return; + } + + ImageProperty property = image.MetaData.Properties.FirstOrDefault(p => p.Name == GifConstants.Comments); + if (property == null || string.IsNullOrEmpty(property.Value)) + { + return; + } + + byte[] comments = this.options.TextEncoding.GetBytes(property.Value); + + int count = Math.Min(comments.Length, 255); + + this.buffer[0] = GifConstants.ExtensionIntroducer; + this.buffer[1] = GifConstants.CommentLabel; + this.buffer[2] = (byte)count; + + writer.Write(this.buffer, 0, 3); + writer.Write(comments, 0, count); + writer.Write(GifConstants.Terminator); + } + + /// + /// Writes the graphics control extension to the stream. + /// + /// The pixel format. + /// The to encode. + /// The stream to write to. + /// The index of the color in the color palette to make transparent. + private void WriteGraphicalControlExtension(Image image, EndianBinaryWriter writer, int transparencyIndex) + where TColor : struct, IPixel + { + this.WriteGraphicalControlExtension(image, image.MetaData, writer, transparencyIndex); + } + + /// + /// Writes the graphics control extension to the stream. + /// + /// The pixel format. + /// The to encode. + /// The stream to write to. + /// The index of the color in the color palette to make transparent. + private void WriteGraphicalControlExtension(ImageFrame imageFrame, EndianBinaryWriter writer, int transparencyIndex) + where TColor : struct, IPixel + { + this.WriteGraphicalControlExtension(imageFrame, imageFrame.MetaData, writer, transparencyIndex); + } + /// /// Writes the graphics control extension to the stream. /// /// The pixel format. /// The to encode. + /// The metadata of the image or frame. /// The stream to write to. /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(ImageBase image, EndianBinaryWriter writer, int transparencyIndex) - where TColor : struct, IPackedPixel, IEquatable + private void WriteGraphicalControlExtension(ImageBase image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) + where TColor : struct, IPixel { // TODO: Check transparency logic. bool hasTransparent = transparencyIndex < 255; @@ -250,7 +311,7 @@ namespace ImageSharp.Formats DisposalMethod = disposalMethod, TransparencyFlag = hasTransparent, TransparencyIndex = transparencyIndex, - DelayTime = image.FrameDelay + DelayTime = metaData.FrameDelay }; // Write the intro. @@ -279,7 +340,7 @@ namespace ImageSharp.Formats /// The to be encoded. /// The stream to write to. private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { writer.Write(GifConstants.ImageDescriptorLabel); // 2c @@ -305,7 +366,7 @@ namespace ImageSharp.Formats /// The to encode. /// The writer to write to the stream with. private void WriteColorTable(QuantizedImage image, EndianBinaryWriter writer) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Grab the palette and write it to the stream. int pixelCount = image.Palette.Length; @@ -340,7 +401,7 @@ namespace ImageSharp.Formats /// The containing indexed pixels. /// The stream to write to. private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (LzwEncoder encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) { diff --git a/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs new file mode 100644 index 000000000..5d7c6e40b --- /dev/null +++ b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public GifEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private GifEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the encoding that should be used when writing comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + /// Gets or sets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + public int Quality { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Converts the options to a instance with a + /// cast or by creating a new instance with the specfied options. + /// + /// The options for the encoder. + /// The options for the . + internal static IGifEncoderOptions Create(IEncoderOptions options) + { + return options as IGifEncoderOptions ?? new GifEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs new file mode 100644 index 000000000..729bf1d11 --- /dev/null +++ b/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the options for the . + /// + public interface IGifDecoderOptions : IDecoderOptions + { + /// + /// Gets the encoding that should be used when reading comments. + /// + Encoding TextEncoding { get; } + } +} diff --git a/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs new file mode 100644 index 000000000..c1d6b7ad8 --- /dev/null +++ b/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public interface IGifEncoderOptions : IEncoderOptions + { + /// + /// Gets the encoding that should be used when writing comments. + /// + Encoding TextEncoding { get; } + + /// + /// Gets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + int Quality { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + + /// + /// Gets the quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + } +} diff --git a/src/ImageSharp.Formats.Gif/ImageExtensions.cs b/src/ImageSharp.Formats.Gif/ImageExtensions.cs index 09c836a68..1ba03ed35 100644 --- a/src/ImageSharp.Formats.Gif/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Gif/ImageExtensions.cs @@ -21,13 +21,34 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. Between 1 and 256. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsGif(this Image source, Stream stream, int quality = 256) - where TColor : struct, IPackedPixel, IEquatable - => source.Save(stream, new GifEncoder { Quality = quality }); + public static Image SaveAsGif(this Image source, Stream stream) + where TColor : struct, IPixel + { + return SaveAsGif(source, stream, null); + } + + /// + /// Saves the image to the given stream with 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. + /// + /// The . + /// + public static Image SaveAsGif(this Image source, Stream stream, IGifEncoderOptions options) + where TColor : struct, IPixel + { + GifEncoder encoder = new GifEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs new file mode 100644 index 000000000..900d77ec4 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System; + + /// + /// A structure to store unprocessed instances and their coordinates while scanning the image. + /// The is present in a "raw" decoded frequency-domain form. + /// We need to apply IDCT and unzigging to transform them into color-space blocks. + /// + internal struct DecodedBlock + { + /// + /// A value indicating whether the instance is initialized. + /// + public bool Initialized; + + /// + /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) + /// + public int Bx; + + /// + /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) + /// + public int By; + + /// + /// The + /// + public Block8x8F Block; + + /// + /// Store the block data into a + /// + /// X coordinate of the block + /// Y coordinate of the block + /// The + public void SaveBlock(int bx, int by, ref Block8x8F block) + { + this.Bx = bx; + this.By = by; + this.Block = block; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockArray.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockArray.cs new file mode 100644 index 000000000..97a79dd61 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockArray.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System; + using System.Buffers; + + /// + /// Because has no information for rented arrays, + /// we need to store the count and the buffer separately when storing pooled arrays. + /// + internal struct DecodedBlockArray : IDisposable + { + /// + /// The used to pool data in . + /// Should always clean arrays when returning! + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(); + + /// + /// Initializes a new instance of the struct. Rents a buffer. + /// + /// The number of valid -s + public DecodedBlockArray(int count) + { + this.Count = count; + this.Buffer = ArrayPool.Rent(count); + } + + /// + /// Gets the number of actual -s inside + /// + public int Count { get; } + + /// + /// Gets the rented buffer. + /// + public DecodedBlock[] Buffer { get; private set; } + + /// + /// Returns the rented buffer to the pool. + /// + public void Dispose() + { + if (this.Buffer != null) + { + ArrayPool.Return(this.Buffer, true); + this.Buffer = null; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs deleted file mode 100644 index 04ece04ee..000000000 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats.Jpg -{ - using System; - using System.Buffers; - - /// - /// A structure to store unprocessed instances and their coordinates while scanning the image. - /// - internal struct DecodedBlockMemento - { - /// - /// A value indicating whether the instance is initialized. - /// - public bool Initialized; - - /// - /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) - /// - public int Bx; - - /// - /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) - /// - public int By; - - /// - /// The - /// - public Block8x8F Block; - - /// - /// Store the block data into a at the given index of an . - /// - /// The array - /// The index in the array - /// X coordinate of the block - /// Y coordinate of the block - /// The - public static void Store(ref DecodedBlockMemento.Array blockArray, int index, int bx, int by, ref Block8x8F block) - { - if (index >= blockArray.Count) - { - throw new IndexOutOfRangeException("Block index is out of range in DecodedBlockMemento.Store()!"); - } - - blockArray.Buffer[index].Initialized = true; - blockArray.Buffer[index].Bx = bx; - blockArray.Buffer[index].By = by; - blockArray.Buffer[index].Block = block; - } - - /// - /// Because has no information for rented arrays, we need to store the count and the buffer separately. - /// - public struct Array : IDisposable - { - /// - /// The used to pool data in . - /// Should always clean arrays when returning! - /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(); - - /// - /// Initializes a new instance of the struct. Rents a buffer. - /// - /// The number of valid -s - public Array(int count) - { - this.Count = count; - this.Buffer = ArrayPool.Rent(count); - } - - /// - /// Gets the number of actual -s inside - /// - public int Count { get; } - - /// - /// Gets the rented buffer. - /// - public DecodedBlockMemento[] Buffer { get; private set; } - - /// - /// Returns the rented buffer to the pool. - /// - public void Dispose() - { - if (this.Buffer != null) - { - ArrayPool.Return(this.Buffer, true); - this.Buffer = null; - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs index 03013219c..390e5dd15 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs @@ -12,6 +12,16 @@ namespace ImageSharp.Formats.Jpg /// internal struct HuffmanTree : IDisposable { + /// + /// The index of the AC table row + /// + public const int AcTableIndex = 1; + + /// + /// The index of the DC table row + /// + public const int DcTableIndex = 0; + /// /// The maximum (inclusive) number of codes in a Huffman tree. /// diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegBlockProcessor.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegBlockProcessor.cs new file mode 100644 index 000000000..85018a06f --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegBlockProcessor.cs @@ -0,0 +1,166 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct JpegBlockProcessor + { + /// + /// The + /// + private ComputationData data; + + /// + /// Pointers to elements of + /// + private DataPointers pointers; + + /// + /// The component index. + /// + private int componentIndex; + + /// + /// Initialize the instance on the stack. + /// + /// The instance + /// The current component index + public static void Init(JpegBlockProcessor* processor, int componentIndex) + { + processor->componentIndex = componentIndex; + processor->data = ComputationData.Create(); + processor->pointers = new DataPointers(&processor->data); + } + + /// + /// Dequantize, perform the inverse DCT and store the blocks to the into the corresponding instances. + /// + /// The instance + public void ProcessAllBlocks(JpegDecoderCore decoder) + { + DecodedBlockArray blockArray = decoder.DecodedBlocks[this.componentIndex]; + for (int i = 0; i < blockArray.Count; i++) + { + this.ProcessBlockColors(decoder, ref blockArray.Buffer[i]); + } + } + + /// + /// Dequantize, perform the inverse DCT and store decodedBlock.Block to the into the corresponding instance. + /// + /// The + /// The + private void ProcessBlockColors(JpegDecoderCore decoder, ref DecodedBlock decodedBlock) + { + this.data.Block = decodedBlock.Block; + int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; + this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + Block8x8F* b = this.pointers.Block; + + Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); + + DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); + + var destChannel = decoder.GetDestinationChannel(this.componentIndex); + var destArea = destChannel.GetOffsetedSubAreaForBlock(decodedBlock.Bx, decodedBlock.By); + destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); + } + + /// + /// Holds the "large" data blocks needed for computations. + /// + [StructLayout(LayoutKind.Sequential)] + public struct ComputationData + { + /// + /// Temporal block 1 to store intermediate and/or final computation results + /// + public Block8x8F Block; + + /// + /// Temporal block 1 to store intermediate and/or final computation results + /// + public Block8x8F Temp1; + + /// + /// Temporal block 2 to store intermediate and/or final computation results + /// + public Block8x8F Temp2; + + /// + /// The quantization table as + /// + public Block8x8F QuantiazationTable; + + /// + /// The jpeg unzig data + /// + public UnzigData Unzig; + + /// + /// Creates and initializes a new instance + /// + /// The + public static ComputationData Create() + { + ComputationData data = default(ComputationData); + data.Unzig = UnzigData.Create(); + return data; + } + } + + /// + /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of + /// + public struct DataPointers + { + /// + /// Pointer to + /// + public Block8x8F* Block; + + /// + /// Pointer to + /// + public Block8x8F* Temp1; + + /// + /// Pointer to + /// + public Block8x8F* Temp2; + + /// + /// Pointer to + /// + public Block8x8F* QuantiazationTable; + + /// + /// Pointer to as int* + /// + public int* Unzig; + + /// + /// Initializes a new instance of the struct. + /// + /// Pointer to + internal DataPointers(ComputationData* dataPtr) + { + this.Block = &dataPtr->Block; + this.Temp1 = &dataPtr->Temp1; + this.Temp2 = &dataPtr->Temp2; + this.QuantiazationTable = &dataPtr->QuantiazationTable; + this.Unzig = dataPtr->Unzig.Data; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs index 06f170be5..7b910cdd2 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs @@ -23,21 +23,6 @@ namespace ImageSharp.Formats.Jpg /// public Block8x8F Block; - /// - /// Temporal block 1 to store intermediate and/or final computation results - /// - public Block8x8F Temp1; - - /// - /// Temporal block 2 to store intermediate and/or final computation results - /// - public Block8x8F Temp2; - - /// - /// The quantization table as - /// - public Block8x8F QuantiazationTable; - /// /// The jpeg unzig data /// diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.DataPointers.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.DataPointers.cs index b76ad59bb..52e25f3a8 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.DataPointers.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.DataPointers.cs @@ -20,21 +20,6 @@ namespace ImageSharp.Formats.Jpg /// public Block8x8F* Block; - /// - /// Pointer to - /// - public Block8x8F* Temp1; - - /// - /// Pointer to - /// - public Block8x8F* Temp2; - - /// - /// Pointer to - /// - public Block8x8F* QuantiazationTable; - /// /// Pointer to as int* /// @@ -57,9 +42,6 @@ namespace ImageSharp.Formats.Jpg public DataPointers(ComputationData* basePtr) { this.Block = &basePtr->Block; - this.Temp1 = &basePtr->Temp1; - this.Temp2 = &basePtr->Temp2; - this.QuantiazationTable = &basePtr->QuantiazationTable; this.Unzig = basePtr->Unzig.Data; this.ComponentScan = (ComponentScan*)basePtr->ScanData; this.Dc = basePtr->Dc; diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 0e389771c..10f859e42 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -7,10 +7,11 @@ namespace ImageSharp.Formats.Jpg { using System; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// - /// Encapsulates the impementation of Jpeg SOS decoder. See JpegScanDecoder.md! - /// TODO: Split JpegScanDecoder: 1. JpegScanDecoder for Huffman-decoding () 2. JpegBlockProcessor for processing () + /// Encapsulates the impementation of Jpeg SOS Huffman decoding. See JpegScanDecoder.md! + /// /// and are the spectral selection bounds. /// and are the successive approximation high and low values. /// The spec calls these values Ss, Se, Ah and Al. @@ -26,17 +27,21 @@ namespace ImageSharp.Formats.Jpg /// significant bit. /// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. /// + [StructLayout(LayoutKind.Sequential)] internal unsafe partial struct JpegScanDecoder { + // The JpegScanDecoder members should be ordered in a way that results in optimal memory layout. +#pragma warning disable SA1202 // ElementsMustBeOrderedByAccess + /// - /// The AC table index + /// The buffer /// - public const int AcTableIndex = 1; + private ComputationData data; /// - /// The DC table index + /// Pointers to elements of /// - public const int DcTableIndex = 0; + private DataPointers pointers; /// /// The current component index @@ -88,16 +93,6 @@ namespace ImageSharp.Formats.Jpg /// private int eobRun; - /// - /// Pointers to elements of - /// - private DataPointers pointers; - - /// - /// The buffer - /// - private ComputationData data; - /// /// Initializes a default-constructed instance for reading data from -s stream. /// @@ -105,30 +100,10 @@ namespace ImageSharp.Formats.Jpg /// The instance /// The remaining bytes in the segment block. public static void InitStreamReading(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) - { - Init(p); - p->InitStreamReadingImpl(decoder, remaining); - } - - /// - /// Initializes a default-constructed instance, filling the data and setting the pointers. - /// - /// Pointer to on the stack - public static void Init(JpegScanDecoder* p) { p->data = ComputationData.Create(); p->pointers = new DataPointers(&p->data); - } - - /// - /// Loads the data from the given into the block. - /// - /// The - public void LoadMemento(ref DecodedBlockMemento memento) - { - this.bx = memento.Bx; - this.by = memento.By; - this.data.Block = memento.Block; + p->InitStreamReadingImpl(decoder, remaining); } /// @@ -204,8 +179,8 @@ namespace ImageSharp.Formats.Jpg } // Store the decoded block - DecodedBlockMemento.Array blocks = decoder.DecodedBlocks[this.ComponentIndex]; - DecodedBlockMemento.Store(ref blocks, blockIndex, this.bx, this.by, ref this.data.Block); + DecodedBlockArray blocks = decoder.DecodedBlocks[this.ComponentIndex]; + blocks.Buffer[blockIndex].SaveBlock(this.bx, this.by, ref this.data.Block); } // for j @@ -251,26 +226,6 @@ namespace ImageSharp.Formats.Jpg } } - /// - /// Dequantize, perform the inverse DCT and store the block to the into the corresponding instances. - /// - /// The instance - public void ProcessBlockColors(JpegDecoderCore decoder) - { - int qtIndex = decoder.ComponentArray[this.ComponentIndex].Selector; - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - - Block8x8F* b = this.pointers.Block; - - Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); - - DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); - - var destChannel = decoder.GetDestinationChannel(this.ComponentIndex); - var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); - destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); - } - private void ResetDc() { Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); @@ -351,8 +306,7 @@ namespace ImageSharp.Formats.Jpg private void DecodeBlock(JpegDecoderCore decoder, int scanIndex) { var b = this.pointers.Block; - DecoderErrorCode errorCode; - int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; + int huffmannIdx = (HuffmanTree.AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; if (this.ah != 0) { this.Refine(ref decoder.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); @@ -360,13 +314,14 @@ namespace ImageSharp.Formats.Jpg else { int zig = this.zigStart; + DecoderErrorCode errorCode; if (zig == 0) { zig++; // Decode the DC coefficient, as specified in section F.2.2.1. int value; - int huffmanIndex = (DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; + int huffmanIndex = (HuffmanTree.DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; errorCode = decoder.InputProcessor.DecodeHuffmanUnsafe( ref decoder.HuffmanTrees[huffmanIndex], out value); diff --git a/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs new file mode 100644 index 000000000..a54517965 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public interface IJpegEncoderOptions : IEncoderOptions + { + /// + /// Gets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// The quality of the jpg image from 0 to 100. + int Quality { get; } + + /// + /// Gets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + JpegSubsample? Subsample { get; } + } +} diff --git a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs index c0cda4d46..351275ebb 100644 --- a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs @@ -21,13 +21,34 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The quality to save the image to. Between 1 and 100. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsJpeg(this Image source, Stream stream, int quality = 75) - where TColor : struct, IPackedPixel, IEquatable - => source.Save(stream, new JpegEncoder { Quality = quality }); + public static Image SaveAsJpeg(this Image source, Stream stream) + where TColor : struct, IPixel + { + return 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. + /// + /// The . + /// + public static Image SaveAsJpeg(this Image source, Stream stream, IJpegEncoderOptions options) + where TColor : struct, IPixel + { + JpegEncoder encoder = new JpegEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs index 9ee216b4d..eeb371d1e 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs @@ -14,13 +14,13 @@ namespace ImageSharp.Formats public class JpegDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { + public void Decode(Image image, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel + { Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); - using (JpegDecoderCore decoder = new JpegDecoderCore()) + using (JpegDecoderCore decoder = new JpegDecoderCore(options)) { decoder.Decode(image, stream, false); } diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index eca4d4622..f1b85fa0b 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -2,13 +2,12 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Formats { using System; - using System.Buffers; using System.IO; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; using System.Threading.Tasks; using ImageSharp.Formats.Jpg; @@ -38,6 +37,11 @@ namespace ImageSharp.Formats public InputProcessor InputProcessor; #pragma warning restore SA401 + /// + /// The decoder options. + /// + private readonly IDecoderOptions options; + /// /// The App14 marker color-space /// @@ -68,6 +72,11 @@ namespace ImageSharp.Formats /// private bool isJfif; + /// + /// Whether the image has a EXIF header + /// + private bool isExif; + /// /// The vertical resolution. Calculated if the image has a JFIF header. /// @@ -81,13 +90,15 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - public JpegDecoderCore() + /// The decoder options. + public JpegDecoderCore(IDecoderOptions options) { + this.options = options ?? new DecoderOptions(); this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.ComponentArray = new Component[MaxComponents]; - this.DecodedBlocks = new DecodedBlockMemento.Array[MaxComponents]; + this.DecodedBlocks = new DecodedBlockArray[MaxComponents]; } /// @@ -101,10 +112,12 @@ namespace ImageSharp.Formats public HuffmanTree[] HuffmanTrees { get; } /// - /// Gets the saved state between progressive-mode scans. - /// TODO: Also save non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop) + /// Gets the array of -s storing the "raw" frequency-domain decoded blocks. + /// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks. + /// This is done by . + /// When ==true, we are touching these blocks multiple times - each time we process a Scan. /// - public DecodedBlockMemento.Array[] DecodedBlocks { get; } + public DecodedBlockArray[] DecodedBlocks { get; } /// /// Gets the quantization tables, in zigzag order. @@ -171,7 +184,7 @@ namespace ImageSharp.Formats /// The stream, where the image should be. /// Whether to decode metadata only. public void Decode(Image image, Stream stream, bool metadataOnly) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.ProcessStream(image, stream, metadataOnly); if (!metadataOnly) @@ -191,7 +204,7 @@ namespace ImageSharp.Formats this.HuffmanTrees[i].Dispose(); } - foreach (DecodedBlockMemento.Array blockArray in this.DecodedBlocks) + foreach (DecodedBlockArray blockArray in this.DecodedBlocks) { blockArray.Dispose(); } @@ -242,7 +255,7 @@ namespace ImageSharp.Formats /// The cr chroma component. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int ccb = cb - 128; int ccr = cr - 128; @@ -268,7 +281,7 @@ namespace ImageSharp.Formats /// The stream /// Whether to decode metadata only. private void ProcessStream(Image image, Stream stream, bool metadataOnly) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.InputStream = stream; this.InputProcessor = new InputProcessor(stream, this.Temp); @@ -461,28 +474,21 @@ namespace ImageSharp.Formats /// /// Process the blocks in into Jpeg image channels ( and ) - /// The blocks are expected in a "raw" frequency-domain decoded format. We need to apply IDCT and unzigging to transform them into color-space blocks. + /// are in a "raw" frequency-domain form. We need to apply IDCT, dequantization and unzigging to transform them into color-space blocks. /// We can copy these blocks into -s afterwards. /// /// The pixel type private void ProcessBlocksIntoJpegImageChannels() - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Parallel.For( 0, this.ComponentCount, componentIndex => { - JpegScanDecoder scanDecoder = default(JpegScanDecoder); - JpegScanDecoder.Init(&scanDecoder); - - scanDecoder.ComponentIndex = componentIndex; - DecodedBlockMemento.Array blockArray = this.DecodedBlocks[componentIndex]; - for (int i = 0; i < blockArray.Count; i++) - { - scanDecoder.LoadMemento(ref blockArray.Buffer[i]); - scanDecoder.ProcessBlockColors(this); - } + JpegBlockProcessor processor = default(JpegBlockProcessor); + JpegBlockProcessor.Init(&processor, componentIndex); + processor.ProcessAllBlocks(this); }); } @@ -492,7 +498,7 @@ namespace ImageSharp.Formats /// The pixel type /// The destination image private void ConvertJpegPixelsToImagePixels(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (this.grayImage.IsInitialized) { @@ -550,12 +556,25 @@ namespace ImageSharp.Formats /// The pixel format. /// The image to assign the resolution to. private void AssignResolution(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) { - image.HorizontalResolution = this.horizontalResolution; - image.VerticalResolution = this.verticalResolution; + image.MetaData.HorizontalResolution = this.horizontalResolution; + image.MetaData.VerticalResolution = this.verticalResolution; + } + else if (this.isExif) + { + ExifValue horizontal = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); + ExifValue vertical = image.MetaData.ExifProfile.GetValue(ExifTag.YResolution); + double horizontalValue = horizontal != null ? ((Rational)horizontal.Value).ToDouble() : 0; + double verticalValue = vertical != null ? ((Rational)vertical.Value).ToDouble() : 0; + + if (horizontalValue > 0 && verticalValue > 0) + { + image.MetaData.HorizontalResolution = horizontalValue; + image.MetaData.VerticalResolution = verticalValue; + } } } @@ -567,7 +586,7 @@ namespace ImageSharp.Formats /// The image height. /// The image. private void ConvertFromCmyk(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; @@ -591,7 +610,7 @@ namespace ImageSharp.Formats byte yellow = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; TColor packed = default(TColor); - this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); + this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); pixels[x, y] = packed; } }); @@ -608,7 +627,7 @@ namespace ImageSharp.Formats /// The image height. /// The image. private void ConvertFromGrayScale(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { image.InitPixels(width, height); @@ -643,7 +662,7 @@ namespace ImageSharp.Formats /// The height. /// The image. private void ConvertFromRGB(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; image.InitPixels(width, height); @@ -684,7 +703,7 @@ namespace ImageSharp.Formats /// The image height. /// The image. private void ConvertFromYCbCr(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; image.InitPixels(width, height); @@ -725,7 +744,7 @@ namespace ImageSharp.Formats /// The image height. /// The image. private void ConvertFromYcck(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; @@ -749,7 +768,7 @@ namespace ImageSharp.Formats byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; TColor packed = default(TColor); - this.PackYcck(ref packed, yy, cb, cr, x, y); + this.PackYcck(ref packed, yy, cb, cr, x, y); pixels[x, y] = packed; } }); @@ -850,7 +869,7 @@ namespace ImageSharp.Formats /// The x-position within the image. /// The y-position within the image. private void PackCmyk(ref TColor packed, byte c, byte m, byte y, int xx, int yy) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Get keyline float keyline = (255 - this.blackImage[xx, yy]) / 255F; @@ -875,7 +894,7 @@ namespace ImageSharp.Formats /// The x-position within the image. /// The y-position within the image. private void PackYcck(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get // CMY, and patch in the original K. The RGB to CMY inversion cancels @@ -944,9 +963,9 @@ namespace ImageSharp.Formats /// The remaining bytes in the segment block. /// The image. private void ProcessApp1Marker(int remaining, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - if (remaining < 6) + if (remaining < 6 || this.options.IgnoreMetadata) { this.InputProcessor.Skip(remaining); return; @@ -958,7 +977,8 @@ namespace ImageSharp.Formats if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0' && profile[5] == '\0') { - image.ExifProfile = new ExifProfile(profile); + this.isExif = true; + image.MetaData.ExifProfile = new ExifProfile(profile); } } @@ -983,8 +1003,8 @@ namespace ImageSharp.Formats if (this.isJfif) { - this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[10] << 8)); - this.verticalResolution = (short)(this.Temp[11] + (this.Temp[12] << 8)); + this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[8] << 8)); + this.verticalResolution = (short)(this.Temp[11] + (this.Temp[10] << 8)); } if (remaining > 0) @@ -1042,7 +1062,7 @@ namespace ImageSharp.Formats } this.InputProcessor.ReadFull(this.Temp, 0, remaining); - this.RestartInterval = ((int)this.Temp[0] << 8) + (int)this.Temp[1]; + this.RestartInterval = (this.Temp[0] << 8) + this.Temp[1]; } /// @@ -1316,7 +1336,7 @@ namespace ImageSharp.Formats { int count = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor * this.ComponentArray[i].VerticalFactor; - this.DecodedBlocks[i] = new DecodedBlockMemento.Array(count); + this.DecodedBlocks[i] = new DecodedBlockArray(count); } } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs index 6f404c9bb..2f2823fa2 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs @@ -5,7 +5,6 @@ namespace ImageSharp.Formats { - using System; using System.IO; /// @@ -13,73 +12,27 @@ namespace ImageSharp.Formats /// public class JpegEncoder : IImageEncoder { - /// - /// The quality used to encode the image. - /// - private int quality = 75; - - /// - /// The subsamples scheme used to encode the image. - /// - private JpegSubsample subsample = JpegSubsample.Ratio420; - - /// - /// Whether subsampling has been specifically set. - /// - private bool subsampleSet; - - /// - /// Gets or sets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). - /// - /// - /// If the quality is less than or equal to 80, the subsampling ratio will switch to - /// - /// The quality of the jpg image from 0 to 100. - public int Quality + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel { - get { return this.quality; } - set { this.quality = value.Clamp(1, 100); } + IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options); + + this.Encode(image, stream, gifOptions); } /// - /// Gets or sets the subsample ration, that will be used to encode the image. + /// Encodes the image to the specified stream from the . /// - /// The subsample ratio of the jpg image. - public JpegSubsample Subsample + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The options for the encoder. + public void Encode(Image image, Stream stream, IJpegEncoderOptions options) + where TColor : struct, IPixel { - get - { - return this.subsample; - } - - set - { - this.subsample = value; - this.subsampleSet = true; - } - } - - /// - public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { - // Ensure that quality can be set but has a fallback. - if (image.Quality > 0) - { - this.Quality = image.Quality; - } - - JpegEncoderCore encode = new JpegEncoderCore(); - if (this.subsampleSet) - { - encode.Encode(image, stream, this.Quality, this.Subsample); - } - else - { - // Use 4:2:0 Subsampling at quality < 91% for reduced filesize. - encode.Encode(image, stream, this.Quality, this.Quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); - } + JpegEncoderCore encode = new JpegEncoderCore(options); + encode.Encode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs index a97bc4bca..66f400c01 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs @@ -8,7 +8,6 @@ namespace ImageSharp.Formats using System; using System.Buffers; using System.IO; - using System.Numerics; using System.Runtime.CompilerServices; using ImageSharp.Formats.Jpg; @@ -119,6 +118,11 @@ namespace ImageSharp.Formats /// private readonly byte[] huffmanBuffer = new byte[179]; + /// + /// The options for the encoder. + /// + private readonly IJpegEncoderOptions options; + /// /// The accumulated bits to write to the stream. /// @@ -149,16 +153,23 @@ namespace ImageSharp.Formats /// private JpegSubsample subsample; + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + public JpegEncoderCore(IJpegEncoderOptions options) + { + this.options = options ?? new JpegEncoderOptions(); + } + /// /// Encode writes the image to the jpeg baseline format with the given options. /// /// The pixel format. /// The image to write from. /// The stream to write to. - /// The quality. - /// The subsampling mode. - public void Encode(Image image, Stream stream, int quality, JpegSubsample sample) - where TColor : struct, IPackedPixel, IEquatable + public void Encode(Image image, Stream stream) + where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -169,18 +180,17 @@ namespace ImageSharp.Formats throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); } - this.outputStream = stream; - this.subsample = sample; - - if (quality < 1) + // Ensure that quality can be set but has a fallback. + int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + if (quality == 0) { - quality = 1; + quality = 75; } - if (quality > 100) - { - quality = 100; - } + quality = quality.Clamp(1, 100); + + this.outputStream = stream; + this.subsample = this.options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); // Convert from a quality rating to a scaling factor. int scale; @@ -201,7 +211,7 @@ namespace ImageSharp.Formats int componentCount = 3; // Write the Start Of Image marker. - this.WriteApplicationHeader((short)image.HorizontalResolution, (short)image.VerticalResolution); + this.WriteApplicationHeader((short)image.MetaData.HorizontalResolution, (short)image.MetaData.VerticalResolution); this.WriteProfiles(image); @@ -288,7 +298,7 @@ namespace ImageSharp.Formats Block8x8F* cbBlock, Block8x8F* crBlock, PixelArea rgbBytes) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { float* yBlockRaw = (float*)yBlock; float* cbBlockRaw = (float*)cbBlock; @@ -434,7 +444,7 @@ namespace ImageSharp.Formats /// The pixel format. /// The pixel accessor providing access to the image pixels. private void Encode444(PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default(Block8x8F); @@ -705,9 +715,15 @@ namespace ImageSharp.Formats /// The image. /// The pixel format. private void WriteProfiles(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - this.WriteProfile(image.ExifProfile); + if (this.options.IgnoreMetadata) + { + return; + } + + image.MetaData.SyncProfiles(); + this.WriteProfile(image.MetaData.ExifProfile); } /// @@ -772,7 +788,7 @@ namespace ImageSharp.Formats /// The pixel format. /// The pixel accessor providing access to the image pixels. private void WriteStartOfScan(PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: We should allow grayscale writing. @@ -799,7 +815,7 @@ namespace ImageSharp.Formats /// The pixel format. /// The pixel accessor providing access to the image pixels. private void Encode420(PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default(Block8x8F); diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs new file mode 100644 index 000000000..73e483164 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public sealed class JpegEncoderOptions : EncoderOptions, IJpegEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public JpegEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private JpegEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// + /// If the quality is less than or equal to 90, the subsampling ratio will switch to + /// + /// The quality of the jpg image from 0 to 100. + public int Quality { get; set; } + + /// + /// Gets or sets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + public JpegSubsample? Subsample { get; set; } + + /// + /// Converts the options to a instance with a + /// cast or by creating a new instance with the specfied options. + /// + /// The options for the encoder. + /// The options for the . + internal static IJpegEncoderOptions Create(IEncoderOptions options) + { + return options as IJpegEncoderOptions ?? new JpegEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Jpeg/Utils/JpegUtils.cs b/src/ImageSharp.Formats.Jpeg/Utils/JpegUtils.cs index 7f62058b7..dd96985d9 100644 --- a/src/ImageSharp.Formats.Jpeg/Utils/JpegUtils.cs +++ b/src/ImageSharp.Formats.Jpeg/Utils/JpegUtils.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Formats.Jpg PixelArea dest, int sourceY, int sourceX) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { pixels.SafeCopyTo(dest, sourceY, sourceX); int stretchFromX = pixels.Width - sourceX; @@ -49,13 +49,13 @@ namespace ImageSharp.Formats.Jpg // Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsInvalidStretchStartingPosition(PixelArea area, int fromX, int fromY) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; } private static void StretchPixels(PixelArea area, int fromX, int fromY) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (IsInvalidStretchStartingPosition(area, fromX, fromY)) { diff --git a/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs b/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs index d5f810708..b4ec49946 100644 --- a/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Average filter uses the average of the two neighboring pixels (left and above) to predict /// the value of a pixel. @@ -19,6 +21,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) { // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) @@ -42,6 +45,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) { // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) @@ -67,6 +71,7 @@ namespace ImageSharp.Formats /// The left byte /// The above byte /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Average(byte left, byte above) { return (left + above) >> 1; diff --git a/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs b/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs index e5787a944..5abd89296 100644 --- a/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Runtime.CompilerServices; /// /// The None filter, the scanline is transmitted unmodified; it is only necessary to @@ -19,6 +20,7 @@ namespace ImageSharp.Formats /// /// The scanline to decode /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] Decode(byte[] scanline) { // No change required. @@ -30,6 +32,7 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The filtered scanline result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] result) { // Insert a byte before the data. diff --git a/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs b/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs index ff208f3d7..a43d4f080 100644 --- a/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Runtime.CompilerServices; /// /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), @@ -22,6 +23,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) { // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) @@ -46,6 +48,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) { // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) @@ -76,6 +79,7 @@ namespace ImageSharp.Formats /// /// The . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte PaethPredicator(byte left, byte above, byte upperLeft) { int p = left + above - upperLeft; diff --git a/src/ImageSharp.Formats.Png/Filters/SubFilter.cs b/src/ImageSharp.Formats.Png/Filters/SubFilter.cs index 65e86f4f1..4610ed0ae 100644 --- a/src/ImageSharp.Formats.Png/Filters/SubFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/SubFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Sub filter transmits the difference between each byte and the value of the corresponding byte /// of the prior pixel. @@ -18,6 +20,7 @@ namespace ImageSharp.Formats /// The scanline to decode /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, int bytesPerScanline, int bytesPerPixel) { // Sub(x) + Raw(x-bpp) @@ -37,6 +40,7 @@ namespace ImageSharp.Formats /// The scanline to encode /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel) { // Sub(x) = Raw(x) - Raw(x-bpp) diff --git a/src/ImageSharp.Formats.Png/Filters/UpFilter.cs b/src/ImageSharp.Formats.Png/Filters/UpFilter.cs index 036862ddc..525f50d0f 100644 --- a/src/ImageSharp.Formats.Png/Filters/UpFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/UpFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, /// rather than just to its left, is used as the predictor. @@ -18,6 +20,7 @@ namespace ImageSharp.Formats /// The scanline to decode /// The previous scanline. /// The number of bytes per scanline + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline) { // Up(x) + Prior(x) @@ -39,6 +42,7 @@ namespace ImageSharp.Formats /// The scanline to encode /// The previous scanline. /// The filtered scanline result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result) { // Up(x) = Raw(x) - Prior(x) diff --git a/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs b/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs new file mode 100644 index 000000000..cc6d194bf --- /dev/null +++ b/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the options for the . + /// + public interface IPngDecoderOptions : IDecoderOptions + { + /// + /// Gets the encoding that should be used when reading text chunks. + /// + Encoding TextEncoding { get; } + } +} diff --git a/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs b/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs new file mode 100644 index 000000000..0008080d3 --- /dev/null +++ b/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public interface IPngEncoderOptions : IEncoderOptions + { + /// + /// Gets the quality of output for images. + /// + int Quality { get; } + + /// + /// Gets the png color type + /// + PngColorType PngColorType { get; } + + /// + /// Gets the compression level 1-9. + /// + int CompressionLevel { get; } + + /// + /// Gets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. + /// + /// The gamma value of the image. + float Gamma { get; } + + /// + /// Gets quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + + /// + /// Gets a value indicating whether this instance should write + /// gamma information to the stream. + /// + bool WriteGamma { get; } + } +} diff --git a/src/ImageSharp.Formats.Png/ImageExtensions.cs b/src/ImageSharp.Formats.Png/ImageExtensions.cs index d46c46217..79e96175c 100644 --- a/src/ImageSharp.Formats.Png/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Png/ImageExtensions.cs @@ -5,7 +5,6 @@ namespace ImageSharp { - using System; using System.IO; using Formats; @@ -21,15 +20,34 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. - /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. - /// /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsPng(this Image source, Stream stream, int quality = int.MaxValue) - where TColor : struct, IPackedPixel, IEquatable - => source.Save(stream, new PngEncoder { Quality = quality }); + public static Image SaveAsPng(this Image source, Stream stream) + where TColor : struct, IPixel + { + return 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. + /// + /// The . + /// + public static Image SaveAsPng(this Image source, Stream stream, IPngEncoderOptions options) + where TColor : struct, IPixel + { + PngEncoder encoder = new PngEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoder.cs b/src/ImageSharp.Formats.Png/PngDecoder.cs index 845f0f222..d527e1654 100644 --- a/src/ImageSharp.Formats.Png/PngDecoder.cs +++ b/src/ImageSharp.Formats.Png/PngDecoder.cs @@ -30,16 +30,26 @@ namespace ImageSharp.Formats /// public class PngDecoder : IImageDecoder { + /// + public void Decode(Image image, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel + { + IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); + + this.Decode(image, stream, pngOptions); + } + /// /// Decodes the image from the specified stream to the . /// /// The pixel format. /// The to decode to. /// The containing image data. - public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + /// The options for the decoder. + public void Decode(Image image, Stream stream, IPngDecoderOptions options) + where TColor : struct, IPixel { - new PngDecoderCore().Decode(image, stream); + new PngDecoderCore(options).Decode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index ffc037b62..fd03ed39b 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; using System.IO; using System.Linq; - using System.Text; + using System.Runtime.CompilerServices; using static ComparableExtensions; @@ -64,6 +64,11 @@ namespace ImageSharp.Formats /// private readonly char[] chars = new char[4]; + /// + /// The decoder options. + /// + private readonly IPngDecoderOptions options; + /// /// Reusable crc for validating chunks. /// @@ -104,6 +109,11 @@ namespace ImageSharp.Formats /// private byte[] paletteAlpha; + /// + /// A value indicating whether the end chunk has been reached. + /// + private bool isEndChunkReached; + /// /// Initializes static members of the class. /// @@ -120,6 +130,15 @@ namespace ImageSharp.Formats ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 }); } + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public PngDecoderCore(IPngDecoderOptions options) + { + this.options = options ?? new PngDecoderOptions(); + } + /// /// Gets or sets the png color type /// @@ -138,24 +157,17 @@ namespace ImageSharp.Formats /// Thrown if the image is larger than the maximum allowable size. /// public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image currentImage = image; this.currentStream = stream; this.currentStream.Skip(8); - bool isEndChunkReached = false; - using (MemoryStream dataStream = new MemoryStream()) { PngChunk currentChunk; - while ((currentChunk = this.ReadChunk()) != null) + while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null) { - if (isEndChunkReached) - { - throw new ImageFormatException("Image does not end with end chunk."); - } - try { switch (currentChunk.Type) @@ -174,7 +186,7 @@ namespace ImageSharp.Formats byte[] pal = new byte[currentChunk.Length]; Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length); this.palette = pal; - image.Quality = pal.Length / 3; + image.MetaData.Quality = pal.Length / 3; break; case PngChunkTypes.PaletteAlpha: byte[] alpha = new byte[currentChunk.Length]; @@ -185,7 +197,7 @@ namespace ImageSharp.Formats this.ReadTextChunk(currentImage, currentChunk.Data, currentChunk.Length); break; case PngChunkTypes.End: - isEndChunkReached = true; + this.isEndChunkReached = true; break; } } @@ -262,14 +274,14 @@ namespace ImageSharp.Formats /// The image to read to. /// The data containing physical data. private void ReadPhysicalChunk(Image image, byte[] data) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { data.ReverseBytes(0, 4); data.ReverseBytes(4, 4); // 39.3700787 = inches in a meter. - image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; - image.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; + image.MetaData.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; + image.MetaData.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; } /// @@ -325,7 +337,7 @@ namespace ImageSharp.Formats /// The containing data. /// The pixel data. private void ReadScanlines(MemoryStream dataStream, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; @@ -356,7 +368,7 @@ namespace ImageSharp.Formats /// The compressed pixel data stream. /// The image pixel accessor. private void DecodePixelData(Stream compressedStream, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); @@ -429,7 +441,7 @@ namespace ImageSharp.Formats /// The compressed pixel data stream. /// The image pixel accessor. private void DecodeInterlacedPixelData(Stream compressedStream, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); @@ -518,7 +530,7 @@ namespace ImageSharp.Formats /// The current image row. /// The image pixels private void ProcessDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { TColor color = default(TColor); switch (this.PngColorType) @@ -643,7 +655,7 @@ namespace ImageSharp.Formats /// The column start index. Always 0 for none interlaced images. /// The column increment. Always 1 for none interlaced images. private void ProcessInterlacedDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels, int pixelOffset = 0, int increment = 1) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { TColor color = default(TColor); @@ -761,8 +773,13 @@ namespace ImageSharp.Formats /// The containing data. /// The maximum length to read. private void ReadTextChunk(Image image, byte[] data, int length) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { + if (this.options.IgnoreMetadata) + { + return; + } + int zeroIndex = 0; for (int i = 0; i < length; i++) @@ -774,10 +791,10 @@ namespace ImageSharp.Formats } } - string name = Encoding.Unicode.GetString(data, 0, zeroIndex); - string value = Encoding.Unicode.GetString(data, zeroIndex + 1, length - zeroIndex - 1); + string name = this.options.TextEncoding.GetString(data, 0, zeroIndex); + string value = this.options.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); - image.Properties.Add(new ImageProperty(name, value)); + image.MetaData.Properties.Add(new ImageProperty(name, value)); } /// @@ -927,12 +944,7 @@ namespace ImageSharp.Formats private void ReadChunkLength(PngChunk chunk) { int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4); - if (numBytes >= 1 && numBytes <= 3) - { - throw new ImageFormatException("Image stream is not valid!"); - } - - if (numBytes <= 0) + if (numBytes < 4) { chunk.Length = -1; return; @@ -948,6 +960,7 @@ namespace ImageSharp.Formats /// /// Th current pass index /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ComputeColumnsAdam7(int pass) { int width = this.header.Width; diff --git a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs new file mode 100644 index 000000000..e8990ec45 --- /dev/null +++ b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the options for the . + /// + public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions + { + private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// Initializes a new instance of the class. + /// + public PngDecoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the decoder. + private PngDecoderOptions(IDecoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the encoding that should be used when reading text chunks. + /// + public Encoding TextEncoding { get; set; } = DefaultEncoding; + + /// + /// Converts the options to a instance with a cast + /// or by creating a new instance with the specfied options. + /// + /// The options for the decoder. + /// The options for the . + internal static IPngDecoderOptions Create(IDecoderOptions options) + { + return options as IPngDecoderOptions ?? new PngDecoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Png/PngEncoder.cs b/src/ImageSharp.Formats.Png/PngEncoder.cs index 13125e30e..e583f381f 100644 --- a/src/ImageSharp.Formats.Png/PngEncoder.cs +++ b/src/ImageSharp.Formats.Png/PngEncoder.cs @@ -5,72 +5,34 @@ namespace ImageSharp.Formats { - using System; using System.IO; - using ImageSharp.Quantizers; - /// /// Image encoder for writing image data to a stream in png format. /// public class PngEncoder : IImageEncoder { - /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the png color type - /// - public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; - - /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. - /// - public int CompressionLevel { get; set; } = 6; - - /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. - /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// Gets or sets quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel + { + IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options); - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 0; + this.Encode(image, stream, pngOptions); + } /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. + /// Encodes the image to the specified stream from the . /// - public bool WriteGamma { get; set; } - - /// - public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { - PngEncoderCore encoder = new PngEncoderCore - { - CompressionLevel = this.CompressionLevel, - Gamma = this.Gamma, - Quality = this.Quality, - PngColorType = this.PngColorType, - Quantizer = this.Quantizer, - WriteGamma = this.WriteGamma, - Threshold = this.Threshold - }; - - encoder.Encode(image, stream); + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The options for the encoder. + public void Encode(Image image, Stream stream, IPngEncoderOptions options) + where TColor : struct, IPixel + { + PngEncoderCore encode = new PngEncoderCore(options); + encode.Encode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Png/PngEncoderCore.cs b/src/ImageSharp.Formats.Png/PngEncoderCore.cs index 2ab42623d..7950d260c 100644 --- a/src/ImageSharp.Formats.Png/PngEncoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngEncoderCore.cs @@ -40,6 +40,11 @@ namespace ImageSharp.Formats /// private readonly Crc32 crc = new Crc32(); + /// + /// The options for the encoder. + /// + private readonly IPngEncoderOptions options; + /// /// Contains the raw pixel data from an indexed image. /// @@ -86,53 +91,37 @@ namespace ImageSharp.Formats private byte[] paeth; /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the png color type + /// The quality of output for images. /// - public PngColorType PngColorType { get; set; } + private int quality; /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. + /// The png color type. /// - public int CompressionLevel { get; set; } = 6; + private PngColorType pngColorType; /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. + /// The quantizer for reducing the color count. /// - public bool WriteGamma { get; set; } + private IQuantizer quantizer; /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. + /// Initializes a new instance of the class. /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// Gets or sets the quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } + /// The options for the encoder. + public PngEncoderCore(IPngEncoderOptions options) + { + this.options = options ?? new PngEncoderOptions(); + } /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// /// The pixel format. /// The to encode from. /// The to encode the image data to. - public void Encode(ImageBase image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + public void Encode(Image image, Stream stream) + where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -153,23 +142,26 @@ namespace ImageSharp.Formats stream.Write(this.chunkDataBuffer, 0, 8); // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.Quality; - this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; + this.quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + this.quality = this.quality > 0 ? this.quality.Clamp(1, int.MaxValue) : int.MaxValue; + + this.pngColorType = this.options.PngColorType; + this.quantizer = this.options.Quantizer; // Set correct color type if the color count is 256 or less. - if (this.Quality <= 256) + if (this.quality <= 256) { - this.PngColorType = PngColorType.Palette; + this.pngColorType = PngColorType.Palette; } - if (this.PngColorType == PngColorType.Palette && this.Quality > 256) + if (this.pngColorType == PngColorType.Palette && this.quality > 256) { - this.Quality = 256; + this.quality = 256; } // Set correct bit depth. - this.bitDepth = this.Quality <= 256 - ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) + this.bitDepth = this.quality <= 256 + ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.quality).Clamp(1, 8) : (byte)8; // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk @@ -188,7 +180,7 @@ namespace ImageSharp.Formats { Width = image.Width, Height = image.Height, - ColorType = (byte)this.PngColorType, + ColorType = (byte)this.pngColorType, BitDepth = this.bitDepth, FilterMethod = 0, // None CompressionMethod = 0, @@ -198,7 +190,7 @@ namespace ImageSharp.Formats this.WriteHeaderChunk(stream, header); // Collect the indexed pixel data - if (this.PngColorType == PngColorType.Palette) + if (this.pngColorType == PngColorType.Palette) { this.CollectIndexedBytes(image, stream, header); } @@ -262,7 +254,7 @@ namespace ImageSharp.Formats /// The containing image data. /// The . private void CollectIndexedBytes(ImageBase image, Stream stream, PngHeader header) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Quantize the image and get the pixels. QuantizedImage quantized = this.WritePaletteChunk(stream, header, image); @@ -277,7 +269,7 @@ namespace ImageSharp.Formats /// The row index. /// The raw scanline. private void CollectGrayscaleBytes(PixelAccessor pixels, int row, byte[] rawScanline) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Copy the pixels across from the image. // Reuse the chunk type buffer. @@ -311,7 +303,7 @@ namespace ImageSharp.Formats /// The row index. /// The raw scanline. private void CollectColorBytes(PixelAccessor pixels, int row, byte[] rawScanline) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // We can use the optimized PixelAccessor here and copy the bytes in unmanaged memory. using (PixelArea pixelRow = new PixelArea(this.width, rawScanline, this.bytesPerPixel == 4 ? ComponentOrder.Xyzw : ComponentOrder.Xyz)) @@ -332,9 +324,9 @@ namespace ImageSharp.Formats /// The filtered scanline result. /// The private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Palette: Buffer.BlockCopy(this.palettePixelData, row * rawScanline.Length, rawScanline, 0, rawScanline.Length); @@ -362,7 +354,7 @@ namespace ImageSharp.Formats private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) { // Palette images don't compress well with adaptive filtering. - if (this.PngColorType == PngColorType.Palette || this.bitDepth < 8) + if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { NoneFilter.Encode(rawScanline, result); return result; @@ -436,7 +428,7 @@ namespace ImageSharp.Formats /// The private int CalculateBytesPerPixel() { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: return 1; @@ -486,20 +478,20 @@ namespace ImageSharp.Formats /// The image to encode. /// The private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - if (this.Quality > 256) + if (this.quality > 256) { return null; } - if (this.Quantizer == null) + if (this.quantizer == null) { - this.Quantizer = new WuQuantizer(); + this.quantizer = new OctreeQuantizer(); } // Quantize the image returning a palette. This boxing is icky. - QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.quality); // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; @@ -524,7 +516,7 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; - if (alpha <= this.Threshold) + if (alpha <= this.options.Threshold) { transparentPixels.Add((byte)offset); } @@ -554,14 +546,14 @@ namespace ImageSharp.Formats /// The containing image data. /// The image base. private void WritePhysicalChunk(Stream stream, ImageBase imageBase) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image image = imageBase as Image; - if (image != null && image.HorizontalResolution > 0 && image.VerticalResolution > 0) + if (image != null && image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0) { // 39.3700787 = inches in a meter. - int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787D); - int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787D); + int dpmX = (int)Math.Round(image.MetaData.HorizontalResolution * 39.3700787D); + int dpmY = (int)Math.Round(image.MetaData.VerticalResolution * 39.3700787D); WriteInteger(this.chunkDataBuffer, 0, dpmX); WriteInteger(this.chunkDataBuffer, 4, dpmY); @@ -578,9 +570,9 @@ namespace ImageSharp.Formats /// The containing image data. private void WriteGammaChunk(Stream stream) { - if (this.WriteGamma) + if (this.options.WriteGamma) { - int gammaValue = (int)(this.Gamma * 100000F); + int gammaValue = (int)(this.options.Gamma * 100000F); byte[] size = BitConverter.GetBytes(gammaValue); @@ -600,7 +592,7 @@ namespace ImageSharp.Formats /// The pixel accessor. /// The stream. private void WriteDataChunks(PixelAccessor pixels, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int bytesPerScanline = this.width * this.bytesPerPixel; byte[] previousScanline = new byte[bytesPerScanline]; @@ -608,7 +600,7 @@ namespace ImageSharp.Formats int resultLength = bytesPerScanline + 1; byte[] result = new byte[resultLength]; - if (this.PngColorType != PngColorType.Palette) + if (this.pngColorType != PngColorType.Palette) { this.sub = new byte[resultLength]; this.up = new byte[resultLength]; @@ -622,7 +614,7 @@ namespace ImageSharp.Formats try { memoryStream = new MemoryStream(); - using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) + using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) { for (int y = 0; y < this.height; y++) { diff --git a/src/ImageSharp.Formats.Png/PngEncoderOptions.cs b/src/ImageSharp.Formats.Png/PngEncoderOptions.cs new file mode 100644 index 000000000..2891f1974 --- /dev/null +++ b/src/ImageSharp.Formats.Png/PngEncoderOptions.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public sealed class PngEncoderOptions : EncoderOptions, IPngEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public PngEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private PngEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the png color type + /// + public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; + + /// + /// Gets or sets the compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; + + /// + /// Gets or sets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + public float Gamma { get; set; } = 2.2F; + + /// + /// Gets or sets quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 0; + + /// + /// Gets or sets a value indicating whether this instance should write + /// gamma information to the stream. The default value is false. + /// + public bool WriteGamma { get; set; } + + /// + /// Converts the options to a instance with a + /// cast or by creating a new instance with the specfied options. + /// + /// The options for the encoder. + /// The options for the . + internal static IPngEncoderOptions Create(IEncoderOptions options) + { + return options as IPngEncoderOptions ?? new PngEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs b/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs index 1b5b6c9bb..672726d92 100644 --- a/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs +++ b/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The threshold to apply binarization of the image. Must be between 0 and 1. /// The . public static Image BinaryThreshold(this Image source, float threshold) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return BinaryThreshold(source, threshold, source.Bounds); } @@ -38,9 +38,10 @@ namespace ImageSharp /// /// The . public static Image BinaryThreshold(this Image source, float threshold, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new BinaryThresholdProcessor(threshold)); + source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Binarization/Dither.cs b/src/ImageSharp.Processing/Binarization/Dither.cs new file mode 100644 index 000000000..dd6dfe8a1 --- /dev/null +++ b/src/ImageSharp.Processing/Binarization/Dither.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + using ImageSharp.Dithering; + using ImageSharp.Processing.Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The component index to test the threshold against. Must range from 0 to 3. + /// The . + public static Image Dither(this Image source, IOrderedDither dither, int index = 0) + where TColor : struct, IPixel + { + return Dither(source, dither, source.Bounds, index); + } + + /// + /// 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 component index to test the threshold against. Must range from 0 to 3. + /// The . + public static Image Dither(this Image source, IOrderedDither dither, Rectangle rectangle, int index = 0) + where TColor : struct, IPixel + { + source.ApplyProcessor(new OrderedDitherProcessor(dither, index), rectangle); + return source; + } + + /// + /// 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 Image Dither(this Image source, IErrorDiffuser diffuser, float threshold) + where TColor : struct, IPixel + { + return Dither(source, diffuser, threshold, source.Bounds); + } + + /// + /// 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 Image Dither(this Image source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) + where TColor : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold), rectangle); + return source; + } + } +} diff --git a/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs b/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs index e172a21be..63d6dd33c 100644 --- a/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs +++ b/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image BlackWhite(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return BlackWhite(source, source.Bounds); } @@ -37,9 +37,10 @@ namespace ImageSharp /// /// The . public static Image BlackWhite(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new BlackWhiteProcessor()); + source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs b/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs index 2e90b059e..36a139d0e 100644 --- a/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs +++ b/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The type of color blindness simulator to apply. /// The . public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return ColorBlindness(source, colorBlindness, source.Bounds); } @@ -39,7 +39,7 @@ namespace ImageSharp /// /// The . public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IImageProcessor processor; @@ -78,7 +78,8 @@ namespace ImageSharp break; } - return source.Apply(rectangle, processor); + source.ApplyProcessor(processor, rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs b/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs index f1a17c02b..613b999d4 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The formula to apply to perform the operation. /// The . public static Image Grayscale(this Image source, GrayscaleMode mode = GrayscaleMode.Bt709) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Grayscale(source, source.Bounds, mode); } @@ -39,13 +39,14 @@ namespace ImageSharp /// The formula to apply to perform the operation. /// The . public static Image Grayscale(this Image source, Rectangle rectangle, GrayscaleMode mode = GrayscaleMode.Bt709) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IImageProcessor processor = mode == GrayscaleMode.Bt709 ? (IImageProcessor)new GrayscaleBt709Processor() : new GrayscaleBt601Processor(); - return source.Apply(rectangle, processor); + source.ApplyProcessor(processor, rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Hue.cs b/src/ImageSharp.Processing/ColorMatrix/Hue.cs index f03f65692..8edeb2ff3 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Hue.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Hue.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The angle in degrees to adjust the image. /// The . public static Image Hue(this Image source, float degrees) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Hue(source, degrees, source.Bounds); } @@ -39,9 +39,10 @@ namespace ImageSharp /// /// The . public static Image Hue(this Image source, float degrees, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new HueProcessor(degrees)); + source.ApplyProcessor(new HueProcessor(degrees), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs b/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs index 2592d8090..5084c96b2 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Kodachrome(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Kodachrome(source, source.Bounds); } @@ -37,9 +37,10 @@ namespace ImageSharp /// /// The . public static Image Kodachrome(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new KodachromeProcessor()); + source.ApplyProcessor(new KodachromeProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs index 2605bc301..ef6b23d5d 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Lomograph(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Lomograph(source, source.Bounds); } @@ -37,9 +37,10 @@ namespace ImageSharp /// /// The . public static Image Lomograph(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new LomographProcessor()); + source.ApplyProcessor(new LomographProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs index 5c51a710b..68b10173c 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Polaroid(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Polaroid(source, source.Bounds); } @@ -37,9 +37,10 @@ namespace ImageSharp /// /// The . public static Image Polaroid(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new PolaroidProcessor()); + source.ApplyProcessor(new PolaroidProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Saturation.cs b/src/ImageSharp.Processing/ColorMatrix/Saturation.cs index 773329ea6..7a6359744 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Saturation.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Saturation.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The new saturation of the image. Must be between -100 and 100. /// The . public static Image Saturation(this Image source, int amount) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Saturation(source, amount, source.Bounds); } @@ -39,9 +39,10 @@ namespace ImageSharp /// /// The . public static Image Saturation(this Image source, int amount, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new SaturationProcessor(amount)); + source.ApplyProcessor(new SaturationProcessor(amount), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Sepia.cs b/src/ImageSharp.Processing/ColorMatrix/Sepia.cs index 3f29b93e5..4943635e0 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Sepia.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Sepia.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Sepia(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Sepia(source, source.Bounds); } @@ -37,9 +37,10 @@ namespace ImageSharp /// /// The . public static Image Sepia(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new SepiaProcessor()); + source.ApplyProcessor(new SepiaProcessor(), rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Convolution/BoxBlur.cs b/src/ImageSharp.Processing/Convolution/BoxBlur.cs index e16c30516..428142ffa 100644 --- a/src/ImageSharp.Processing/Convolution/BoxBlur.cs +++ b/src/ImageSharp.Processing/Convolution/BoxBlur.cs @@ -7,7 +7,6 @@ namespace ImageSharp { using System; - using Processing; using Processing.Processors; /// @@ -23,7 +22,7 @@ namespace ImageSharp /// The 'radius' value representing the size of the area to sample. /// The . public static Image BoxBlur(this Image source, int radius = 7) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return BoxBlur(source, radius, source.Bounds); } @@ -39,9 +38,10 @@ namespace ImageSharp /// /// The . public static Image BoxBlur(this Image source, int radius, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new BoxBlurProcessor(radius)); + source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Convolution/DetectEdges.cs b/src/ImageSharp.Processing/Convolution/DetectEdges.cs index 32fc167f1..dba062b56 100644 --- a/src/ImageSharp.Processing/Convolution/DetectEdges.cs +++ b/src/ImageSharp.Processing/Convolution/DetectEdges.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image DetectEdges(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DetectEdges(source, source.Bounds, new SobelProcessor { Grayscale = true }); } @@ -39,7 +39,7 @@ namespace ImageSharp /// /// The . public static Image DetectEdges(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DetectEdges(source, rectangle, new SobelProcessor { Grayscale = true }); } @@ -53,7 +53,7 @@ namespace ImageSharp /// Whether to convert the image to Grayscale first. Defaults to true. /// The . public static Image DetectEdges(this Image source, EdgeDetection filter, bool grayscale = true) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DetectEdges(source, filter, source.Bounds, grayscale); } @@ -70,7 +70,7 @@ namespace ImageSharp /// Whether to convert the image to Grayscale first. Defaults to true. /// The . public static Image DetectEdges(this Image source, EdgeDetection filter, Rectangle rectangle, bool grayscale = true) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IEdgeDetectorProcessor processor; @@ -128,7 +128,7 @@ namespace ImageSharp /// The filter for detecting edges. /// The . public static Image DetectEdges(this Image source, IEdgeDetectorProcessor filter) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DetectEdges(source, source.Bounds, filter); } @@ -144,9 +144,10 @@ namespace ImageSharp /// The filter for detecting edges. /// The . public static Image DetectEdges(this Image source, Rectangle rectangle, IEdgeDetectorProcessor filter) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, filter); + source.ApplyProcessor(filter, rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Convolution/GaussianBlur.cs b/src/ImageSharp.Processing/Convolution/GaussianBlur.cs index 7e8b9a403..81f854638 100644 --- a/src/ImageSharp.Processing/Convolution/GaussianBlur.cs +++ b/src/ImageSharp.Processing/Convolution/GaussianBlur.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The 'sigma' value representing the weight of the blur. /// The . public static Image GaussianBlur(this Image source, float sigma = 3f) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return GaussianBlur(source, sigma, source.Bounds); } @@ -39,9 +39,10 @@ namespace ImageSharp /// /// The . public static Image GaussianBlur(this Image source, float sigma, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new GaussianBlurProcessor(sigma)); + source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs b/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs index ef4910459..61816198a 100644 --- a/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs +++ b/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The 'sigma' value representing the weight of the blur. /// The . public static Image GaussianSharpen(this Image source, float sigma = 3f) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return GaussianSharpen(source, sigma, source.Bounds); } @@ -39,9 +39,10 @@ namespace ImageSharp /// /// The . public static Image GaussianSharpen(this Image source, float sigma, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new GaussianSharpenProcessor(sigma)); + source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/Alpha.cs b/src/ImageSharp.Processing/Effects/Alpha.cs index 856276a89..39849d4d4 100644 --- a/src/ImageSharp.Processing/Effects/Alpha.cs +++ b/src/ImageSharp.Processing/Effects/Alpha.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The new opacity of the image. Must be between 0 and 100. /// The . public static Image Alpha(this Image source, int percent) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Alpha(source, percent, source.Bounds); } @@ -38,9 +38,10 @@ namespace ImageSharp /// /// The . public static Image Alpha(this Image source, int percent, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new AlphaProcessor(percent)); + source.ApplyProcessor(new AlphaProcessor(percent), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/BackgroundColor.cs b/src/ImageSharp.Processing/Effects/BackgroundColor.cs index ac1add351..2e621172e 100644 --- a/src/ImageSharp.Processing/Effects/BackgroundColor.cs +++ b/src/ImageSharp.Processing/Effects/BackgroundColor.cs @@ -22,9 +22,10 @@ namespace ImageSharp /// The color to set as the background. /// The . public static Image BackgroundColor(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(source.Bounds, new BackgroundColorProcessor(color)); + source.ApplyProcessor(new BackgroundColorProcessor(color), source.Bounds); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/Brightness.cs b/src/ImageSharp.Processing/Effects/Brightness.cs index 8c9ff8946..8ba702c4f 100644 --- a/src/ImageSharp.Processing/Effects/Brightness.cs +++ b/src/ImageSharp.Processing/Effects/Brightness.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The new brightness of the image. Must be between -100 and 100. /// The . public static Image Brightness(this Image source, int amount) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Brightness(source, amount, source.Bounds); } @@ -38,9 +38,10 @@ namespace ImageSharp /// /// The . public static Image Brightness(this Image source, int amount, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(rectangle, new BrightnessProcessor(amount)); + where TColor : struct, IPixel + { + source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/Contrast.cs b/src/ImageSharp.Processing/Effects/Contrast.cs index 831028682..0228f4fe3 100644 --- a/src/ImageSharp.Processing/Effects/Contrast.cs +++ b/src/ImageSharp.Processing/Effects/Contrast.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The new contrast of the image. Must be between -100 and 100. /// The . public static Image Contrast(this Image source, int amount) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Contrast(source, amount, source.Bounds); } @@ -38,9 +38,10 @@ namespace ImageSharp /// /// The . public static Image Contrast(this Image source, int amount, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new ContrastProcessor(amount)); + source.ApplyProcessor(new ContrastProcessor(amount), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/Invert.cs b/src/ImageSharp.Processing/Effects/Invert.cs index 31e524000..6c51ad3eb 100644 --- a/src/ImageSharp.Processing/Effects/Invert.cs +++ b/src/ImageSharp.Processing/Effects/Invert.cs @@ -21,7 +21,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Invert(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Invert(source, source.Bounds); } @@ -36,9 +36,10 @@ namespace ImageSharp /// /// The . public static Image Invert(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return source.Apply(rectangle, new InvertProcessor()); + source.ApplyProcessor(new InvertProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/OilPainting.cs b/src/ImageSharp.Processing/Effects/OilPainting.cs index 463cfd675..d7d8444c0 100644 --- a/src/ImageSharp.Processing/Effects/OilPainting.cs +++ b/src/ImageSharp.Processing/Effects/OilPainting.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The number of neighboring pixels used in calculating each individual pixel value. /// The . public static Image OilPaint(this Image source, int levels = 10, int brushSize = 15) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return OilPaint(source, levels, brushSize, source.Bounds); } @@ -40,7 +40,7 @@ namespace ImageSharp /// /// The . public static Image OilPaint(this Image source, int levels, int brushSize, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.MustBeGreaterThan(levels, 0, nameof(levels)); @@ -49,7 +49,8 @@ namespace ImageSharp throw new ArgumentOutOfRangeException(nameof(brushSize)); } - return source.Apply(rectangle, new OilPaintingProcessor(levels, brushSize)); + source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Effects/Pixelate.cs b/src/ImageSharp.Processing/Effects/Pixelate.cs index 92d1fdd69..721dd930b 100644 --- a/src/ImageSharp.Processing/Effects/Pixelate.cs +++ b/src/ImageSharp.Processing/Effects/Pixelate.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The size of the pixels. /// The . public static Image Pixelate(this Image source, int size = 4) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Pixelate(source, size, source.Bounds); } @@ -38,14 +38,15 @@ namespace ImageSharp /// /// The . public static Image Pixelate(this Image source, int size, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (size <= 0 || size > source.Height || size > source.Width) { throw new ArgumentOutOfRangeException(nameof(size)); } - return source.Apply(rectangle, new PixelateProcessor(size)); + source.ApplyProcessor(new PixelateProcessor(size), rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Overlays/Glow.cs b/src/ImageSharp.Processing/Overlays/Glow.cs index 6511407da..e8dfbdf0e 100644 --- a/src/ImageSharp.Processing/Overlays/Glow.cs +++ b/src/ImageSharp.Processing/Overlays/Glow.cs @@ -21,9 +21,9 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Glow(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return Glow(source, default(TColor), source.Bounds.Width * .5F, source.Bounds); + return Glow(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds); } /// @@ -34,7 +34,7 @@ namespace ImageSharp /// The color to set as the glow. /// The . public static Image Glow(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Glow(source, color, source.Bounds.Width * .5F, source.Bounds); } @@ -47,9 +47,9 @@ namespace ImageSharp /// The the radius. /// The . public static Image Glow(this Image source, float radius) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return Glow(source, default(TColor), radius, source.Bounds); + return Glow(source, NamedColors.Black, radius, source.Bounds); } /// @@ -62,9 +62,9 @@ namespace ImageSharp /// /// The . public static Image Glow(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return Glow(source, default(TColor), 0, rectangle); + return Glow(source, NamedColors.Black, 0, rectangle); } /// @@ -79,16 +79,11 @@ namespace ImageSharp /// /// The . public static Image Glow(this Image source, TColor color, float radius, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - GlowProcessor processor = new GlowProcessor { Radius = radius, }; - - if (!color.Equals(default(TColor))) - { - processor.GlowColor = color; - } - - return source.Apply(rectangle, processor); + GlowProcessor processor = new GlowProcessor(color) { Radius = radius, }; + source.ApplyProcessor(processor, rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Overlays/Vignette.cs b/src/ImageSharp.Processing/Overlays/Vignette.cs index f728a3e1c..e42ead8d3 100644 --- a/src/ImageSharp.Processing/Overlays/Vignette.cs +++ b/src/ImageSharp.Processing/Overlays/Vignette.cs @@ -21,9 +21,9 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Vignette(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return Vignette(source, default(TColor), source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); } /// @@ -34,7 +34,7 @@ namespace ImageSharp /// The color to set as the vignette. /// The . public static Image Vignette(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); } @@ -48,9 +48,9 @@ namespace ImageSharp /// The the y-radius. /// The . public static Image Vignette(this Image source, float radiusX, float radiusY) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return Vignette(source, default(TColor), radiusX, radiusY, source.Bounds); + return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds); } /// @@ -63,9 +63,9 @@ namespace ImageSharp /// /// The . public static Image Vignette(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return Vignette(source, default(TColor), 0, 0, rectangle); + return Vignette(source, NamedColors.Black, 0, 0, rectangle); } /// @@ -81,16 +81,11 @@ namespace ImageSharp /// /// The . public static Image Vignette(this Image source, TColor color, float radiusX, float radiusY, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - VignetteProcessor processor = new VignetteProcessor { RadiusX = radiusX, RadiusY = radiusY }; - - if (!color.Equals(default(TColor))) - { - processor.VignetteColor = color; - } - - return source.Apply(rectangle, processor); + VignetteProcessor processor = new VignetteProcessor(color) { RadiusX = radiusX, RadiusY = radiusY }; + source.ApplyProcessor(processor, rectangle); + return source; } } } \ 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 2eb5225f8..2d57957d3 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -14,34 +14,27 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BinaryThresholdProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. /// /// The threshold to split the image. Must be between 0 and 1. - /// - /// is less than 0 or is greater than 1. - /// public BinaryThresholdProcessor(float threshold) { - // TODO: Check limit. + // TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties. Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - this.Value = threshold; + this.Threshold = threshold; - TColor upper = default(TColor); - upper.PackFromVector4(Color.White.ToVector4()); - this.UpperColor = upper; - - TColor lower = default(TColor); - lower.PackFromVector4(Color.Black.ToVector4()); - this.LowerColor = lower; + // Default to white/black for upper/lower. + this.UpperColor = NamedColors.White; + this.LowerColor = NamedColors.Black; } /// /// Gets the threshold value. /// - public float Value { get; } + public float Threshold { get; } /// /// Gets or sets the color to use for pixels that are above the threshold. @@ -62,7 +55,7 @@ namespace ImageSharp.Processing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float threshold = this.Value; + float threshold = this.Threshold; TColor upper = this.UpperColor; TColor lower = this.LowerColor; diff --git a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs new file mode 100644 index 000000000..ce03c58a8 --- /dev/null +++ b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processing.Processors +{ + using System; + + using ImageSharp.Dithering; + + /// + /// An that dithers an image using error diffusion. + /// + /// The pixel format. + public class ErrorDiffusionDitherProcessor : ImageProcessor + where TColor : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + public ErrorDiffusionDitherProcessor(IErrorDiffuser diffuser, float threshold) + { + Guard.NotNull(diffuser, nameof(diffuser)); + + this.Diffuser = diffuser; + this.Threshold = threshold; + + // Default to white/black for upper/lower. + this.UpperColor = NamedColors.White; + this.LowerColor = NamedColors.Black; + } + + /// + /// Gets the error diffuser. + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the threshold value. + /// + public float Threshold { get; } + + /// + /// Gets or sets the color to use for pixels that are above the threshold. + /// + public TColor UpperColor { get; set; } + + /// + /// Gets or sets the color to use for pixels that fall below the threshold. + /// + public TColor LowerColor { get; set; } + + /// + protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int y = minY; y < maxY; y++) + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TColor sourceColor = sourcePixels[offsetX, offsetY]; + TColor transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; + this.Diffuser.Dither(sourcePixels, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs new file mode 100644 index 000000000..4126419f2 --- /dev/null +++ b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -0,0 +1,114 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processing.Processors +{ + using System; + using System.Buffers; + + using ImageSharp.Dithering; + + /// + /// An that dithers an image using error diffusion. + /// + /// The pixel format. + public class OrderedDitherProcessor : ImageProcessor + where TColor : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The component index to test the threshold against. Must range from 0 to 3. + public OrderedDitherProcessor(IOrderedDither dither, int index) + { + Guard.NotNull(dither, nameof(dither)); + Guard.MustBeBetweenOrEqualTo(index, 0, 3, nameof(index)); + + // Alpha8 only stores the pixel data in the alpha channel. + if (typeof(TColor) == typeof(Alpha8)) + { + index = 3; + } + + this.Dither = dither; + this.Index = index; + + // Default to white/black for upper/lower. + this.UpperColor = NamedColors.White; + this.LowerColor = NamedColors.Black; + } + + /// + /// Gets the ditherer. + /// + public IOrderedDither Dither { get; } + + /// + /// Gets the component index to test the threshold against. + /// + public int Index { get; } + + /// + /// Gets or sets the color to use for pixels that are above the threshold. + /// + public TColor UpperColor { get; set; } + + /// + /// Gets or sets the color to use for pixels that fall below the threshold. + /// + public TColor LowerColor { get; set; } + + /// + protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int y = minY; y < maxY; y++) + { + int offsetY = y - startY; + byte[] bytes = ArrayPool.Shared.Rent(4); + + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TColor sourceColor = sourcePixels[offsetX, offsetY]; + this.Dither.Dither(sourcePixels, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); + } + + ArrayPool.Shared.Return(bytes); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs index 305375eca..0ea821bef 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BlackWhiteProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs index 3e34d0838..15e7c78da 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class AchromatomalyProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs index 53a7a3556..adca0fe98 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class AchromatopsiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs index 5d252961c..6de54beea 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class DeuteranomalyProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs index cd48df401..4729ccc61 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class DeuteranopiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs index 234c2e13b..200fff24d 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ProtanomalyProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs index a88b8812e..7c0f03543 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ProtanopiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs index 1f68bddbb..63f1fd9eb 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class TritanomalyProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs index 85332e810..2200414fe 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class TritanopiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorMatrixFilter.cs index b11b82b10..a37228a9b 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public abstract Matrix4x4 Matrix { get; } diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs index 364919e74..bd14da59e 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GrayscaleBt601Processor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs index 37d41ab74..925a36c75 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GrayscaleBt709Processor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/HueProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/HueProcessor.cs index 0de0891fe..fdf5ffdb4 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/HueProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/HueProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class HueProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/IColorMatrixFilter.cs index 4230fda12..faee890eb 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/IColorMatrixFilter.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/IColorMatrixFilter.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public interface IColorMatrixFilter : IImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the used to alter the image. diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/KodachromeProcessor.cs index 84a05e579..fee168498 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/KodachromeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/KodachromeProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class KodachromeProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs index 731e04bf7..0e614afe8 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs @@ -13,8 +13,10 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class LomographProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { + private static readonly TColor VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); + /// public override Matrix4x4 Matrix => new Matrix4x4() { @@ -30,9 +32,7 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - TColor packed = default(TColor); - packed.PackFromVector4(new Color(0, 10, 0).ToVector4()); // Very dark (mostly black) lime green. - new VignetteProcessor { VignetteColor = packed }.Apply(source, sourceRectangle); + new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs index 678edf011..666fe5bc0 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -13,8 +13,11 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class PolaroidProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { + private static TColor veryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); + private static TColor lightOrange = ColorBuilder.FromRGBA(255, 153, 102, 178); + /// public override Matrix4x4 Matrix => new Matrix4x4() { @@ -36,13 +39,8 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - TColor packedV = default(TColor); - packedV.PackFromVector4(new Color(102, 34, 0).ToVector4()); // Very dark orange [Brown tone] - new VignetteProcessor { VignetteColor = packedV }.Apply(source, sourceRectangle); - - TColor packedG = default(TColor); - packedG.PackFromVector4(new Color(255, 153, 102, 178).ToVector4()); // Light orange - new GlowProcessor { GlowColor = packedG, Radius = source.Width / 4F }.Apply(source, sourceRectangle); + new VignetteProcessor(veryDarkOrange).Apply(source, sourceRectangle); + new GlowProcessor(lightOrange) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/SaturationProcessor.cs index 430228d53..d63326385 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/SaturationProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/SaturationProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class SaturationProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/SepiaProcessor.cs index 1170fc3a9..d8fdc6cd1 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/SepiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/SepiaProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class SepiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4 diff --git a/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs index 272b3cc8b..3597ba7de 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BoxBlurProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The maximum size of the kernel in either direction. @@ -35,12 +35,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) @@ -52,46 +52,42 @@ namespace ImageSharp.Processing.Processors /// Create a 1 dimensional Box kernel. /// /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateBoxKernel(bool horizontal) + /// The + private Fast2DArray CreateBoxKernel(bool horizontal) { int size = this.kernelSize; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; - - if (horizontal) - { - kernel[0] = new float[size]; - } - - float sum = 0.0f; + Fast2DArray kernel = horizontal + ? new Fast2DArray(size, 1) + : new Fast2DArray(1, size); + float sum = 0F; for (int i = 0; i < size; i++) { float x = 1; sum += x; if (horizontal) { - kernel[0][i] = x; + kernel[0, i] = x; } else { - kernel[i] = new[] { x }; + kernel[i, 0] = x; } } - // Normalise kernel so that the sum of all weights equals 1 + // Normalize kernel so that the sum of all weights equals 1 if (horizontal) { for (int i = 0; i < size; i++) { - kernel[0][i] = kernel[0][i] / sum; + kernel[0, i] = kernel[0, i] / sum; } } else { for (int i = 0; i < size; i++) { - kernel[i][0] = kernel[i][0] / sum; + kernel[i, 0] = kernel[i, 0] / sum; } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs index d6ea42f0c..d104f5d35 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -14,14 +14,14 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class Convolution2DProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2DProcessor(float[][] kernelX, float[][] kernelY) + public Convolution2DProcessor(Fast2DArray kernelX, Fast2DArray kernelY) { this.KernelX = kernelX; this.KernelY = kernelY; @@ -30,20 +30,20 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - int kernelYHeight = this.KernelY.Length; - int kernelYWidth = this.KernelY[0].Length; - int kernelXHeight = this.KernelX.Length; - int kernelXWidth = this.KernelX[0].Length; + int kernelYHeight = this.KernelY.Height; + int kernelYWidth = this.KernelY.Width; + int kernelXHeight = this.KernelX.Height; + int kernelXWidth = this.KernelX.Width; int radiusY = kernelYHeight >> 1; int radiusX = kernelXWidth >> 1; @@ -54,73 +54,73 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - TColor[] target = new TColor[source.Width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => + using (PixelAccessor sourcePixels = source.Lock()) { - for (int x = startX; x < endX; x++) + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => { - float rX = 0; - float gX = 0; - float bX = 0; - float rY = 0; - float gY = 0; - float bY = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelYHeight; fy++) + for (int x = startX; x < endX; x++) { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelXWidth; fx++) + float rX = 0; + float gX = 0; + float bX = 0; + float rY = 0; + float gY = 0; + float bY = 0; + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelYHeight; fy++) { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); + int fyr = fy - radiusY; + int offsetY = y + fyr; - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - float r = currentColor.X; - float g = currentColor.Y; - float b = currentColor.Z; - - if (fy < kernelXHeight) - { - rX += this.KernelX[fy][fx] * r; - gX += this.KernelX[fy][fx] * g; - bX += this.KernelX[fy][fx] * b; - } + offsetY = offsetY.Clamp(0, maxY); - if (fx < kernelYWidth) + for (int fx = 0; fx < kernelXWidth; fx++) { - rY += this.KernelY[fy][fx] * r; - gY += this.KernelY[fy][fx] * g; - bY += this.KernelY[fy][fx] * b; + int fxr = fx - radiusX; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + + if (fy < kernelXHeight) + { + Vector4 kx = this.KernelX[fy, fx] * currentColor; + rX += kx.X; + gX += kx.Y; + bX += kx.Z; + } + + if (fx < kernelYWidth) + { + Vector4 ky = this.KernelY[fy, fx] * currentColor; + rY += ky.X; + gY += ky.Y; + bY += ky.Z; + } } } - } - float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); - float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); - float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); + float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); + float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); + float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; - } - }); - } + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); + targetPixels[x, y] = packed; + } + }); + } - source.SetPixels(source.Width, source.Height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs index ad7ed83ed..1d118443c 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -14,14 +14,14 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class Convolution2PassProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2PassProcessor(float[][] kernelX, float[][] kernelY) + public Convolution2PassProcessor(Fast2DArray kernelX, Fast2DArray kernelY) { this.KernelX = kernelX; this.KernelY = kernelY; @@ -30,46 +30,46 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float[][] kernelX = this.KernelX; - float[][] kernelY = this.KernelY; int width = source.Width; int height = source.Height; - TColor[] target = new TColor[width * height]; - TColor[] firstPass = new TColor[width * height]; - - this.ApplyConvolution(width, height, firstPass, source.Pixels, sourceRectangle, kernelX); - this.ApplyConvolution(width, height, target, firstPass, sourceRectangle, kernelY); + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + { + using (PixelAccessor firstPassPixels = new PixelAccessor(width, height)) + using (PixelAccessor sourcePixels = source.Lock()) + { + this.ApplyConvolution(firstPassPixels, sourcePixels, sourceRectangle, this.KernelX); + this.ApplyConvolution(targetPixels, firstPassPixels, sourceRectangle, this.KernelY); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// /// Applies the process to the specified portion of the specified at the specified location /// and with the specified size. /// - /// The image width. - /// The image height. - /// The target pixels to apply the process to. - /// The source pixels. Cannot be null. + /// The target pixels to apply the process to. + /// The source pixels. Cannot be null. /// /// The structure that specifies the portion of the image object to draw. /// /// The kernel operator. - private void ApplyConvolution(int width, int height, TColor[] target, TColor[] source, Rectangle sourceRectangle, float[][] kernel) + private void ApplyConvolution(PixelAccessor targetPixels, PixelAccessor sourcePixels, Rectangle sourceRectangle, Fast2DArray kernel) { - int kernelHeight = kernel.Length; - int kernelWidth = kernel[0].Length; + int kernelHeight = kernel.Height; + int kernelWidth = kernel.Width; int radiusY = kernelHeight >> 1; int radiusX = kernelWidth >> 1; @@ -80,10 +80,7 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor sourcePixels = source.Lock(width, height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) - { - Parallel.For( + Parallel.For( startY, endY, this.ParallelOptions, @@ -109,7 +106,7 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - destination += kernel[fy][fx] * currentColor; + destination += kernel[fy, fx] * currentColor; } } @@ -118,7 +115,6 @@ namespace ImageSharp.Processing.Processors targetPixels[x, y] = packed; } }); - } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs index 17d7e2918..6b5b6d3fe 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -14,13 +14,13 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ConvolutionProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. /// /// The 2d gradient operator. - public ConvolutionProcessor(float[][] kernelXY) + public ConvolutionProcessor(Fast2DArray kernelXY) { this.KernelXY = kernelXY; } @@ -28,13 +28,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets the 2d gradient operator. /// - public virtual float[][] KernelXY { get; } + public Fast2DArray KernelXY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float[][] kernelX = this.KernelXY; - int kernelLength = kernelX.GetLength(0); + int kernelLength = this.KernelXY.Height; int radius = kernelLength >> 1; int startY = sourceRectangle.Y; @@ -44,60 +43,55 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - TColor[] target = new TColor[source.Width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => + using (PixelAccessor sourcePixels = source.Lock()) { - for (int x = startX; x < endX; x++) + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => { - float rX = 0; - float gX = 0; - float bX = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelLength; fy++) + for (int x = startX; x < endX; x++) { - int fyr = fy - radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); + float red = 0; + float green = 0; + float blue = 0; - for (int fx = 0; fx < kernelLength; fx++) + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelLength; fy++) { - int fxr = fx - radius; - int offsetX = x + fxr; + int fyr = fy - radius; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelLength; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; - offsetX = offsetX.Clamp(0, maxX); + offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - float r = currentColor.X; - float g = currentColor.Y; - float b = currentColor.Z; + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + currentColor *= this.KernelXY[fy, fx]; - rX += kernelX[fy][fx] * r; - gX += kernelX[fy][fx] * g; - bX += kernelX[fy][fx] * b; + red += currentColor.X; + green += currentColor.Y; + blue += currentColor.Z; + } } - } - float red = rX; - float green = gX; - float blue = bX; + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); + targetPixels[x, y] = packed; + } + }); + } - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; - } - }); + source.SwapPixelsBuffers(targetPixels); } - - source.SetPixels(source.Width, source.Height, target); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs index 6ee5d0f96..a8c786f71 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs @@ -12,17 +12,28 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class EdgeDetector2DProcessor : ImageProcessor, IEdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + protected EdgeDetector2DProcessor(Fast2DArray kernelX, Fast2DArray kernelY) + { + this.KernelX = kernelX; + this.KernelY = kernelY; + } + /// /// Gets the horizontal gradient operator. /// - public abstract float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public abstract float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// public bool Grayscale { get; set; } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs index 5a1487761..eb8491d4c 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs @@ -14,55 +14,64 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class EdgeDetectorCompassProcessor : ImageProcessor, IEdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the North gradient operator /// - public abstract float[][] North { get; } + public abstract Fast2DArray North { get; } /// /// Gets the NorthWest gradient operator /// - public abstract float[][] NorthWest { get; } + public abstract Fast2DArray NorthWest { get; } /// /// Gets the West gradient operator /// - public abstract float[][] West { get; } + public abstract Fast2DArray West { get; } /// /// Gets the SouthWest gradient operator /// - public abstract float[][] SouthWest { get; } + public abstract Fast2DArray SouthWest { get; } /// /// Gets the South gradient operator /// - public abstract float[][] South { get; } + public abstract Fast2DArray South { get; } /// /// Gets the SouthEast gradient operator /// - public abstract float[][] SouthEast { get; } + public abstract Fast2DArray SouthEast { get; } /// /// Gets the East gradient operator /// - public abstract float[][] East { get; } + public abstract Fast2DArray East { get; } /// /// Gets the NorthEast gradient operator /// - public abstract float[][] NorthEast { get; } + public abstract Fast2DArray NorthEast { get; } /// public bool Grayscale { get; set; } + /// + protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) + { + if (this.Grayscale) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + } + /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float[][][] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; + Fast2DArray[] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; @@ -75,72 +84,61 @@ namespace ImageSharp.Processing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); - // First run. - ImageBase target = new Image(source.Width, source.Height); - target.ClonePixels(source.Width, source.Height, source.Pixels); - new ConvolutionProcessor(kernels[0]).Apply(target, sourceRectangle); - - if (kernels.Length == 1) - { - return; - } - - int shiftY = startY; - int shiftX = startX; - - // Reset offset if necessary. - if (minX > 0) + // we need a clean copy for each pass to start from + using (ImageBase cleanCopy = new Image(source)) { - shiftX = 0; - } + new ConvolutionProcessor(kernels[0]).Apply(source, sourceRectangle); - if (minY > 0) - { - shiftY = 0; - } + if (kernels.Length == 1) + { + return; + } - // Additional runs. - // ReSharper disable once ForCanBeConvertedToForeach - for (int i = 1; i < kernels.Length; i++) - { - // Create a clone for each pass and copy the offset pixels across. - ImageBase pass = new Image(source.Width, source.Height); - pass.ClonePixels(source.Width, source.Height, source.Pixels); + int shiftY = startY; + int shiftX = startX; - new ConvolutionProcessor(kernels[i]).Apply(pass, sourceRectangle); + // Reset offset if necessary. + if (minX > 0) + { + shiftX = 0; + } - using (PixelAccessor passPixels = pass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + if (minY > 0) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - shiftY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - shiftX; - - // Grab the max components of the two pixels - TColor packed = default(TColor); - packed.PackFromVector4(Vector4.Max(passPixels[offsetX, offsetY].ToVector4(), targetPixels[offsetX, offsetY].ToVector4())); - targetPixels[offsetX, offsetY] = packed; - } - }); + shiftY = 0; } - } - source.SetPixels(source.Width, source.Height, target.Pixels); - } + // Additional runs. + // ReSharper disable once ForCanBeConvertedToForeach + for (int i = 1; i < kernels.Length; i++) + { + using (ImageBase pass = new Image(cleanCopy)) + { + new ConvolutionProcessor(kernels[i]).Apply(pass, sourceRectangle); - /// - protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) - { - if (this.Grayscale) - { - new GrayscaleBt709Processor().Apply(source, sourceRectangle); + using (PixelAccessor passPixels = pass.Lock()) + using (PixelAccessor targetPixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - shiftY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - shiftX; + + // Grab the max components of the two pixels + TColor packed = default(TColor); + packed.PackFromVector4(Vector4.Max(passPixels[offsetX, offsetY].ToVector4(), targetPixels[offsetX, offsetY].ToVector4())); + targetPixels[offsetX, offsetY] = packed; + } + }); + } + } + } } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs index 1033111fc..a963bb578 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs @@ -12,21 +12,24 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class EdgeDetectorProcessor : ImageProcessor, IEdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + /// The 2d gradient operator. + protected EdgeDetectorProcessor(Fast2DArray kernelXY) + { + this.KernelXY = kernelXY; + } + /// public bool Grayscale { get; set; } /// /// Gets the 2d gradient operator. /// - public abstract float[][] KernelXY { get; } - - /// - protected override void OnApply(ImageBase source, Rectangle sourceRectangle) - { - new ConvolutionProcessor(this.KernelXY).Apply(source, sourceRectangle); - } + public Fast2DArray KernelXY { get; } /// protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) @@ -36,5 +39,11 @@ namespace ImageSharp.Processing.Processors new GrayscaleBt709Processor().Apply(source, sourceRectangle); } } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + new ConvolutionProcessor(this.KernelXY).Apply(source, sourceRectangle); + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/IEdgeDetectorProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/IEdgeDetectorProcessor.cs index 68dc7ccdb..7c0923bbb 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/IEdgeDetectorProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/IEdgeDetectorProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public interface IEdgeDetectorProcessor : IImageProcessor, IEdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs index f628ea1b9..6456cc073 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs @@ -15,32 +15,36 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class KayyaliProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. /// - private static readonly float[][] KayyaliX = - { - new float[] { 6, 0, -6 }, - new float[] { 0, 0, 0 }, - new float[] { -6, 0, 6 } - }; + private static readonly Fast2DArray KayyaliX = + new float[,] + { + { 6, 0, -6 }, + { 0, 0, 0 }, + { -6, 0, 6 } + }; /// /// The vertical gradient operator. /// - private static readonly float[][] KayyaliY = - { - new float[] { -6, 0, 6 }, - new float[] { 0, 0, 0 }, - new float[] { 6, 0, -6 } - }; - - /// - public override float[][] KernelX => KayyaliX; + private static readonly Fast2DArray KayyaliY = + new float[,] + { + { -6, 0, 6 }, + { 0, 0, 0 }, + { 6, 0, -6 } + }; - /// - public override float[][] KernelY => KayyaliY; + /// + /// Initializes a new instance of the class. + /// + public KayyaliProcessor() + : base(KayyaliX, KayyaliY) + { + } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs index 3f7e0a00e..90b3fc4b4 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Processing.Processors { using System; @@ -14,110 +15,118 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class KirschProcessor : EdgeDetectorCompassProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The North gradient operator /// - private static readonly float[][] KirschNorth = - { - new float[] { 5, 5, 5 }, - new float[] { -3, 0, -3 }, - new float[] { -3, -3, -3 } - }; + private static readonly Fast2DArray KirschNorth = + new float[,] + { + { 5, 5, 5 }, + { -3, 0, -3 }, + { -3, -3, -3 } + }; /// /// The NorthWest gradient operator /// - private static readonly float[][] KirschNorthWest = - { - new float[] { 5, 5, -3 }, - new float[] { 5, 0, -3 }, - new float[] { -3, -3, -3 } - }; + private static readonly Fast2DArray KirschNorthWest = + new float[,] + { + { 5, 5, -3 }, + { 5, 0, -3 }, + { -3, -3, -3 } + }; /// /// The West gradient operator /// - private static readonly float[][] KirschWest = - { - new float[] { 5, -3, -3 }, - new float[] { 5, 0, -3 }, - new float[] { 5, -3, -3 } - }; + private static readonly Fast2DArray KirschWest = + new float[,] + { + { 5, -3, -3 }, + { 5, 0, -3 }, + { 5, -3, -3 } + }; /// /// The SouthWest gradient operator /// - private static readonly float[][] KirschSouthWest = - { - new float[] { -3, -3, -3 }, - new float[] { 5, 0, -3 }, - new float[] { 5, 5, -3 } - }; + private static readonly Fast2DArray KirschSouthWest = + new float[,] + { + { -3, -3, -3 }, + { 5, 0, -3 }, + { 5, 5, -3 } + }; /// /// The South gradient operator /// - private static readonly float[][] KirschSouth = - { - new float[] { -3, -3, -3 }, - new float[] { -3, 0, -3 }, - new float[] { 5, 5, 5 } - }; + private static readonly Fast2DArray KirschSouth = + new float[,] + { + { -3, -3, -3 }, + { -3, 0, -3 }, + { 5, 5, 5 } + }; /// /// The SouthEast gradient operator /// - private static readonly float[][] KirschSouthEast = - { - new float[] { -3, -3, -3 }, - new float[] { -3, 0, 5 }, - new float[] { -3, 5, 5 } - }; + private static readonly Fast2DArray KirschSouthEast = + new float[,] + { + { -3, -3, -3 }, + { -3, 0, 5 }, + { -3, 5, 5 } + }; /// /// The East gradient operator /// - private static readonly float[][] KirschEast = - { - new float[] { -3, -3, 5 }, - new float[] { -3, 0, 5 }, - new float[] { -3, -3, 5 } - }; + private static readonly Fast2DArray KirschEast = + new float[,] + { + { -3, -3, 5 }, + { -3, 0, 5 }, + { -3, -3, 5 } + }; /// /// The NorthEast gradient operator /// - private static readonly float[][] KirschNorthEast = - { - new float[] { -3, 5, 5 }, - new float[] { -3, 0, 5 }, - new float[] { -3, -3, -3 } - }; + private static readonly Fast2DArray KirschNorthEast = + new float[,] + { + { -3, 5, 5 }, + { -3, 0, 5 }, + { -3, -3, -3 } + }; /// - public override float[][] North => KirschNorth; + public override Fast2DArray North => KirschNorth; /// - public override float[][] NorthWest => KirschNorthWest; + public override Fast2DArray NorthWest => KirschNorthWest; /// - public override float[][] West => KirschWest; + public override Fast2DArray West => KirschWest; /// - public override float[][] SouthWest => KirschSouthWest; + public override Fast2DArray SouthWest => KirschSouthWest; /// - public override float[][] South => KirschSouth; + public override Fast2DArray South => KirschSouth; /// - public override float[][] SouthEast => KirschSouthEast; + public override Fast2DArray SouthEast => KirschSouthEast; /// - public override float[][] East => KirschEast; + public override Fast2DArray East => KirschEast; /// - public override float[][] NorthEast => KirschNorthEast; + public override Fast2DArray NorthEast => KirschNorthEast; } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs index b19c5c773..c8823efee 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs @@ -15,19 +15,25 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class Laplacian3X3Processor : EdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The 2d gradient operator. /// - private static readonly float[][] Laplacian3X3XY = new float[][] - { - new float[] { -1, -1, -1 }, - new float[] { -1, 8, -1 }, - new float[] { -1, -1, -1 } - }; + private static readonly Fast2DArray Laplacian3X3XY = + new float[,] + { + { -1, -1, -1 }, + { -1, 8, -1 }, + { -1, -1, -1 } + }; - /// - public override float[][] KernelXY => Laplacian3X3XY; + /// + /// Initializes a new instance of the class. + /// + public Laplacian3X3Processor() + : base(Laplacian3X3XY) + { + } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs index efa6c28c5..3aad6d1ef 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs @@ -15,21 +15,27 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class Laplacian5X5Processor : EdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The 2d gradient operator. /// - private static readonly float[][] Laplacian5X5XY = - { - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, 24, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 } - }; + private static readonly Fast2DArray Laplacian5X5XY = + new float[,] + { + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, 24, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } + }; - /// - public override float[][] KernelXY => Laplacian5X5XY; + /// + /// Initializes a new instance of the class. + /// + public Laplacian5X5Processor() + : base(Laplacian5X5XY) + { + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs index 595ca6a4b..a7da76e13 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs @@ -15,21 +15,27 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class LaplacianOfGaussianProcessor : EdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The 2d gradient operator. /// - private static readonly float[][] LaplacianOfGaussianXY = - { - new float[] { 0, 0, -1, 0, 0 }, - new float[] { 0, -1, -2, -1, 0 }, - new float[] { -1, -2, 16, -2, -1 }, - new float[] { 0, -1, -2, -1, 0 }, - new float[] { 0, 0, -1, 0, 0 } - }; + private static readonly Fast2DArray LaplacianOfGaussianXY = + new float[,] + { + { 0, 0, -1, 0, 0 }, + { 0, -1, -2, -1, 0 }, + { -1, -2, 16, -2, -1 }, + { 0, -1, -2, -1, 0 }, + { 0, 0, -1, 0, 0 } + }; - /// - public override float[][] KernelXY => LaplacianOfGaussianXY; + /// + /// Initializes a new instance of the class. + /// + public LaplacianOfGaussianProcessor() + : base(LaplacianOfGaussianXY) + { + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs index 5c48722ef..fea984418 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs @@ -15,32 +15,36 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class PrewittProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. /// - private static readonly float[][] PrewittX = - { - new float[] { -1, 0, 1 }, - new float[] { -1, 0, 1 }, - new float[] { -1, 0, 1 } - }; + private static readonly Fast2DArray PrewittX = + new float[,] + { + { -1, 0, 1 }, + { -1, 0, 1 }, + { -1, 0, 1 } + }; /// /// The vertical gradient operator. /// - private static readonly float[][] PrewittY = - { - new float[] { 1, 1, 1 }, - new float[] { 0, 0, 0 }, - new float[] { -1, -1, -1 } - }; - - /// - public override float[][] KernelX => PrewittX; + private static readonly Fast2DArray PrewittY = + new float[,] + { + { 1, 1, 1 }, + { 0, 0, 0 }, + { -1, -1, -1 } + }; - /// - public override float[][] KernelY => PrewittY; + /// + /// Initializes a new instance of the class. + /// + public PrewittProcessor() + : base(PrewittX, PrewittY) + { + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs index c64ee8abe..329a995c0 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs @@ -15,30 +15,34 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class RobertsCrossProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. /// - private static readonly float[][] RobertsCrossX = - { - new float[] { 1, 0 }, - new float[] { 0, -1 } - }; + private static readonly Fast2DArray RobertsCrossX = + new float[,] + { + { 1, 0 }, + { 0, -1 } + }; /// /// The vertical gradient operator. /// - private static readonly float[][] RobertsCrossY = - { - new float[] { 0, 1 }, - new float[] { -1, 0 } - }; - - /// - public override float[][] KernelX => RobertsCrossX; + private static readonly Fast2DArray RobertsCrossY = + new float[,] + { + { 0, 1 }, + { -1, 0 } + }; - /// - public override float[][] KernelY => RobertsCrossY; + /// + /// Initializes a new instance of the class. + /// + public RobertsCrossProcessor() + : base(RobertsCrossX, RobertsCrossY) + { + } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs index 4e61707c4..60726deab 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Processing.Processors { using System; @@ -14,110 +15,118 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class RobinsonProcessor : EdgeDetectorCompassProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The North gradient operator /// - private static readonly float[][] RobinsonNorth = - { - new float[] { 1, 2, 1 }, - new float[] { 0, 0, 0 }, - new float[] { -1, -2, -1 } - }; + private static readonly Fast2DArray RobinsonNorth = + new float[,] + { + { 1, 2, 1 }, + { 0, 0, 0 }, + { -1, -2, -1 } + }; /// /// The NorthWest gradient operator /// - private static readonly float[][] RobinsonNorthWest = - { - new float[] { 2, 1, 0 }, - new float[] { 1, 0, -1 }, - new float[] { 0, -1, -2 } - }; + private static readonly Fast2DArray RobinsonNorthWest = + new float[,] + { + { 2, 1, 0 }, + { 1, 0, -1 }, + { 0, -1, -2 } + }; /// /// The West gradient operator /// - private static readonly float[][] RobinsonWest = - { - new float[] { 1, 0, -1 }, - new float[] { 2, 0, -2 }, - new float[] { 1, 0, -1 } - }; + private static readonly Fast2DArray RobinsonWest = + new float[,] + { + { 1, 0, -1 }, + { 2, 0, -2 }, + { 1, 0, -1 } + }; /// /// The SouthWest gradient operator /// - private static readonly float[][] RobinsonSouthWest = - { - new float[] { 0, -1, -2 }, - new float[] { 1, 0, -1 }, - new float[] { 2, 1, 0 } - }; + private static readonly Fast2DArray RobinsonSouthWest = + new float[,] + { + { 0, -1, -2 }, + { 1, 0, -1 }, + { 2, 1, 0 } + }; /// /// The South gradient operator /// - private static readonly float[][] RobinsonSouth = - { - new float[] { -1, -2, -1 }, - new float[] { 0, 0, 0 }, - new float[] { 1, 2, 1 } - }; + private static readonly Fast2DArray RobinsonSouth = + new float[,] + { + { -1, -2, -1 }, + { 0, 0, 0 }, + { 1, 2, 1 } + }; /// /// The SouthEast gradient operator /// - private static readonly float[][] RobinsonSouthEast = - { - new float[] { -2, -1, 0 }, - new float[] { -1, 0, 1 }, - new float[] { 0, 1, 2 } - }; + private static readonly Fast2DArray RobinsonSouthEast = + new float[,] + { + { -2, -1, 0 }, + { -1, 0, 1 }, + { 0, 1, 2 } + }; /// /// The East gradient operator /// - private static readonly float[][] RobinsonEast = - { - new float[] { -1, 0, 1 }, - new float[] { -2, 0, 2 }, - new float[] { -1, 0, 1 } - }; + private static readonly Fast2DArray RobinsonEast = + new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }; /// /// The NorthEast gradient operator /// - private static readonly float[][] RobinsonNorthEast = - { - new float[] { 0, 1, 2 }, - new float[] { -1, 0, 1 }, - new float[] { -2, -1, 0 } - }; + private static readonly Fast2DArray RobinsonNorthEast = + new float[,] + { + { 0, 1, 2 }, + { -1, 0, 1 }, + { -2, -1, 0 } + }; /// - public override float[][] North => RobinsonNorth; + public override Fast2DArray North => RobinsonNorth; /// - public override float[][] NorthWest => RobinsonNorthWest; + public override Fast2DArray NorthWest => RobinsonNorthWest; /// - public override float[][] West => RobinsonWest; + public override Fast2DArray West => RobinsonWest; /// - public override float[][] SouthWest => RobinsonSouthWest; + public override Fast2DArray SouthWest => RobinsonSouthWest; /// - public override float[][] South => RobinsonSouth; + public override Fast2DArray South => RobinsonSouth; /// - public override float[][] SouthEast => RobinsonSouthEast; + public override Fast2DArray SouthEast => RobinsonSouthEast; /// - public override float[][] East => RobinsonEast; + public override Fast2DArray East => RobinsonEast; /// - public override float[][] NorthEast => RobinsonNorthEast; + public override Fast2DArray NorthEast => RobinsonNorthEast; } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs index de2a185f8..ed45ba0ac 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs @@ -15,32 +15,36 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class ScharrProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. /// - private static readonly float[][] ScharrX = new float[3][] - { - new float[] { -3, 0, 3 }, - new float[] { -10, 0, 10 }, - new float[] { -3, 0, 3 } - }; + private static readonly Fast2DArray ScharrX = + new float[,] + { + { -3, 0, 3 }, + { -10, 0, 10 }, + { -3, 0, 3 } + }; /// /// The vertical gradient operator. /// - private static readonly float[][] ScharrY = new float[3][] - { - new float[] { 3, 10, 3 }, - new float[] { 0, 0, 0 }, - new float[] { -3, -10, -3 } - }; - - /// - public override float[][] KernelX => ScharrX; + private static readonly Fast2DArray ScharrY = + new float[,] + { + { 3, 10, 3 }, + { 0, 0, 0 }, + { -3, -10, -3 } + }; - /// - public override float[][] KernelY => ScharrY; + /// + /// Initializes a new instance of the class. + /// + public ScharrProcessor() + : base(ScharrX, ScharrY) + { + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs index 328c903dc..3d2c583a7 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -15,32 +15,36 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class SobelProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. /// - private static readonly float[][] SobelX = - { - new float[] { -1, 0, 1 }, - new float[] { -2, 0, 2 }, - new float[] { -1, 0, 1 } - }; + private static readonly Fast2DArray SobelX = + new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }; /// /// The vertical gradient operator. /// - private static readonly float[][] SobelY = - { - new float[] { -1, -2, -1 }, - new float[] { 0, 0, 0 }, - new float[] { 1, 2, 1 } - }; - - /// - public override float[][] KernelX => SobelX; + private static readonly Fast2DArray SobelY = + new float[,] + { + { -1, -2, -1 }, + { 0, 0, 0 }, + { 1, 2, 1 } + }; - /// - public override float[][] KernelY => SobelY; + /// + /// Initializes a new instance of the class. + /// + public SobelProcessor() + : base(SobelX, SobelY) + { + } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs index 7cd3bbe9c..ddaffc9b4 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GaussianBlurProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The maximum size of the kernel in either direction. @@ -71,12 +71,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) @@ -88,21 +88,18 @@ namespace ImageSharp.Processing.Processors /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateGaussianKernel(bool horizontal) + /// The + private Fast2DArray CreateGaussianKernel(bool horizontal) { int size = this.kernelSize; float weight = this.sigma; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; + Fast2DArray kernel = horizontal + ? new Fast2DArray(size, 1) + : new Fast2DArray(1, size); - if (horizontal) - { - kernel[0] = new float[size]; - } - - float sum = 0.0f; + float sum = 0F; + float midpoint = (size - 1) / 2F; - float midpoint = (size - 1) / 2f; for (int i = 0; i < size; i++) { float x = i - midpoint; @@ -110,27 +107,27 @@ namespace ImageSharp.Processing.Processors sum += gx; if (horizontal) { - kernel[0][i] = gx; + kernel[0, i] = gx; } else { - kernel[i] = new[] { gx }; + kernel[i, 0] = gx; } } - // Normalise kernel so that the sum of all weights equals 1 + // Normalize kernel so that the sum of all weights equals 1 if (horizontal) { for (int i = 0; i < size; i++) { - kernel[0][i] = kernel[0][i] / sum; + kernel[0, i] = kernel[0, i] / sum; } } else { for (int i = 0; i < size; i++) { - kernel[i][0] = kernel[i][0] / sum; + kernel[i, 0] = kernel[i, 0] / sum; } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs index d0654dd77..6541b7380 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GaussianSharpenProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The maximum size of the kernel in either direction. @@ -73,12 +73,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) @@ -90,17 +90,14 @@ namespace ImageSharp.Processing.Processors /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateGaussianKernel(bool horizontal) + /// The + private Fast2DArray CreateGaussianKernel(bool horizontal) { int size = this.kernelSize; float weight = this.sigma; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; - - if (horizontal) - { - kernel[0] = new float[size]; - } + Fast2DArray kernel = horizontal + ? new Fast2DArray(size, 1) + : new Fast2DArray(1, size); float sum = 0; @@ -112,11 +109,11 @@ namespace ImageSharp.Processing.Processors sum += gx; if (horizontal) { - kernel[0][i] = gx; + kernel[0, i] = gx; } else { - kernel[i] = new[] { gx }; + kernel[i, 0] = gx; } } @@ -130,12 +127,12 @@ namespace ImageSharp.Processing.Processors if (i == midpointRounded) { // Calculate central value - kernel[0][i] = (2f * sum) - kernel[0][i]; + kernel[0, i] = (2F * sum) - kernel[0, i]; } else { // invert value - kernel[0][i] = -kernel[0][i]; + kernel[0, i] = -kernel[0, i]; } } } @@ -146,12 +143,12 @@ namespace ImageSharp.Processing.Processors if (i == midpointRounded) { // Calculate central value - kernel[i][0] = (2 * sum) - kernel[i][0]; + kernel[i, 0] = (2 * sum) - kernel[i, 0]; } else { // invert value - kernel[i][0] = -kernel[i][0]; + kernel[i, 0] = -kernel[i, 0]; } } } @@ -161,14 +158,14 @@ namespace ImageSharp.Processing.Processors { for (int i = 0; i < size; i++) { - kernel[0][i] = kernel[0][i] / sum; + kernel[0, i] = kernel[0, i] / sum; } } else { for (int i = 0; i < size; i++) { - kernel[i][0] = kernel[i][0] / sum; + kernel[i, 0] = kernel[i, 0] / sum; } } diff --git a/src/ImageSharp.Processing/Processors/Effects/AlphaProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/AlphaProcessor.cs index ecf47a036..11af92ea7 100644 --- a/src/ImageSharp.Processing/Processors/Effects/AlphaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/AlphaProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class AlphaProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/BackgroundColorProcessor.cs index 356b2e925..d6d209dc7 100644 --- a/src/ImageSharp.Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BackgroundColorProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/BrightnessProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/BrightnessProcessor.cs index eb88b9c41..566f2c6d7 100644 --- a/src/ImageSharp.Processing/Processors/Effects/BrightnessProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/BrightnessProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BrightnessProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/ContrastProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/ContrastProcessor.cs index 0cc56cc8e..f4acc42bf 100644 --- a/src/ImageSharp.Processing/Processors/Effects/ContrastProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/ContrastProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ContrastProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/InvertProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/InvertProcessor.cs index ec1ea7786..641cd1b47 100644 --- a/src/ImageSharp.Processing/Processors/Effects/InvertProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/InvertProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class InvertProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) diff --git a/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs index 9e12a2a91..6b9558ad2 100644 --- a/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// Adapted from by Dewald Esterhuizen. /// The pixel format. public class OilPaintingProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -67,91 +67,92 @@ namespace ImageSharp.Processing.Processors startX = 0; } - TColor[] target = new TColor[source.Width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = startX; x < endX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - 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++) + for (int x = startX; x < endX; x++) { - int fyr = fy - radius; - int offsetY = y + fyr; - - // Skip the current row - if (offsetY < minY) - { - continue; - } + int maxIntensity = 0; + int maxIndex = 0; - // Outwith the current bounds so break. - if (offsetY >= maxY) - { - break; - } + int[] intensityBin = new int[levels]; + float[] redBin = new float[levels]; + float[] blueBin = new float[levels]; + float[] greenBin = new float[levels]; - for (int fx = 0; fx <= radius; fx++) + for (int fy = 0; fy <= radius; fy++) { - int fxr = fx - radius; - int offsetX = x + fxr; + int fyr = fy - radius; + int offsetY = y + fyr; - // Skip the column - if (offsetX < 0) + // Skip the current row + if (offsetY < minY) { continue; } - if (offsetX < maxX) + // Outwith the current bounds so break. + if (offsetY >= maxY) { - // ReSharper disable once AccessToDisposedClosure - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - - float sourceRed = color.X; - float sourceBlue = color.Z; - float sourceGreen = color.Y; + break; + } - int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); + for (int fx = 0; fx <= radius; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; - intensityBin[currentIntensity] += 1; - blueBin[currentIntensity] += sourceBlue; - greenBin[currentIntensity] += sourceGreen; - redBin[currentIntensity] += sourceRed; + // Skip the column + if (offsetX < 0) + { + continue; + } - if (intensityBin[currentIntensity] > maxIntensity) + if (offsetX < maxX) { - maxIntensity = intensityBin[currentIntensity]; - maxIndex = currentIntensity; + // ReSharper disable once AccessToDisposedClosure + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + + float sourceRed = color.X; + float sourceBlue = color.Z; + float sourceGreen = color.Y; + + int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); + + intensityBin[currentIntensity] += 1; + blueBin[currentIntensity] += sourceBlue; + greenBin[currentIntensity] += sourceGreen; + redBin[currentIntensity] += sourceRed; + + if (intensityBin[currentIntensity] > maxIntensity) + { + maxIntensity = intensityBin[currentIntensity]; + maxIndex = currentIntensity; + } } } - } - float red = Math.Abs(redBin[maxIndex] / maxIntensity); - float green = Math.Abs(greenBin[maxIndex] / maxIntensity); - float blue = Math.Abs(blueBin[maxIndex] / maxIntensity); + float red = Math.Abs(redBin[maxIndex] / maxIntensity); + float green = Math.Abs(greenBin[maxIndex] / maxIntensity); + float blue = Math.Abs(blueBin[maxIndex] / maxIntensity); - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); + targetPixels[x, y] = packed; + } } - } - }); - } + }); + } - source.SetPixels(source.Width, source.Height, target); + source.SwapPixelsBuffers(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 9c9cf92fe..d9d78dfa8 100644 --- a/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class PixelateProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -63,51 +63,52 @@ namespace ImageSharp.Processing.Processors // Get the range on the y-plane to choose from. IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); - TColor[] target = new TColor[source.Width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { - Parallel.ForEach( - range, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - int offsetPy = offset; - - for (int x = minX; x < maxX; x += size) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.ForEach( + range, + this.ParallelOptions, + y => { - int offsetX = x - startX; - int offsetPx = offset; + int offsetY = y - startY; + int offsetPy = offset; - // Make sure that the offset is within the boundary of the image. - while (offsetY + offsetPy >= maxY) + for (int x = minX; x < maxX; x += size) { - offsetPy--; - } + int offsetX = x - startX; + int offsetPx = offset; - while (x + offsetPx >= maxX) - { - offsetPx--; - } + // Make sure that the offset is within the boundary of the image. + while (offsetY + offsetPy >= maxY) + { + offsetPy--; + } - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - TColor pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; + while (x + offsetPx >= maxX) + { + 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++) + // Get the pixel color in the centre of the soon to be pixelated area. + // ReSharper disable AccessToDisposedClosure + TColor pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; + + // For each pixel in the pixelate size, set it to the centre color. + for (int l = offsetY; l < offsetY + size && l < maxY; l++) { - targetPixels[k, l] = pixel; + for (int k = offsetX; k < offsetX + size && k < maxX; k++) + { + targetPixels[k, l] = pixel; + } } } - } - }); + }); - source.SetPixels(source.Width, source.Height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } diff --git a/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs index f0e32f1fa..9043b66cb 100644 --- a/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs @@ -14,15 +14,14 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GlowProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public GlowProcessor() + /// The color or the glow. + public GlowProcessor(TColor color) { - TColor color = default(TColor); - color.PackFromVector4(Color.Black.ToVector4()); this.GlowColor = color; } diff --git a/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs index 8449f1833..f1872d0b8 100644 --- a/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs @@ -14,15 +14,14 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class VignetteProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public VignetteProcessor() + /// The color of the vignette. + public VignetteProcessor(TColor color) { - TColor color = default(TColor); - color.PackFromVector4(Color.Black.ToVector4()); this.VignetteColor = color; } diff --git a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs index a392de051..2190254f0 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class CompandingResizeProcessor : ResamplingWeightedProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -56,6 +56,8 @@ namespace ImageSharp.Processing.Processors 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; @@ -66,103 +68,104 @@ namespace ImageSharp.Processing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); - TColor[] target = new TColor[width * height]; - if (this.Sampler is NearestNeighborResampler) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - - for (int x = minX; x < maxX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; - } - }); + // Y coordinates of source points + int originY = (int)(((y - startY) * heightFactor) + sourceY); + + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; + } + }); + } + + // Break out now. + source.SwapPixelsBuffers(targetPixels); + return; } - - // Break out now. - source.SetPixels(width, height, target); - return; } // 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. - TColor[] firstPass = new TColor[width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock(width, source.Height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - sourceRectangle.Height, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor firstPassPixels = new PixelAccessor(width, source.Height)) + { + Parallel.For( + 0, + sourceRectangle.Bottom, + this.ParallelOptions, + y => { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; + for (int x = minX; x < maxX; x++) + { + // Ensure offsets are normalised for cropping and padding. + Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - // Destination color components - Vector4 destination = Vector4.Zero; + // Destination color components + Vector4 destination = Vector4.Zero; - for (int i = 0; i < horizontalValues.Length; i++) - { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index, y].ToVector4().Expand() * xw.Value; - } + for (int i = 0; i < horizontalValues.Length; i++) + { + Weight xw = horizontalValues[i]; + destination += sourcePixels[xw.Index + sourceX, y].ToVector4().Expand() * xw.Value; + } - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - firstPassPixels[x, y] = d; - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + TColor d = default(TColor); + d.PackFromVector4(destination.Compress()); + firstPassPixels[x, y] = d; + } + }); - for (int x = 0; x < width; x++) + // Now process the rows. + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - // Destination color components - Vector4 destination = Vector4.Zero; + // Ensure offsets are normalised for cropping and padding. + Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - for (int i = 0; i < verticalValues.Length; i++) + for (int x = 0; x < width; x++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index].ToVector4().Expand() * yw.Value; + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < verticalValues.Length; i++) + { + Weight yw = verticalValues[i]; + destination += firstPassPixels[x, yw.Index + sourceY].ToVector4().Expand() * yw.Value; + } + + TColor d = default(TColor); + d.PackFromVector4(destination.Compress()); + targetPixels[x, y] = d; } + }); + } - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - targetPixels[x, y] = d; - } - }); + source.SwapPixelsBuffers(targetPixels); } - - source.SetPixels(width, height, target); } } } \ 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 27b5bef0c..88f23a7fd 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class CropProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -42,25 +42,25 @@ namespace ImageSharp.Processing.Processors int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); - TColor[] target = new TColor[this.CropRectangle.Width * this.CropRectangle.Height]; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(this.CropRectangle.Width, this.CropRectangle.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - targetPixels[x - minX, y - minY] = sourcePixels[x, y]; - } - }); - } + for (int x = minX; x < maxX; x++) + { + targetPixels[x - minX, y - minY] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(this.CropRectangle.Width, this.CropRectangle.Height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ 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 e0c6e9b92..d150c59d4 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class EntropyCropProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -36,24 +36,24 @@ namespace ImageSharp.Processing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - ImageBase temp = new Image(source.Width, source.Height); - temp.ClonePixels(source.Width, source.Height, source.Pixels); + using (ImageBase temp = new Image(source)) + { + // Detect the edges. + new SobelProcessor().Apply(temp, sourceRectangle); - // Detect the edges. - new SobelProcessor().Apply(temp, sourceRectangle); + // Apply threshold binarization filter. + new BinaryThresholdProcessor(this.Value).Apply(temp, sourceRectangle); - // Apply threshold binarization filter. - new BinaryThresholdProcessor(this.Value).Apply(temp, sourceRectangle); + // Search for the first white pixels + Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); - // Search for the first white pixels - Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); + if (rectangle == sourceRectangle) + { + return; + } - if (rectangle == sourceRectangle) - { - return; + new CropProcessor(rectangle).Apply(source, sourceRectangle); } - - new CropProcessor(rectangle).Apply(source, sourceRectangle); } } } \ 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 ba21dced7..da4f2b3a8 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class FlipProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -55,27 +55,27 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); - TColor[] target = new TColor[width * height]; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - halfHeight, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + halfHeight, + this.ParallelOptions, + y => { - int newY = height - y - 1; - targetPixels[x, y] = sourcePixels[x, newY]; - targetPixels[x, newY] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < width; x++) + { + int newY = height - y - 1; + targetPixels[x, y] = sourcePixels[x, newY]; + targetPixels[x, newY] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// @@ -89,27 +89,27 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); - TColor[] target = new TColor[width * height]; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < halfWidth; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - int newX = width - x - 1; - targetPixels[x, y] = sourcePixels[newX, y]; - targetPixels[newX, y] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < halfWidth; x++) + { + int newX = width - x - 1; + targetPixels[x, y] = sourcePixels[newX, y]; + targetPixels[newX, y] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Transforms/Matrix3x2Processor.cs b/src/ImageSharp.Processing/Processors/Transforms/Matrix3x2Processor.cs index 209ad3914..7e8757687 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/Matrix3x2Processor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/Matrix3x2Processor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class Matrix3x2Processor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the rectangle designating the target canvas. diff --git a/src/ImageSharp.Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index cac887153..25a7a4efd 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class ResamplingWeightedProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs index 687e452e6..9ec804aa4 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ResizeProcessor : ResamplingWeightedProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -55,6 +55,8 @@ namespace ImageSharp.Processing.Processors 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; @@ -65,103 +67,104 @@ namespace ImageSharp.Processing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); - TColor[] target = new TColor[width * height]; - if (this.Sampler is NearestNeighborResampler) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - - for (int x = minX; x < maxX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; - } - }); + // Y coordinates of source points + int originY = (int)(((y - startY) * heightFactor) + sourceY); + + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; + } + }); + } + + // Break out now. + source.SwapPixelsBuffers(targetPixels); + return; } - - // Break out now. - source.SetPixels(width, height, target); - return; } // 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. - TColor[] firstPass = new TColor[width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock(width, source.Height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - sourceRectangle.Height, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor firstPassPixels = new PixelAccessor(width, source.Height)) + { + Parallel.For( + 0, + sourceRectangle.Bottom, + this.ParallelOptions, + y => { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; + for (int x = minX; x < maxX; x++) + { + // Ensure offsets are normalised for cropping and padding. + Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - // Destination color components - Vector4 destination = Vector4.Zero; + // Destination color components + Vector4 destination = Vector4.Zero; - for (int i = 0; i < horizontalValues.Length; i++) - { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index, y].ToVector4() * xw.Value; - } + for (int i = 0; i < horizontalValues.Length; i++) + { + Weight xw = horizontalValues[i]; + destination += sourcePixels[xw.Index + sourceX, y].ToVector4() * xw.Value; + } - TColor d = default(TColor); - d.PackFromVector4(destination); - firstPassPixels[x, y] = d; - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + TColor d = default(TColor); + d.PackFromVector4(destination); + firstPassPixels[x, y] = d; + } + }); - for (int x = 0; x < width; x++) + // Now process the rows. + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - // Destination color components - Vector4 destination = Vector4.Zero; + // Ensure offsets are normalised for cropping and padding. + Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - for (int i = 0; i < verticalValues.Length; i++) + for (int x = 0; x < width; x++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index].ToVector4() * yw.Value; + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < verticalValues.Length; i++) + { + Weight yw = verticalValues[i]; + destination += firstPassPixels[x, yw.Index + sourceY].ToVector4() * yw.Value; + } + + TColor d = default(TColor); + d.PackFromVector4(destination); + targetPixels[x, y] = d; } + }); + } - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; - } - }); + source.SwapPixelsBuffers(targetPixels); } - - source.SetPixels(width, height, target); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs index 9b9534b39..22fbb895c 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class RotateProcessor : Matrix3x2Processor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The transform matrix to apply. @@ -42,29 +42,30 @@ namespace ImageSharp.Processing.Processors int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - TColor[] target = new TColor[width * height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - Point transformedPoint = Point.Rotate(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + for (int x = 0; x < width; x++) { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + Point transformedPoint = Point.Rotate(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + { + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + } } - } - }); - } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// @@ -124,28 +125,29 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = new TColor[width * height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(height, width)) + using (PixelAccessor targetPixels = new PixelAccessor(height, width)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + newX = height - newX - 1; + int newY = width - x - 1; + targetPixels[newX, newY] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(height, width, target); + source.SwapPixelsBuffers(targetPixels); + } } /// @@ -156,27 +158,28 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = new TColor[width * height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - int newX = width - x - 1; - int newY = height - y - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < width; x++) + { + int newX = width - x - 1; + int newY = height - y - 1; + targetPixels[newX, newY] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// @@ -187,26 +190,27 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = new TColor[width * height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(height, width)) + using (PixelAccessor targetPixels = new PixelAccessor(height, width)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - int newX = height - y - 1; - targetPixels[newX, x] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + targetPixels[newX, x] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(height, width, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ 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 c94f69358..c6afbedcc 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class SkewProcessor : Matrix3x2Processor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The transform matrix to apply. @@ -42,29 +42,30 @@ namespace ImageSharp.Processing.Processors int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - TColor[] target = new TColor[width * height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - Point transformedPoint = Point.Skew(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + for (int x = 0; x < width; x++) { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + Point transformedPoint = Point.Skew(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + { + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + } } - } - }); - } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// diff --git a/src/ImageSharp.Processing/Transforms/AutoOrient.cs b/src/ImageSharp.Processing/Transforms/AutoOrient.cs index 8d86ae814..8c5e22b99 100644 --- a/src/ImageSharp.Processing/Transforms/AutoOrient.cs +++ b/src/ImageSharp.Processing/Transforms/AutoOrient.cs @@ -21,7 +21,7 @@ namespace ImageSharp /// The image to auto rotate. /// The public static Image AutoOrient(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Orientation orientation = GetExifOrientation(source); @@ -64,14 +64,14 @@ namespace ImageSharp /// The image to auto rotate. /// The private static Orientation GetExifOrientation(Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - if (source.ExifProfile == null) + if (source.MetaData.ExifProfile == null) { return Orientation.Unknown; } - ExifValue value = source.ExifProfile.GetValue(ExifTag.Orientation); + ExifValue value = source.MetaData.ExifProfile.GetValue(ExifTag.Orientation); if (value == null) { return Orientation.Unknown; @@ -79,7 +79,7 @@ namespace ImageSharp Orientation orientation = (Orientation)value.Value; - source.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); + source.MetaData.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); return orientation; } diff --git a/src/ImageSharp.Processing/Transforms/Crop.cs b/src/ImageSharp.Processing/Transforms/Crop.cs index 09309a805..92773aaea 100644 --- a/src/ImageSharp.Processing/Transforms/Crop.cs +++ b/src/ImageSharp.Processing/Transforms/Crop.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The target image height. /// The public static Image Crop(this Image source, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Crop(source, new Rectangle(0, 0, width, height)); } @@ -38,10 +38,12 @@ namespace ImageSharp /// /// The public static Image Crop(this Image source, Rectangle cropRectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { CropProcessor processor = new CropProcessor(cropRectangle); - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Transforms/EntropyCrop.cs b/src/ImageSharp.Processing/Transforms/EntropyCrop.cs index 8ba6baf19..ad2ce89e3 100644 --- a/src/ImageSharp.Processing/Transforms/EntropyCrop.cs +++ b/src/ImageSharp.Processing/Transforms/EntropyCrop.cs @@ -22,10 +22,12 @@ namespace ImageSharp /// The threshold for entropic density. /// The public static Image EntropyCrop(this Image source, float threshold = .5f) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { EntropyCropProcessor processor = new EntropyCropProcessor(threshold); - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Transforms/Flip.cs b/src/ImageSharp.Processing/Transforms/Flip.cs index 4b4c1b7d6..ed096eb75 100644 --- a/src/ImageSharp.Processing/Transforms/Flip.cs +++ b/src/ImageSharp.Processing/Transforms/Flip.cs @@ -23,10 +23,12 @@ namespace ImageSharp /// The to perform the flip. /// The public static Image Flip(this Image source, FlipType flipType) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { FlipProcessor processor = new FlipProcessor(flipType); - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Transforms/Options/ResizeHelper.cs b/src/ImageSharp.Processing/Transforms/Options/ResizeHelper.cs index 4ed147aed..3dc37e52d 100644 --- a/src/ImageSharp.Processing/Transforms/Options/ResizeHelper.cs +++ b/src/ImageSharp.Processing/Transforms/Options/ResizeHelper.cs @@ -24,7 +24,7 @@ namespace ImageSharp.Processing /// The . /// public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { switch (options.Mode) { @@ -55,7 +55,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; @@ -174,7 +174,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; @@ -255,7 +255,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; @@ -342,7 +342,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; @@ -383,7 +383,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; diff --git a/src/ImageSharp.Processing/Transforms/Pad.cs b/src/ImageSharp.Processing/Transforms/Pad.cs index df45a94f3..bd530ecd8 100644 --- a/src/ImageSharp.Processing/Transforms/Pad.cs +++ b/src/ImageSharp.Processing/Transforms/Pad.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The new height. /// The . public static Image Pad(this Image source, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { ResizeOptions options = new ResizeOptions { diff --git a/src/ImageSharp.Processing/Transforms/Resize.cs b/src/ImageSharp.Processing/Transforms/Resize.cs index 35ffc8b7d..ab256c4ae 100644 --- a/src/ImageSharp.Processing/Transforms/Resize.cs +++ b/src/ImageSharp.Processing/Transforms/Resize.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) @@ -51,7 +51,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, Size size) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, size.Width, size.Height, new BicubicResampler(), false); } @@ -66,7 +66,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, width, height, new BicubicResampler(), false); } @@ -82,7 +82,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height, bool compand) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, width, height, new BicubicResampler(), compand); } @@ -98,7 +98,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height, IResampler sampler) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, width, height, sampler, false); } @@ -115,7 +115,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand); } @@ -139,7 +139,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (width == 0 && height > 0) { @@ -167,7 +167,8 @@ namespace ImageSharp processor = new ResizeProcessor(sampler, width, height, targetRectangle); } - return source.Apply(sourceRectangle, processor); + source.ApplyProcessor(processor, sourceRectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Transforms/Rotate.cs b/src/ImageSharp.Processing/Transforms/Rotate.cs index b35bbc58a..76311ef25 100644 --- a/src/ImageSharp.Processing/Transforms/Rotate.cs +++ b/src/ImageSharp.Processing/Transforms/Rotate.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The angle in degrees to perform the rotation. /// The public static Image Rotate(this Image source, float degrees) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Rotate(source, degrees, true); } @@ -36,7 +36,7 @@ namespace ImageSharp /// The to perform the rotation. /// The public static Image Rotate(this Image source, RotateType rotateType) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Rotate(source, (float)rotateType, false); } @@ -50,10 +50,12 @@ namespace ImageSharp /// Whether to expand the image to fit the rotated result. /// The public static Image Rotate(this Image source, float degrees, bool expand) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } diff --git a/src/ImageSharp.Processing/Transforms/RotateFlip.cs b/src/ImageSharp.Processing/Transforms/RotateFlip.cs index e69f18338..d6050db3f 100644 --- a/src/ImageSharp.Processing/Transforms/RotateFlip.cs +++ b/src/ImageSharp.Processing/Transforms/RotateFlip.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The to perform the flip. /// The public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Rotate(rotateType).Flip(flipType); } diff --git a/src/ImageSharp.Processing/Transforms/Skew.cs b/src/ImageSharp.Processing/Transforms/Skew.cs index 825dce555..03fdbcceb 100644 --- a/src/ImageSharp.Processing/Transforms/Skew.cs +++ b/src/ImageSharp.Processing/Transforms/Skew.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The angle in degrees to perform the rotation along the y-axis. /// The public static Image Skew(this Image source, float degreesX, float degreesY) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Skew(source, degreesX, degreesY, true); } @@ -38,10 +38,12 @@ namespace ImageSharp /// Whether to expand the image to fit the skewed result. /// The public static Image Skew(this Image source, float degreesX, float degreesY, bool expand) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 036ee14c1..1ad6f9a64 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -8,6 +8,7 @@ namespace ImageSharp using System; using System.Globalization; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. @@ -17,7 +18,7 @@ namespace ImageSharp /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, /// as it avoids the need to create new values for modification operations. /// - public partial struct Color : IPackedPixel, IEquatable + public partial struct Color : IPixel, IPackedVector { /// /// The shift count for the red component @@ -66,28 +67,6 @@ namespace ImageSharp this.packedValue = Pack(r, g, b, a); } - /// - /// Initializes a new instance of the struct. - /// - /// - /// The hexadecimal representation of the combined color components arranged - /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. - /// - public Color(string hex) - { - Guard.NotNullOrEmpty(hex, nameof(hex)); - - hex = ToRgbaHex(hex); - - if (hex == null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out this.packedValue)) - { - throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); - } - - // Order parsed from hex string will be backwards, so reverse it. - this.packedValue = Pack(this.A, this.B, this.G, this.R); - } - /// /// Initializes a new instance of the struct. /// @@ -138,11 +117,13 @@ namespace ImageSharp /// public byte R { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.packedValue >> RedShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.packedValue = this.packedValue & 0xFFFFFF00 | (uint)value << RedShift; @@ -154,11 +135,13 @@ namespace ImageSharp /// public byte G { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.packedValue >> GreenShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.packedValue = this.packedValue & 0xFFFF00FF | (uint)value << GreenShift; @@ -170,11 +153,13 @@ namespace ImageSharp /// public byte B { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.packedValue >> BlueShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.packedValue = this.packedValue & 0xFF00FFFF | (uint)value << BlueShift; @@ -186,11 +171,13 @@ namespace ImageSharp /// public byte A { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.packedValue >> AlphaShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.packedValue = this.packedValue & 0x00FFFFFF | (uint)value << AlphaShift; @@ -223,6 +210,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Color left, Color right) { return left.packedValue == right.packedValue; @@ -236,6 +224,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Color left, Color right) { return left.packedValue != right.packedValue; @@ -253,10 +242,14 @@ namespace ImageSharp /// public static Color FromHex(string hex) { - return new Color(hex); + return ColorBuilder.FromHex(hex); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.packedValue = Pack(x, y, z, w); @@ -273,6 +266,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.R; @@ -281,6 +275,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.R; @@ -290,6 +285,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.B; @@ -298,6 +294,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.B; @@ -307,12 +304,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.packedValue = Pack(ref vector); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; @@ -325,6 +324,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Color other) { return this.packedValue == other.packedValue; @@ -350,11 +350,12 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); vector *= MaxBytes; vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); return (uint)(((byte)vector.X << RedShift) | ((byte)vector.Y << GreenShift) | ((byte)vector.Z << BlueShift) @@ -366,6 +367,7 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector3 vector) { Vector4 value = new Vector4(vector, 1); @@ -380,6 +382,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { Vector4 value = new Vector4(x, y, z, w); @@ -394,43 +397,10 @@ namespace ImageSharp /// The z-component /// The w-component /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(byte x, byte y, byte z, byte w) { return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); } - - /// - /// 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) - { - hex = hex.StartsWith("#") ? hex.Substring(1) : hex; - - if (hex.Length == 8) - { - return hex; - } - - if (hex.Length == 6) - { - return hex + "FF"; - } - - if (hex.Length < 3 || hex.Length > 4) - { - return null; - } - - string red = char.ToString(hex[0]); - string green = char.ToString(hex[1]); - string blue = char.ToString(hex[2]); - string alpha = hex.Length == 3 ? "F" : char.ToString(hex[3]); - - return red + red + green + green + blue + blue + alpha + alpha; - } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/ColorBuilder{TColor}.cs b/src/ImageSharp/Colors/ColorBuilder{TColor}.cs new file mode 100644 index 000000000..f60b5c8c0 --- /dev/null +++ b/src/ImageSharp/Colors/ColorBuilder{TColor}.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + + /// + /// A set of named colors mapped to the provided Color space. + /// + /// The type of the color. + public static class ColorBuilder + where TColor : 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 heax string. + public static TColor FromHex(string hex) + { + Guard.NotNullOrEmpty(hex, nameof(hex)); + + hex = ToRgbaHex(hex); + uint packedValue; + if (hex == null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out packedValue)) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + TColor result = default(TColor); + + result.PackFromBytes( + (byte)(packedValue >> 24), + (byte)(packedValue >> 16), + (byte)(packedValue >> 8), + (byte)(packedValue >> 0)); + 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 TColor FromRGB(byte red, byte green, byte blue) + { + TColor color = default(TColor); + color.PackFromBytes(red, green, blue, 255); + return color; + } + + /// + /// 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 TColor FromRGBA(byte red, byte green, byte blue, byte alpha) + { + TColor color = default(TColor); + color.PackFromBytes(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) + { + hex = hex.StartsWith("#") ? hex.Substring(1) : hex; + + if (hex.Length == 8) + { + return hex; + } + + if (hex.Length == 6) + { + return hex + "FF"; + } + + if (hex.Length < 3 || hex.Length > 4) + { + return null; + } + + string red = char.ToString(hex[0]); + string green = char.ToString(hex[1]); + string blue = char.ToString(hex[2]); + string alpha = hex.Length == 3 ? "F" : char.ToString(hex[3]); + + return string.Concat(red, red, green, green, blue, blue, alpha, alpha); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/ColorDefinitions.cs b/src/ImageSharp/Colors/ColorDefinitions.cs index 5c1c30fbd..65165289d 100644 --- a/src/ImageSharp/Colors/ColorDefinitions.cs +++ b/src/ImageSharp/Colors/ColorDefinitions.cs @@ -18,711 +18,711 @@ namespace ImageSharp /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// - public static readonly Color AliceBlue = new Color(240, 248, 255, 255); + public static readonly Color AliceBlue = NamedColors.AliceBlue; /// /// Represents a matching the W3C definition that has an hex value of #FAEBD7. /// - public static readonly Color AntiqueWhite = new Color(250, 235, 215, 255); + public static readonly Color AntiqueWhite = NamedColors.AntiqueWhite; /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Aqua = new Color(0, 255, 255, 255); + public static readonly Color Aqua = NamedColors.Aqua; /// /// Represents a matching the W3C definition that has an hex value of #7FFFD4. /// - public static readonly Color Aquamarine = new Color(127, 255, 212, 255); + public static readonly Color Aquamarine = NamedColors.Aquamarine; /// /// Represents a matching the W3C definition that has an hex value of #F0FFFF. /// - public static readonly Color Azure = new Color(240, 255, 255, 255); + public static readonly Color Azure = NamedColors.Azure; /// /// Represents a matching the W3C definition that has an hex value of #F5F5DC. /// - public static readonly Color Beige = new Color(245, 245, 220, 255); + public static readonly Color Beige = NamedColors.Beige; /// /// Represents a matching the W3C definition that has an hex value of #FFE4C4. /// - public static readonly Color Bisque = new Color(255, 228, 196, 255); + public static readonly Color Bisque = NamedColors.Bisque; /// /// Represents a matching the W3C definition that has an hex value of #000000. /// - public static readonly Color Black = new Color(0, 0, 0, 255); + public static readonly Color Black = NamedColors.Black; /// /// Represents a matching the W3C definition that has an hex value of #FFEBCD. /// - public static readonly Color BlanchedAlmond = new Color(255, 235, 205, 255); + public static readonly Color BlanchedAlmond = NamedColors.BlanchedAlmond; /// /// Represents a matching the W3C definition that has an hex value of #0000FF. /// - public static readonly Color Blue = new Color(0, 0, 255, 255); + public static readonly Color Blue = NamedColors.Blue; /// /// Represents a matching the W3C definition that has an hex value of #8A2BE2. /// - public static readonly Color BlueViolet = new Color(138, 43, 226, 255); + public static readonly Color BlueViolet = NamedColors.BlueViolet; /// /// Represents a matching the W3C definition that has an hex value of #A52A2A. /// - public static readonly Color Brown = new Color(165, 42, 42, 255); + public static readonly Color Brown = NamedColors.Brown; /// /// Represents a matching the W3C definition that has an hex value of #DEB887. /// - public static readonly Color BurlyWood = new Color(222, 184, 135, 255); + public static readonly Color BurlyWood = NamedColors.BurlyWood; /// /// Represents a matching the W3C definition that has an hex value of #5F9EA0. /// - public static readonly Color CadetBlue = new Color(95, 158, 160, 255); + public static readonly Color CadetBlue = NamedColors.CadetBlue; /// /// Represents a matching the W3C definition that has an hex value of #7FFF00. /// - public static readonly Color Chartreuse = new Color(127, 255, 0, 255); + public static readonly Color Chartreuse = NamedColors.Chartreuse; /// /// Represents a matching the W3C definition that has an hex value of #D2691E. /// - public static readonly Color Chocolate = new Color(210, 105, 30, 255); + public static readonly Color Chocolate = NamedColors.Chocolate; /// /// Represents a matching the W3C definition that has an hex value of #FF7F50. /// - public static readonly Color Coral = new Color(255, 127, 80, 255); + public static readonly Color Coral = NamedColors.Coral; /// /// Represents a matching the W3C definition that has an hex value of #6495ED. /// - public static readonly Color CornflowerBlue = new Color(100, 149, 237, 255); + public static readonly Color CornflowerBlue = NamedColors.CornflowerBlue; /// /// Represents a matching the W3C definition that has an hex value of #FFF8DC. /// - public static readonly Color Cornsilk = new Color(255, 248, 220, 255); + public static readonly Color Cornsilk = NamedColors.Cornsilk; /// /// Represents a matching the W3C definition that has an hex value of #DC143C. /// - public static readonly Color Crimson = new Color(220, 20, 60, 255); + public static readonly Color Crimson = NamedColors.Crimson; /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Cyan = new Color(0, 255, 255, 255); + public static readonly Color Cyan = NamedColors.Cyan; /// /// Represents a matching the W3C definition that has an hex value of #00008B. /// - public static readonly Color DarkBlue = new Color(0, 0, 139, 255); + public static readonly Color DarkBlue = NamedColors.DarkBlue; /// /// Represents a matching the W3C definition that has an hex value of #008B8B. /// - public static readonly Color DarkCyan = new Color(0, 139, 139, 255); + public static readonly Color DarkCyan = NamedColors.DarkCyan; /// /// Represents a matching the W3C definition that has an hex value of #B8860B. /// - public static readonly Color DarkGoldenrod = new Color(184, 134, 11, 255); + public static readonly Color DarkGoldenrod = NamedColors.DarkGoldenrod; /// /// Represents a matching the W3C definition that has an hex value of #A9A9A9. /// - public static readonly Color DarkGray = new Color(169, 169, 169, 255); + public static readonly Color DarkGray = NamedColors.DarkGray; /// /// Represents a matching the W3C definition that has an hex value of #006400. /// - public static readonly Color DarkGreen = new Color(0, 100, 0, 255); + public static readonly Color DarkGreen = NamedColors.DarkGreen; /// /// Represents a matching the W3C definition that has an hex value of #BDB76B. /// - public static readonly Color DarkKhaki = new Color(189, 183, 107, 255); + public static readonly Color DarkKhaki = NamedColors.DarkKhaki; /// /// Represents a matching the W3C definition that has an hex value of #8B008B. /// - public static readonly Color DarkMagenta = new Color(139, 0, 139, 255); + public static readonly Color DarkMagenta = NamedColors.DarkMagenta; /// /// Represents a matching the W3C definition that has an hex value of #556B2F. /// - public static readonly Color DarkOliveGreen = new Color(85, 107, 47, 255); + public static readonly Color DarkOliveGreen = NamedColors.DarkOliveGreen; /// /// Represents a matching the W3C definition that has an hex value of #FF8C00. /// - public static readonly Color DarkOrange = new Color(255, 140, 0, 255); + public static readonly Color DarkOrange = NamedColors.DarkOrange; /// /// Represents a matching the W3C definition that has an hex value of #9932CC. /// - public static readonly Color DarkOrchid = new Color(153, 50, 204, 255); + public static readonly Color DarkOrchid = NamedColors.DarkOrchid; /// /// Represents a matching the W3C definition that has an hex value of #8B0000. /// - public static readonly Color DarkRed = new Color(139, 0, 0, 255); + public static readonly Color DarkRed = NamedColors.DarkRed; /// /// Represents a matching the W3C definition that has an hex value of #E9967A. /// - public static readonly Color DarkSalmon = new Color(233, 150, 122, 255); + public static readonly Color DarkSalmon = NamedColors.DarkSalmon; /// /// Represents a matching the W3C definition that has an hex value of #8FBC8B. /// - public static readonly Color DarkSeaGreen = new Color(143, 188, 139, 255); + public static readonly Color DarkSeaGreen = NamedColors.DarkSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #483D8B. /// - public static readonly Color DarkSlateBlue = new Color(72, 61, 139, 255); + public static readonly Color DarkSlateBlue = NamedColors.DarkSlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #2F4F4F. /// - public static readonly Color DarkSlateGray = new Color(47, 79, 79, 255); + public static readonly Color DarkSlateGray = NamedColors.DarkSlateGray; /// /// Represents a matching the W3C definition that has an hex value of #00CED1. /// - public static readonly Color DarkTurquoise = new Color(0, 206, 209, 255); + public static readonly Color DarkTurquoise = NamedColors.DarkTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #9400D3. /// - public static readonly Color DarkViolet = new Color(148, 0, 211, 255); + public static readonly Color DarkViolet = NamedColors.DarkViolet; /// /// Represents a matching the W3C definition that has an hex value of #FF1493. /// - public static readonly Color DeepPink = new Color(255, 20, 147, 255); + public static readonly Color DeepPink = NamedColors.DeepPink; /// /// Represents a matching the W3C definition that has an hex value of #00BFFF. /// - public static readonly Color DeepSkyBlue = new Color(0, 191, 255, 255); + public static readonly Color DeepSkyBlue = NamedColors.DeepSkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #696969. /// - public static readonly Color DimGray = new Color(105, 105, 105, 255); + public static readonly Color DimGray = NamedColors.DimGray; /// /// Represents a matching the W3C definition that has an hex value of #1E90FF. /// - public static readonly Color DodgerBlue = new Color(30, 144, 255, 255); + public static readonly Color DodgerBlue = NamedColors.DodgerBlue; /// /// Represents a matching the W3C definition that has an hex value of #B22222. /// - public static readonly Color Firebrick = new Color(178, 34, 34, 255); + public static readonly Color Firebrick = NamedColors.Firebrick; /// /// Represents a matching the W3C definition that has an hex value of #FFFAF0. /// - public static readonly Color FloralWhite = new Color(255, 250, 240, 255); + public static readonly Color FloralWhite = NamedColors.FloralWhite; /// /// Represents a matching the W3C definition that has an hex value of #228B22. /// - public static readonly Color ForestGreen = new Color(34, 139, 34, 255); + public static readonly Color ForestGreen = NamedColors.ForestGreen; /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Fuchsia = new Color(255, 0, 255, 255); + public static readonly Color Fuchsia = NamedColors.Fuchsia; /// /// Represents a matching the W3C definition that has an hex value of #DCDCDC. /// - public static readonly Color Gainsboro = new Color(220, 220, 220, 255); + public static readonly Color Gainsboro = NamedColors.Gainsboro; /// /// Represents a matching the W3C definition that has an hex value of #F8F8FF. /// - public static readonly Color GhostWhite = new Color(248, 248, 255, 255); + public static readonly Color GhostWhite = NamedColors.GhostWhite; /// /// Represents a matching the W3C definition that has an hex value of #FFD700. /// - public static readonly Color Gold = new Color(255, 215, 0, 255); + public static readonly Color Gold = NamedColors.Gold; /// /// Represents a matching the W3C definition that has an hex value of #DAA520. /// - public static readonly Color Goldenrod = new Color(218, 165, 32, 255); + public static readonly Color Goldenrod = NamedColors.Goldenrod; /// /// Represents a matching the W3C definition that has an hex value of #808080. /// - public static readonly Color Gray = new Color(128, 128, 128, 255); + public static readonly Color Gray = NamedColors.Gray; /// /// Represents a matching the W3C definition that has an hex value of #008000. /// - public static readonly Color Green = new Color(0, 128, 0, 255); + public static readonly Color Green = NamedColors.Green; /// /// Represents a matching the W3C definition that has an hex value of #ADFF2F. /// - public static readonly Color GreenYellow = new Color(173, 255, 47, 255); + public static readonly Color GreenYellow = NamedColors.GreenYellow; /// /// Represents a matching the W3C definition that has an hex value of #F0FFF0. /// - public static readonly Color Honeydew = new Color(240, 255, 240, 255); + public static readonly Color Honeydew = NamedColors.Honeydew; /// /// Represents a matching the W3C definition that has an hex value of #FF69B4. /// - public static readonly Color HotPink = new Color(255, 105, 180, 255); + public static readonly Color HotPink = NamedColors.HotPink; /// /// Represents a matching the W3C definition that has an hex value of #CD5C5C. /// - public static readonly Color IndianRed = new Color(205, 92, 92, 255); + public static readonly Color IndianRed = NamedColors.IndianRed; /// /// Represents a matching the W3C definition that has an hex value of #4B0082. /// - public static readonly Color Indigo = new Color(75, 0, 130, 255); + public static readonly Color Indigo = NamedColors.Indigo; /// /// Represents a matching the W3C definition that has an hex value of #FFFFF0. /// - public static readonly Color Ivory = new Color(255, 255, 240, 255); + public static readonly Color Ivory = NamedColors.Ivory; /// /// Represents a matching the W3C definition that has an hex value of #F0E68C. /// - public static readonly Color Khaki = new Color(240, 230, 140, 255); + public static readonly Color Khaki = NamedColors.Khaki; /// /// Represents a matching the W3C definition that has an hex value of #E6E6FA. /// - public static readonly Color Lavender = new Color(230, 230, 250, 255); + public static readonly Color Lavender = NamedColors.Lavender; /// /// Represents a matching the W3C definition that has an hex value of #FFF0F5. /// - public static readonly Color LavenderBlush = new Color(255, 240, 245, 255); + public static readonly Color LavenderBlush = NamedColors.LavenderBlush; /// /// Represents a matching the W3C definition that has an hex value of #7CFC00. /// - public static readonly Color LawnGreen = new Color(124, 252, 0, 255); + public static readonly Color LawnGreen = NamedColors.LawnGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFFACD. /// - public static readonly Color LemonChiffon = new Color(255, 250, 205, 255); + public static readonly Color LemonChiffon = NamedColors.LemonChiffon; /// /// Represents a matching the W3C definition that has an hex value of #ADD8E6. /// - public static readonly Color LightBlue = new Color(173, 216, 230, 255); + public static readonly Color LightBlue = NamedColors.LightBlue; /// /// Represents a matching the W3C definition that has an hex value of #F08080. /// - public static readonly Color LightCoral = new Color(240, 128, 128, 255); + public static readonly Color LightCoral = NamedColors.LightCoral; /// /// Represents a matching the W3C definition that has an hex value of #E0FFFF. /// - public static readonly Color LightCyan = new Color(224, 255, 255, 255); + public static readonly Color LightCyan = NamedColors.LightCyan; /// /// Represents a matching the W3C definition that has an hex value of #FAFAD2. /// - public static readonly Color LightGoldenrodYellow = new Color(250, 250, 210, 255); + public static readonly Color LightGoldenrodYellow = NamedColors.LightGoldenrodYellow; /// /// Represents a matching the W3C definition that has an hex value of #D3D3D3. /// - public static readonly Color LightGray = new Color(211, 211, 211, 255); + public static readonly Color LightGray = NamedColors.LightGray; /// /// Represents a matching the W3C definition that has an hex value of #90EE90. /// - public static readonly Color LightGreen = new Color(144, 238, 144, 255); + public static readonly Color LightGreen = NamedColors.LightGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFB6C1. /// - public static readonly Color LightPink = new Color(255, 182, 193, 255); + public static readonly Color LightPink = NamedColors.LightPink; /// /// Represents a matching the W3C definition that has an hex value of #FFA07A. /// - public static readonly Color LightSalmon = new Color(255, 160, 122, 255); + public static readonly Color LightSalmon = NamedColors.LightSalmon; /// /// Represents a matching the W3C definition that has an hex value of #20B2AA. /// - public static readonly Color LightSeaGreen = new Color(32, 178, 170, 255); + public static readonly Color LightSeaGreen = NamedColors.LightSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #87CEFA. /// - public static readonly Color LightSkyBlue = new Color(135, 206, 250, 255); + public static readonly Color LightSkyBlue = NamedColors.LightSkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #778899. /// - public static readonly Color LightSlateGray = new Color(119, 136, 153, 255); + public static readonly Color LightSlateGray = NamedColors.LightSlateGray; /// /// Represents a matching the W3C definition that has an hex value of #B0C4DE. /// - public static readonly Color LightSteelBlue = new Color(176, 196, 222, 255); + public static readonly Color LightSteelBlue = NamedColors.LightSteelBlue; /// /// Represents a matching the W3C definition that has an hex value of #FFFFE0. /// - public static readonly Color LightYellow = new Color(255, 255, 224, 255); + public static readonly Color LightYellow = NamedColors.LightYellow; /// /// Represents a matching the W3C definition that has an hex value of #00FF00. /// - public static readonly Color Lime = new Color(0, 255, 0, 255); + public static readonly Color Lime = NamedColors.Lime; /// /// Represents a matching the W3C definition that has an hex value of #32CD32. /// - public static readonly Color LimeGreen = new Color(50, 205, 50, 255); + public static readonly Color LimeGreen = NamedColors.LimeGreen; /// /// Represents a matching the W3C definition that has an hex value of #FAF0E6. /// - public static readonly Color Linen = new Color(250, 240, 230, 255); + public static readonly Color Linen = NamedColors.Linen; /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Magenta = new Color(255, 0, 255, 255); + public static readonly Color Magenta = NamedColors.Magenta; /// /// Represents a matching the W3C definition that has an hex value of #800000. /// - public static readonly Color Maroon = new Color(128, 0, 0, 255); + public static readonly Color Maroon = NamedColors.Maroon; /// /// Represents a matching the W3C definition that has an hex value of #66CDAA. /// - public static readonly Color MediumAquamarine = new Color(102, 205, 170, 255); + public static readonly Color MediumAquamarine = NamedColors.MediumAquamarine; /// /// Represents a matching the W3C definition that has an hex value of #0000CD. /// - public static readonly Color MediumBlue = new Color(0, 0, 205, 255); + public static readonly Color MediumBlue = NamedColors.MediumBlue; /// /// Represents a matching the W3C definition that has an hex value of #BA55D3. /// - public static readonly Color MediumOrchid = new Color(186, 85, 211, 255); + public static readonly Color MediumOrchid = NamedColors.MediumOrchid; /// /// Represents a matching the W3C definition that has an hex value of #9370DB. /// - public static readonly Color MediumPurple = new Color(147, 112, 219, 255); + public static readonly Color MediumPurple = NamedColors.MediumPurple; /// /// Represents a matching the W3C definition that has an hex value of #3CB371. /// - public static readonly Color MediumSeaGreen = new Color(60, 179, 113, 255); + public static readonly Color MediumSeaGreen = NamedColors.MediumSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #7B68EE. /// - public static readonly Color MediumSlateBlue = new Color(123, 104, 238, 255); + public static readonly Color MediumSlateBlue = NamedColors.MediumSlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #00FA9A. /// - public static readonly Color MediumSpringGreen = new Color(0, 250, 154, 255); + public static readonly Color MediumSpringGreen = NamedColors.MediumSpringGreen; /// /// Represents a matching the W3C definition that has an hex value of #48D1CC. /// - public static readonly Color MediumTurquoise = new Color(72, 209, 204, 255); + public static readonly Color MediumTurquoise = NamedColors.MediumTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #C71585. /// - public static readonly Color MediumVioletRed = new Color(199, 21, 133, 255); + public static readonly Color MediumVioletRed = NamedColors.MediumVioletRed; /// /// Represents a matching the W3C definition that has an hex value of #191970. /// - public static readonly Color MidnightBlue = new Color(25, 25, 112, 255); + public static readonly Color MidnightBlue = NamedColors.MidnightBlue; /// /// Represents a matching the W3C definition that has an hex value of #F5FFFA. /// - public static readonly Color MintCream = new Color(245, 255, 250, 255); + public static readonly Color MintCream = NamedColors.MintCream; /// /// Represents a matching the W3C definition that has an hex value of #FFE4E1. /// - public static readonly Color MistyRose = new Color(255, 228, 225, 255); + public static readonly Color MistyRose = NamedColors.MistyRose; /// /// Represents a matching the W3C definition that has an hex value of #FFE4B5. /// - public static readonly Color Moccasin = new Color(255, 228, 181, 255); + public static readonly Color Moccasin = NamedColors.Moccasin; /// /// Represents a matching the W3C definition that has an hex value of #FFDEAD. /// - public static readonly Color NavajoWhite = new Color(255, 222, 173, 255); + public static readonly Color NavajoWhite = NamedColors.NavajoWhite; /// /// Represents a matching the W3C definition that has an hex value of #000080. /// - public static readonly Color Navy = new Color(0, 0, 128, 255); + public static readonly Color Navy = NamedColors.Navy; /// /// Represents a matching the W3C definition that has an hex value of #FDF5E6. /// - public static readonly Color OldLace = new Color(253, 245, 230, 255); + public static readonly Color OldLace = NamedColors.OldLace; /// /// Represents a matching the W3C definition that has an hex value of #808000. /// - public static readonly Color Olive = new Color(128, 128, 0, 255); + public static readonly Color Olive = NamedColors.Olive; /// /// Represents a matching the W3C definition that has an hex value of #6B8E23. /// - public static readonly Color OliveDrab = new Color(107, 142, 35, 255); + public static readonly Color OliveDrab = NamedColors.OliveDrab; /// /// Represents a matching the W3C definition that has an hex value of #FFA500. /// - public static readonly Color Orange = new Color(255, 165, 0, 255); + public static readonly Color Orange = NamedColors.Orange; /// /// Represents a matching the W3C definition that has an hex value of #FF4500. /// - public static readonly Color OrangeRed = new Color(255, 69, 0, 255); + public static readonly Color OrangeRed = NamedColors.OrangeRed; /// /// Represents a matching the W3C definition that has an hex value of #DA70D6. /// - public static readonly Color Orchid = new Color(218, 112, 214, 255); + public static readonly Color Orchid = NamedColors.Orchid; /// /// Represents a matching the W3C definition that has an hex value of #EEE8AA. /// - public static readonly Color PaleGoldenrod = new Color(238, 232, 170, 255); + public static readonly Color PaleGoldenrod = NamedColors.PaleGoldenrod; /// /// Represents a matching the W3C definition that has an hex value of #98FB98. /// - public static readonly Color PaleGreen = new Color(152, 251, 152, 255); + public static readonly Color PaleGreen = NamedColors.PaleGreen; /// /// Represents a matching the W3C definition that has an hex value of #AFEEEE. /// - public static readonly Color PaleTurquoise = new Color(175, 238, 238, 255); + public static readonly Color PaleTurquoise = NamedColors.PaleTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #DB7093. /// - public static readonly Color PaleVioletRed = new Color(219, 112, 147, 255); + public static readonly Color PaleVioletRed = NamedColors.PaleVioletRed; /// /// Represents a matching the W3C definition that has an hex value of #FFEFD5. /// - public static readonly Color PapayaWhip = new Color(255, 239, 213, 255); + public static readonly Color PapayaWhip = NamedColors.PapayaWhip; /// /// Represents a matching the W3C definition that has an hex value of #FFDAB9. /// - public static readonly Color PeachPuff = new Color(255, 218, 185, 255); + public static readonly Color PeachPuff = NamedColors.PeachPuff; /// /// Represents a matching the W3C definition that has an hex value of #CD853F. /// - public static readonly Color Peru = new Color(205, 133, 63, 255); + public static readonly Color Peru = NamedColors.Peru; /// /// Represents a matching the W3C definition that has an hex value of #FFC0CB. /// - public static readonly Color Pink = new Color(255, 192, 203, 255); + public static readonly Color Pink = NamedColors.Pink; /// /// Represents a matching the W3C definition that has an hex value of #DDA0DD. /// - public static readonly Color Plum = new Color(221, 160, 221, 255); + public static readonly Color Plum = NamedColors.Plum; /// /// Represents a matching the W3C definition that has an hex value of #B0E0E6. /// - public static readonly Color PowderBlue = new Color(176, 224, 230, 255); + public static readonly Color PowderBlue = NamedColors.PowderBlue; /// /// Represents a matching the W3C definition that has an hex value of #800080. /// - public static readonly Color Purple = new Color(128, 0, 128, 255); + public static readonly Color Purple = NamedColors.Purple; /// /// Represents a matching the W3C definition that has an hex value of #663399. /// - public static readonly Color RebeccaPurple = new Color(102, 51, 153, 255); + public static readonly Color RebeccaPurple = NamedColors.RebeccaPurple; /// /// Represents a matching the W3C definition that has an hex value of #FF0000. /// - public static readonly Color Red = new Color(255, 0, 0, 255); + public static readonly Color Red = NamedColors.Red; /// /// Represents a matching the W3C definition that has an hex value of #BC8F8F. /// - public static readonly Color RosyBrown = new Color(188, 143, 143, 255); + public static readonly Color RosyBrown = NamedColors.RosyBrown; /// /// Represents a matching the W3C definition that has an hex value of #4169E1. /// - public static readonly Color RoyalBlue = new Color(65, 105, 225, 255); + public static readonly Color RoyalBlue = NamedColors.RoyalBlue; /// /// Represents a matching the W3C definition that has an hex value of #8B4513. /// - public static readonly Color SaddleBrown = new Color(139, 69, 19, 255); + public static readonly Color SaddleBrown = NamedColors.SaddleBrown; /// /// Represents a matching the W3C definition that has an hex value of #FA8072. /// - public static readonly Color Salmon = new Color(250, 128, 114, 255); + public static readonly Color Salmon = NamedColors.Salmon; /// /// Represents a matching the W3C definition that has an hex value of #F4A460. /// - public static readonly Color SandyBrown = new Color(244, 164, 96, 255); + public static readonly Color SandyBrown = NamedColors.SandyBrown; /// /// Represents a matching the W3C definition that has an hex value of #2E8B57. /// - public static readonly Color SeaGreen = new Color(46, 139, 87, 255); + public static readonly Color SeaGreen = NamedColors.SeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFF5EE. /// - public static readonly Color SeaShell = new Color(255, 245, 238, 255); + public static readonly Color SeaShell = NamedColors.SeaShell; /// /// Represents a matching the W3C definition that has an hex value of #A0522D. /// - public static readonly Color Sienna = new Color(160, 82, 45, 255); + public static readonly Color Sienna = NamedColors.Sienna; /// /// Represents a matching the W3C definition that has an hex value of #C0C0C0. /// - public static readonly Color Silver = new Color(192, 192, 192, 255); + public static readonly Color Silver = NamedColors.Silver; /// /// Represents a matching the W3C definition that has an hex value of #87CEEB. /// - public static readonly Color SkyBlue = new Color(135, 206, 235, 255); + public static readonly Color SkyBlue = NamedColors.SkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #6A5ACD. /// - public static readonly Color SlateBlue = new Color(106, 90, 205, 255); + public static readonly Color SlateBlue = NamedColors.SlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #708090. /// - public static readonly Color SlateGray = new Color(112, 128, 144, 255); + public static readonly Color SlateGray = NamedColors.SlateGray; /// /// Represents a matching the W3C definition that has an hex value of #FFFAFA. /// - public static readonly Color Snow = new Color(255, 250, 250, 255); + public static readonly Color Snow = NamedColors.Snow; /// /// Represents a matching the W3C definition that has an hex value of #00FF7F. /// - public static readonly Color SpringGreen = new Color(0, 255, 127, 255); + public static readonly Color SpringGreen = NamedColors.SpringGreen; /// /// Represents a matching the W3C definition that has an hex value of #4682B4. /// - public static readonly Color SteelBlue = new Color(70, 130, 180, 255); + public static readonly Color SteelBlue = NamedColors.SteelBlue; /// /// Represents a matching the W3C definition that has an hex value of #D2B48C. /// - public static readonly Color Tan = new Color(210, 180, 140, 255); + public static readonly Color Tan = NamedColors.Tan; /// /// Represents a matching the W3C definition that has an hex value of #008080. /// - public static readonly Color Teal = new Color(0, 128, 128, 255); + public static readonly Color Teal = NamedColors.Teal; /// /// Represents a matching the W3C definition that has an hex value of #D8BFD8. /// - public static readonly Color Thistle = new Color(216, 191, 216, 255); + public static readonly Color Thistle = NamedColors.Thistle; /// /// Represents a matching the W3C definition that has an hex value of #FF6347. /// - public static readonly Color Tomato = new Color(255, 99, 71, 255); + public static readonly Color Tomato = NamedColors.Tomato; /// /// Represents a matching the W3C definition that has an hex value of #FFFFFF. /// - public static readonly Color Transparent = new Color(255, 255, 255, 0); + public static readonly Color Transparent = NamedColors.Transparent; /// /// Represents a matching the W3C definition that has an hex value of #40E0D0. /// - public static readonly Color Turquoise = new Color(64, 224, 208, 255); + public static readonly Color Turquoise = NamedColors.Turquoise; /// /// Represents a matching the W3C definition that has an hex value of #EE82EE. /// - public static readonly Color Violet = new Color(238, 130, 238, 255); + public static readonly Color Violet = NamedColors.Violet; /// /// Represents a matching the W3C definition that has an hex value of #F5DEB3. /// - public static readonly Color Wheat = new Color(245, 222, 179, 255); + public static readonly Color Wheat = NamedColors.Wheat; /// /// Represents a matching the W3C definition that has an hex value of #FFFFFF. /// - public static readonly Color White = new Color(255, 255, 255, 255); + public static readonly Color White = NamedColors.White; /// /// Represents a matching the W3C definition that has an hex value of #F5F5F5. /// - public static readonly Color WhiteSmoke = new Color(245, 245, 245, 255); + public static readonly Color WhiteSmoke = NamedColors.WhiteSmoke; /// /// Represents a matching the W3C definition that has an hex value of #FFFF00. /// - public static readonly Color Yellow = new Color(255, 255, 0, 255); + public static readonly Color Yellow = NamedColors.Yellow; /// /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// - public static readonly Color YellowGreen = new Color(154, 205, 50, 255); + public static readonly Color YellowGreen = NamedColors.YellowGreen; } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/NamedColors{TColor}.cs b/src/ImageSharp/Colors/NamedColors{TColor}.cs new file mode 100644 index 000000000..bcad4dd40 --- /dev/null +++ b/src/ImageSharp/Colors/NamedColors{TColor}.cs @@ -0,0 +1,727 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A set of named colors mapped to the provided Color space. + /// + /// The type of the color. + public static class NamedColors + where TColor : struct, IPixel + { + /// + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. + /// + public static readonly TColor AliceBlue = ColorBuilder.FromRGBA(240, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly TColor AntiqueWhite = ColorBuilder.FromRGBA(250, 235, 215, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly TColor Aqua = ColorBuilder.FromRGBA(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly TColor Aquamarine = ColorBuilder.FromRGBA(127, 255, 212, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly TColor Azure = ColorBuilder.FromRGBA(240, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly TColor Beige = ColorBuilder.FromRGBA(245, 245, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly TColor Bisque = ColorBuilder.FromRGBA(255, 228, 196, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly TColor Black = ColorBuilder.FromRGBA(0, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. + /// + public static readonly TColor BlanchedAlmond = ColorBuilder.FromRGBA(255, 235, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly TColor Blue = ColorBuilder.FromRGBA(0, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. + /// + public static readonly TColor BlueViolet = ColorBuilder.FromRGBA(138, 43, 226, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A52A2A. + /// + public static readonly TColor Brown = ColorBuilder.FromRGBA(165, 42, 42, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DEB887. + /// + public static readonly TColor BurlyWood = ColorBuilder.FromRGBA(222, 184, 135, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. + /// + public static readonly TColor CadetBlue = ColorBuilder.FromRGBA(95, 158, 160, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFF00. + /// + public static readonly TColor Chartreuse = ColorBuilder.FromRGBA(127, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2691E. + /// + public static readonly TColor Chocolate = ColorBuilder.FromRGBA(210, 105, 30, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF7F50. + /// + public static readonly TColor Coral = ColorBuilder.FromRGBA(255, 127, 80, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6495ED. + /// + public static readonly TColor CornflowerBlue = ColorBuilder.FromRGBA(100, 149, 237, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. + /// + public static readonly TColor Cornsilk = ColorBuilder.FromRGBA(255, 248, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DC143C. + /// + public static readonly TColor Crimson = ColorBuilder.FromRGBA(220, 20, 60, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly TColor Cyan = ColorBuilder.FromRGBA(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00008B. + /// + public static readonly TColor DarkBlue = ColorBuilder.FromRGBA(0, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008B8B. + /// + public static readonly TColor DarkCyan = ColorBuilder.FromRGBA(0, 139, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B8860B. + /// + public static readonly TColor DarkGoldenrod = ColorBuilder.FromRGBA(184, 134, 11, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly TColor DarkGray = ColorBuilder.FromRGBA(169, 169, 169, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #006400. + /// + public static readonly TColor DarkGreen = ColorBuilder.FromRGBA(0, 100, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BDB76B. + /// + public static readonly TColor DarkKhaki = ColorBuilder.FromRGBA(189, 183, 107, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B008B. + /// + public static readonly TColor DarkMagenta = ColorBuilder.FromRGBA(139, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #556B2F. + /// + public static readonly TColor DarkOliveGreen = ColorBuilder.FromRGBA(85, 107, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF8C00. + /// + public static readonly TColor DarkOrange = ColorBuilder.FromRGBA(255, 140, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9932CC. + /// + public static readonly TColor DarkOrchid = ColorBuilder.FromRGBA(153, 50, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B0000. + /// + public static readonly TColor DarkRed = ColorBuilder.FromRGBA(139, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E9967A. + /// + public static readonly TColor DarkSalmon = ColorBuilder.FromRGBA(233, 150, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8FBC8B. + /// + public static readonly TColor DarkSeaGreen = ColorBuilder.FromRGBA(143, 188, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #483D8B. + /// + public static readonly TColor DarkSlateBlue = ColorBuilder.FromRGBA(72, 61, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly TColor DarkSlateGray = ColorBuilder.FromRGBA(47, 79, 79, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00CED1. + /// + public static readonly TColor DarkTurquoise = ColorBuilder.FromRGBA(0, 206, 209, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9400D3. + /// + public static readonly TColor DarkViolet = ColorBuilder.FromRGBA(148, 0, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF1493. + /// + public static readonly TColor DeepPink = ColorBuilder.FromRGBA(255, 20, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00BFFF. + /// + public static readonly TColor DeepSkyBlue = ColorBuilder.FromRGBA(0, 191, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly TColor DimGray = ColorBuilder.FromRGBA(105, 105, 105, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #1E90FF. + /// + public static readonly TColor DodgerBlue = ColorBuilder.FromRGBA(30, 144, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B22222. + /// + public static readonly TColor Firebrick = ColorBuilder.FromRGBA(178, 34, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. + /// + public static readonly TColor FloralWhite = ColorBuilder.FromRGBA(255, 250, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #228B22. + /// + public static readonly TColor ForestGreen = ColorBuilder.FromRGBA(34, 139, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly TColor Fuchsia = ColorBuilder.FromRGBA(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. + /// + public static readonly TColor Gainsboro = ColorBuilder.FromRGBA(220, 220, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. + /// + public static readonly TColor GhostWhite = ColorBuilder.FromRGBA(248, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFD700. + /// + public static readonly TColor Gold = ColorBuilder.FromRGBA(255, 215, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DAA520. + /// + public static readonly TColor Goldenrod = ColorBuilder.FromRGBA(218, 165, 32, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly TColor Gray = ColorBuilder.FromRGBA(128, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly TColor Green = ColorBuilder.FromRGBA(0, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. + /// + public static readonly TColor GreenYellow = ColorBuilder.FromRGBA(173, 255, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFF0. + /// + public static readonly TColor Honeydew = ColorBuilder.FromRGBA(240, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF69B4. + /// + public static readonly TColor HotPink = ColorBuilder.FromRGBA(255, 105, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. + /// + public static readonly TColor IndianRed = ColorBuilder.FromRGBA(205, 92, 92, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4B0082. + /// + public static readonly TColor Indigo = ColorBuilder.FromRGBA(75, 0, 130, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. + /// + public static readonly TColor Ivory = ColorBuilder.FromRGBA(255, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0E68C. + /// + public static readonly TColor Khaki = ColorBuilder.FromRGBA(240, 230, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. + /// + public static readonly TColor Lavender = ColorBuilder.FromRGBA(230, 230, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. + /// + public static readonly TColor LavenderBlush = ColorBuilder.FromRGBA(255, 240, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7CFC00. + /// + public static readonly TColor LawnGreen = ColorBuilder.FromRGBA(124, 252, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFACD. + /// + public static readonly TColor LemonChiffon = ColorBuilder.FromRGBA(255, 250, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. + /// + public static readonly TColor LightBlue = ColorBuilder.FromRGBA(173, 216, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F08080. + /// + public static readonly TColor LightCoral = ColorBuilder.FromRGBA(240, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. + /// + public static readonly TColor LightCyan = ColorBuilder.FromRGBA(224, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. + /// + public static readonly TColor LightGoldenrodYellow = ColorBuilder.FromRGBA(250, 250, 210, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly TColor LightGray = ColorBuilder.FromRGBA(211, 211, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #90EE90. + /// + public static readonly TColor LightGreen = ColorBuilder.FromRGBA(144, 238, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFB6C1. + /// + public static readonly TColor LightPink = ColorBuilder.FromRGBA(255, 182, 193, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA07A. + /// + public static readonly TColor LightSalmon = ColorBuilder.FromRGBA(255, 160, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #20B2AA. + /// + public static readonly TColor LightSeaGreen = ColorBuilder.FromRGBA(32, 178, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEFA. + /// + public static readonly TColor LightSkyBlue = ColorBuilder.FromRGBA(135, 206, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly TColor LightSlateGray = ColorBuilder.FromRGBA(119, 136, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0C4DE. + /// + public static readonly TColor LightSteelBlue = ColorBuilder.FromRGBA(176, 196, 222, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. + /// + public static readonly TColor LightYellow = ColorBuilder.FromRGBA(255, 255, 224, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly TColor Lime = ColorBuilder.FromRGBA(0, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #32CD32. + /// + public static readonly TColor LimeGreen = ColorBuilder.FromRGBA(50, 205, 50, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. + /// + public static readonly TColor Linen = ColorBuilder.FromRGBA(250, 240, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly TColor Magenta = ColorBuilder.FromRGBA(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800000. + /// + public static readonly TColor Maroon = ColorBuilder.FromRGBA(128, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #66CDAA. + /// + public static readonly TColor MediumAquamarine = ColorBuilder.FromRGBA(102, 205, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000CD. + /// + public static readonly TColor MediumBlue = ColorBuilder.FromRGBA(0, 0, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BA55D3. + /// + public static readonly TColor MediumOrchid = ColorBuilder.FromRGBA(186, 85, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9370DB. + /// + public static readonly TColor MediumPurple = ColorBuilder.FromRGBA(147, 112, 219, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #3CB371. + /// + public static readonly TColor MediumSeaGreen = ColorBuilder.FromRGBA(60, 179, 113, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7B68EE. + /// + public static readonly TColor MediumSlateBlue = ColorBuilder.FromRGBA(123, 104, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FA9A. + /// + public static readonly TColor MediumSpringGreen = ColorBuilder.FromRGBA(0, 250, 154, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #48D1CC. + /// + public static readonly TColor MediumTurquoise = ColorBuilder.FromRGBA(72, 209, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C71585. + /// + public static readonly TColor MediumVioletRed = ColorBuilder.FromRGBA(199, 21, 133, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #191970. + /// + public static readonly TColor MidnightBlue = ColorBuilder.FromRGBA(25, 25, 112, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. + /// + public static readonly TColor MintCream = ColorBuilder.FromRGBA(245, 255, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. + /// + public static readonly TColor MistyRose = ColorBuilder.FromRGBA(255, 228, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. + /// + public static readonly TColor Moccasin = ColorBuilder.FromRGBA(255, 228, 181, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. + /// + public static readonly TColor NavajoWhite = ColorBuilder.FromRGBA(255, 222, 173, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly TColor Navy = ColorBuilder.FromRGBA(0, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. + /// + public static readonly TColor OldLace = ColorBuilder.FromRGBA(253, 245, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly TColor Olive = ColorBuilder.FromRGBA(128, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6B8E23. + /// + public static readonly TColor OliveDrab = ColorBuilder.FromRGBA(107, 142, 35, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA500. + /// + public static readonly TColor Orange = ColorBuilder.FromRGBA(255, 165, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF4500. + /// + public static readonly TColor OrangeRed = ColorBuilder.FromRGBA(255, 69, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DA70D6. + /// + public static readonly TColor Orchid = ColorBuilder.FromRGBA(218, 112, 214, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. + /// + public static readonly TColor PaleGoldenrod = ColorBuilder.FromRGBA(238, 232, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #98FB98. + /// + public static readonly TColor PaleGreen = ColorBuilder.FromRGBA(152, 251, 152, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. + /// + public static readonly TColor PaleTurquoise = ColorBuilder.FromRGBA(175, 238, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DB7093. + /// + public static readonly TColor PaleVioletRed = ColorBuilder.FromRGBA(219, 112, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. + /// + public static readonly TColor PapayaWhip = ColorBuilder.FromRGBA(255, 239, 213, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. + /// + public static readonly TColor PeachPuff = ColorBuilder.FromRGBA(255, 218, 185, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD853F. + /// + public static readonly TColor Peru = ColorBuilder.FromRGBA(205, 133, 63, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. + /// + public static readonly TColor Pink = ColorBuilder.FromRGBA(255, 192, 203, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. + /// + public static readonly TColor Plum = ColorBuilder.FromRGBA(221, 160, 221, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. + /// + public static readonly TColor PowderBlue = ColorBuilder.FromRGBA(176, 224, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly TColor Purple = ColorBuilder.FromRGBA(128, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #663399. + /// + public static readonly TColor RebeccaPurple = ColorBuilder.FromRGBA(102, 51, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly TColor Red = ColorBuilder.FromRGBA(255, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. + /// + public static readonly TColor RosyBrown = ColorBuilder.FromRGBA(188, 143, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4169E1. + /// + public static readonly TColor RoyalBlue = ColorBuilder.FromRGBA(65, 105, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B4513. + /// + public static readonly TColor SaddleBrown = ColorBuilder.FromRGBA(139, 69, 19, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FA8072. + /// + public static readonly TColor Salmon = ColorBuilder.FromRGBA(250, 128, 114, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F4A460. + /// + public static readonly TColor SandyBrown = ColorBuilder.FromRGBA(244, 164, 96, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2E8B57. + /// + public static readonly TColor SeaGreen = ColorBuilder.FromRGBA(46, 139, 87, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. + /// + public static readonly TColor SeaShell = ColorBuilder.FromRGBA(255, 245, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A0522D. + /// + public static readonly TColor Sienna = ColorBuilder.FromRGBA(160, 82, 45, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly TColor Silver = ColorBuilder.FromRGBA(192, 192, 192, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEEB. + /// + public static readonly TColor SkyBlue = ColorBuilder.FromRGBA(135, 206, 235, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. + /// + public static readonly TColor SlateBlue = ColorBuilder.FromRGBA(106, 90, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly TColor SlateGray = ColorBuilder.FromRGBA(112, 128, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAFA. + /// + public static readonly TColor Snow = ColorBuilder.FromRGBA(255, 250, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF7F. + /// + public static readonly TColor SpringGreen = ColorBuilder.FromRGBA(0, 255, 127, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4682B4. + /// + public static readonly TColor SteelBlue = ColorBuilder.FromRGBA(70, 130, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2B48C. + /// + public static readonly TColor Tan = ColorBuilder.FromRGBA(210, 180, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly TColor Teal = ColorBuilder.FromRGBA(0, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. + /// + public static readonly TColor Thistle = ColorBuilder.FromRGBA(216, 191, 216, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF6347. + /// + public static readonly TColor Tomato = ColorBuilder.FromRGBA(255, 99, 71, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly TColor Transparent = ColorBuilder.FromRGBA(255, 255, 255, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #40E0D0. + /// + public static readonly TColor Turquoise = ColorBuilder.FromRGBA(64, 224, 208, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EE82EE. + /// + public static readonly TColor Violet = ColorBuilder.FromRGBA(238, 130, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. + /// + public static readonly TColor Wheat = ColorBuilder.FromRGBA(245, 222, 179, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly TColor White = ColorBuilder.FromRGBA(255, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. + /// + public static readonly TColor WhiteSmoke = ColorBuilder.FromRGBA(245, 245, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly TColor Yellow = ColorBuilder.FromRGBA(255, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9ACD32. + /// + public static readonly TColor YellowGreen = ColorBuilder.FromRGBA(154, 205, 50, 255); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 361fe5b9e..9a340544c 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// - /// Packed pixel type containing a single 8 bit normalized W values that is ranging from 0 to 1. + /// Packed pixel type containing a single 8 bit normalized W values ranging from 0 to 1. /// - public struct Alpha8 : IPackedPixel, IEquatable + public struct Alpha8 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -37,6 +38,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Alpha8 left, Alpha8 right) { return left.PackedValue == right.PackedValue; @@ -50,30 +52,38 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Alpha8 left, Alpha8 right) { return left.PackedValue != right.PackedValue; } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(0, 0, 0, this.PackedValue / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackedValue = w; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { bytes[startIndex] = 0; @@ -82,6 +92,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = 0; @@ -91,6 +102,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { bytes[startIndex] = 0; @@ -99,6 +111,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = 0; @@ -122,6 +135,7 @@ namespace ImageSharp /// /// The Alpha8 packed vector to compare. /// True if the packed vectors are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Alpha8 other) { return this.PackedValue == other.PackedValue; @@ -137,6 +151,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -147,6 +162,7 @@ namespace ImageSharp /// /// The float containing the value to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte Pack(float alpha) { return (byte)Math.Round(alpha.Clamp(0, 1) * 255F); diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index 8ab8e3f43..70fd7de8a 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. @@ -16,7 +17,7 @@ namespace ImageSharp /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, /// as it avoids the need to create new values for modification operations. /// - public struct Argb : IPackedPixel, IEquatable + public struct Argb : IPixel, IPackedVector { /// /// The shift count for the blue component @@ -113,11 +114,13 @@ namespace ImageSharp /// public byte R { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.PackedValue >> RedShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.PackedValue = this.PackedValue & 0xFF00FFFF | (uint)value << RedShift; @@ -129,11 +132,13 @@ namespace ImageSharp /// public byte G { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.PackedValue >> GreenShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.PackedValue = this.PackedValue & 0xFFFF00FF | (uint)value << GreenShift; @@ -145,11 +150,13 @@ namespace ImageSharp /// public byte B { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.PackedValue >> BlueShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.PackedValue = this.PackedValue & 0xFFFFFF00 | (uint)value << BlueShift; @@ -161,11 +168,13 @@ namespace ImageSharp /// public byte A { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.PackedValue >> AlphaShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.PackedValue = this.PackedValue & 0x00FFFFFF | (uint)value << AlphaShift; @@ -184,6 +193,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Argb left, Argb right) { return left.PackedValue == right.PackedValue; @@ -197,30 +207,38 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Argb left, Argb right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(ref vector); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackedValue = Pack(x, y, z, w); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.R; @@ -229,6 +247,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.R; @@ -238,6 +257,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.B; @@ -246,6 +266,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.B; @@ -261,12 +282,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Argb other) { return this.PackedValue == other.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { // ReSharper disable once NonReadonlyMemberInGetHashCode @@ -281,6 +304,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { var value = new Vector4(x, y, z, w); @@ -295,6 +319,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(byte x, byte y, byte z, byte w) { return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); @@ -305,6 +330,7 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector3 vector) { var value = new Vector4(vector, 1); @@ -316,11 +342,12 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); vector *= MaxBytes; vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); return (uint)(((byte)vector.X << RedShift) | ((byte)vector.Y << GreenShift) | ((byte)vector.Z << BlueShift) diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 1f1ce0a17..77d943478 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x and z components use 5 bits, and the y component uses 6 bits. /// - public struct Bgr565 : IPackedPixel, IEquatable + public struct Bgr565 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -35,7 +36,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z); } - /// + /// public ushort PackedValue { get; set; } /// @@ -46,6 +47,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Bgr565 left, Bgr565 right) { return left.PackedValue == right.PackedValue; @@ -59,16 +61,21 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Bgr565 left, Bgr565 right) { return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 ToVector3() { return new Vector3( @@ -78,24 +85,28 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector3(), 1F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -105,6 +116,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -115,6 +127,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -124,6 +137,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -140,6 +154,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Bgr565 other) { return this.PackedValue == other.PackedValue; @@ -152,6 +167,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -164,6 +180,7 @@ namespace ImageSharp /// The y-component /// The z-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y, float z) { return (ushort)((((int)Math.Round(x.Clamp(0, 1) * 31F) & 0x1F) << 11) | diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs index 1f33cb791..1346a54ef 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. /// - public struct Bgra4444 : IPackedPixel, IEquatable + public struct Bgra4444 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -34,7 +35,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// + /// public ushort PackedValue { get; set; } /// @@ -45,6 +46,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Bgra4444 left, Bgra4444 right) { return left.PackedValue == right.PackedValue; @@ -58,12 +60,17 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Bgra4444 left, Bgra4444 right) { return left.PackedValue != right.PackedValue; } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { const float Max = 1 / 15F; @@ -76,18 +83,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -97,6 +107,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -107,6 +118,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -116,6 +128,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -132,6 +145,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Bgra4444 other) { return this.PackedValue == other.PackedValue; @@ -144,6 +158,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -157,6 +172,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y, float z, float w) { return (ushort)((((int)Math.Round(w.Clamp(0, 1) * 15F) & 0x0F) << 12) | diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index d0c52068d..7989804cf 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x , y and z components use 5 bits, and the w component uses 1 bit. /// - public struct Bgra5551 : IPackedPixel, IEquatable + public struct Bgra5551 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -36,7 +37,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// + /// public ushort PackedValue { get; set; } /// @@ -47,6 +48,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Bgra5551 left, Bgra5551 right) { return left.PackedValue == right.PackedValue; @@ -60,12 +62,17 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Bgra5551 left, Bgra5551 right) { return left.PackedValue != right.PackedValue; } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -76,18 +83,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -97,6 +107,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -107,6 +118,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -116,6 +128,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -132,6 +145,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Bgra5551 other) { return this.PackedValue == other.PackedValue; @@ -150,6 +164,7 @@ namespace ImageSharp /// Gets a hash code of the packed vector. /// /// The hash code for the packed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -163,6 +178,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y, float z, float w) { return (ushort)( diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs new file mode 100644 index 000000000..259b1c9b4 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -0,0 +1,256 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations + /// for pixel buffers of type . + /// + /// The pixel format. + public unsafe class BulkPixelOperations + where TColor : struct, IPixel + { + /// + /// The size of in bytes + /// + private static readonly int ColorSize = Unsafe.SizeOf(); + + /// + /// Gets the global instance for the pixel type + /// + public static BulkPixelOperations Instance { get; } = default(TColor).CreateBulkOperations(); + + /// + /// Bulk version of + /// + /// The to the source vectors. + /// The to the destination colors. + /// The number of pixels to convert. + internal virtual void PackFromVector4( + BufferPointer sourceVectors, + BufferPointer destColors, + int count) + { + Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; + byte* dp = (byte*)destColors; + + for (int i = 0; i < count; i++) + { + Vector4 v = Unsafe.Read(sp); + TColor c = default(TColor); + c.PackFromVector4(v); + Unsafe.Write(dp, c); + + sp++; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// The to the source colors. + /// The to the destination vectors. + /// The number of pixels to convert. + internal virtual void ToVector4( + BufferPointer sourceColors, + BufferPointer destVectors, + int count) + { + byte* sp = (byte*)sourceColors; + Vector4* dp = (Vector4*)destVectors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = Unsafe.Read(sp); + *dp = c.ToVector4(); + sp += ColorSize; + dp++; + } + } + + /// + /// Bulk version of that converts data in . + /// + /// The to the source bytes. + /// The to the destination colors. + /// The number of pixels to convert. + internal virtual void PackFromXyzBytes( + BufferPointer sourceBytes, + BufferPointer destColors, + int count) + { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[0], sp[1], sp[2], 255); + Unsafe.Write(dp, c); + sp += 3; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. + internal virtual void ToXyzBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) + { + TColor c = Unsafe.Read(sp); + c.ToXyzBytes(dest, i); + sp += ColorSize; + } + } + + /// + /// Bulk version of that converts data in . + /// + /// The to the source bytes. + /// The to the destination colors. + /// The number of pixels to convert. + internal virtual void PackFromXyzwBytes( + BufferPointer sourceBytes, + BufferPointer destColors, + int count) + { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]); + Unsafe.Write(dp, c); + sp += 4; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. + internal virtual void ToXyzwBytes( + BufferPointer sourceColors, + BufferPointer destBytes, + int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) + { + TColor c = Unsafe.Read(sp); + c.ToXyzwBytes(dest, i); + sp += ColorSize; + } + } + + /// + /// Bulk version of that converts data in . + /// + /// The to the source bytes. + /// The to the destination colors. + /// The number of pixels to convert. + internal virtual void PackFromZyxBytes( + BufferPointer sourceBytes, + BufferPointer destColors, + int count) + { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[2], sp[1], sp[0], 255); + Unsafe.Write(dp, c); + sp += 3; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. + internal virtual void ToZyxBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) + { + TColor c = Unsafe.Read(sp); + c.ToZyxBytes(dest, i); + sp += ColorSize; + } + } + + /// + /// Bulk version of that converts data in . + /// + /// The to the source bytes. + /// The to the destination colors. + /// The number of pixels to convert. + internal virtual void PackFromZyxwBytes( + BufferPointer sourceBytes, + BufferPointer destColors, + int count) + { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]); + Unsafe.Write(dp, c); + sp += 4; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. + internal virtual void ToZyxwBytes( + BufferPointer sourceColors, + BufferPointer destBytes, + int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) + { + TColor c = Unsafe.Read(sp); + c.ToZyxwBytes(dest, i); + sp += ColorSize; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index 69c69ecaf..11ec5eaf4 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. /// - public struct Byte4 : IPackedPixel, IEquatable + public struct Byte4 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -37,7 +38,7 @@ namespace ImageSharp this.PackedValue = Pack(ref vector); } - /// + /// public uint PackedValue { get; set; } /// @@ -48,6 +49,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Byte4 left, Byte4 right) { return left.PackedValue == right.PackedValue; @@ -61,24 +63,24 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Byte4 left, Byte4 right) { return left.PackedValue != right.PackedValue; } - /// - /// Sets the packed representation from a Vector4. - /// - /// The vector to create the packed representation from. + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(ref vector); } - /// - /// Expands the packed representation into a Vector4. - /// - /// The expanded vector. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -89,12 +91,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w)); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -104,6 +108,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -114,6 +119,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -123,6 +129,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -139,12 +146,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Byte4 other) { return this == other; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -164,12 +173,14 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector4 vector) { const float Max = 255F; const float Min = 0F; // Clamp the value between min and max values + // TODO: Use Vector4.Clamp() here! uint byte4 = (uint)Math.Round(vector.X.Clamp(Min, Max)) & 0xFF; uint byte3 = ((uint)Math.Round(vector.Y.Clamp(Min, Max)) & 0xFF) << 0x8; uint byte2 = ((uint)Math.Round(vector.Z.Clamp(Min, Max)) & 0xFF) << 0x10; diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index 22e32aa56..4c785a863 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing a single 16 bit floating point value. /// - public struct HalfSingle : IPackedPixel, IEquatable + public struct HalfSingle : IPixel, IPackedVector { /// /// The maximum byte value. @@ -32,7 +33,7 @@ namespace ImageSharp this.PackedValue = HalfTypeHelper.Pack(single); } - /// + /// public ushort PackedValue { get; set; } /// @@ -47,6 +48,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(HalfSingle left, HalfSingle right) { return left.PackedValue == right.PackedValue; @@ -64,39 +66,48 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(HalfSingle left, HalfSingle right) { return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float ToSingle() { return HalfTypeHelper.Unpack(this.PackedValue); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = HalfTypeHelper.Pack(vector.X); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToSingle(), 0, 0, 1); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -110,6 +121,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -124,6 +136,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -137,6 +150,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -157,6 +171,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(HalfSingle other) { return this.PackedValue == other.PackedValue; @@ -169,6 +184,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); diff --git a/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs b/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs index d6d680ed4..a19b51323 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// @@ -17,6 +18,7 @@ namespace ImageSharp /// /// The float to pack /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ushort Pack(float value) { Uif uif = new Uif { F = value }; diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index 7c17dcea8..d06ab6ba0 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit floating-point values. /// - public struct HalfVector2 : IPackedPixel, IEquatable + public struct HalfVector2 : IPixel, IPackedVector { /// /// The maximum byte value. @@ -42,7 +43,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y); } - /// + /// public uint PackedValue { get; set; } /// @@ -57,6 +58,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(HalfVector2 left, HalfVector2 right) { return left.Equals(right); @@ -74,15 +76,20 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(HalfVector2 left, HalfVector2 right) { return !left.Equals(right); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { Vector2 vector; @@ -92,12 +99,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { Vector2 vector = this.ToVector2(); @@ -105,12 +114,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -124,6 +135,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -138,6 +150,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -151,6 +164,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -171,6 +185,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -183,6 +198,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(HalfVector2 other) { return this.PackedValue.Equals(other.PackedValue); @@ -194,6 +210,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y) { uint num2 = HalfTypeHelper.Pack(x); diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs index a7f10fc71..a5fa796e1 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit floating-point values. /// - public struct HalfVector4 : IPackedPixel, IEquatable + public struct HalfVector4 : IPixel, IPackedVector { /// /// The maximum byte value. @@ -45,7 +46,7 @@ namespace ImageSharp this.PackedValue = Pack(ref vector); } - /// + /// public ulong PackedValue { get; set; } /// @@ -60,6 +61,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(HalfVector4 left, HalfVector4 right) { return left.Equals(right); @@ -77,18 +79,24 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(HalfVector4 left, HalfVector4 right) { return !left.Equals(right); } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(ref vector); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -99,12 +107,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -118,6 +128,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -132,6 +143,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -145,6 +157,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -165,6 +178,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -177,6 +191,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(HalfVector4 other) { return this.PackedValue.Equals(other.PackedValue); @@ -187,6 +202,7 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Pack(ref Vector4 vector) { ulong num4 = HalfTypeHelper.Pack(vector.X); diff --git a/src/ImageSharp/Colors/PackedPixel/IPackedPixel.cs b/src/ImageSharp/Colors/PackedPixel/IPackedPixel.cs deleted file mode 100644 index f9b8dc0a2..000000000 --- a/src/ImageSharp/Colors/PackedPixel/IPackedPixel.cs +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - /// - /// An interface that represents a generic packed pixel type. - /// - /// The packed format. uint, long, float. - public interface IPackedPixel : IPackedPixel, IPackedVector - where TPacked : struct, IEquatable - { - } - - /// - /// An interface that represents a packed pixel type. - /// - public interface IPackedPixel : IPackedVector, IPackedBytes - { - } -} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/IPackedVector.cs b/src/ImageSharp/Colors/PackedPixel/IPackedVector.cs deleted file mode 100644 index 4d9a89712..000000000 --- a/src/ImageSharp/Colors/PackedPixel/IPackedVector.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Numerics; - - /// - /// An interface that converts packed vector types to and from values, - /// allowing multiple encodings to be manipulated in a generic manner. - /// - /// The packed format. uint, long, float. - public interface IPackedVector : IPackedVector - where TPacked : struct, IEquatable - { - /// - /// Gets or sets the packed representation of the value. - /// - TPacked PackedValue { get; set; } - } - - /// - /// An interface that converts packed vector types to and from values. - /// - public interface IPackedVector - { - /// - /// Sets the packed representation from a . - /// - /// The vector to create the packed representation from. - void PackFromVector4(Vector4 vector); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - Vector4 ToVector4(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/IPackedVector{TPacked}.cs b/src/ImageSharp/Colors/PackedPixel/IPackedVector{TPacked}.cs new file mode 100644 index 000000000..0450fb8fb --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/IPackedVector{TPacked}.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// This interface exists for ensuring signature compatibility to MonoGame and XNA packed color types. + /// + /// + /// The packed format. uint, long, float. + public interface IPackedVector : IPixel + where TPacked : struct, IEquatable + { + /// + /// Gets or sets the packed representation of the value. + /// + TPacked PackedValue { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/IPackedBytes.cs b/src/ImageSharp/Colors/PackedPixel/IPixel.cs similarity index 61% rename from src/ImageSharp/Colors/PackedPixel/IPackedBytes.cs rename to src/ImageSharp/Colors/PackedPixel/IPixel.cs index 3343a92c7..67e013a42 100644 --- a/src/ImageSharp/Colors/PackedPixel/IPackedBytes.cs +++ b/src/ImageSharp/Colors/PackedPixel/IPixel.cs @@ -1,16 +1,46 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp { + using System; + using System.Numerics; + + /// + /// An interface that represents a generic pixel type. + /// + /// The type implementing this interface + public interface IPixel : IPixel, IEquatable + where TSelf : struct, IPixel + { + /// + /// Creates a instance for this pixel type. + /// This method is not intended to be consumed directly. Use instead. + /// + /// The instance. + BulkPixelOperations CreateBulkOperations(); + } + /// - /// An interface that converts packed vector types to and from values, - /// allowing multiple encodings to be manipulated in a generic manner. + /// An interface that represents a pixel type. /// - public interface IPackedBytes + public interface IPixel { + /// + /// Sets the packed representation from a . + /// + /// The vector to create the packed representation from. + void PackFromVector4(Vector4 vector); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToVector4(); + /// /// Sets the packed representation from the given byte array. /// @@ -52,4 +82,4 @@ namespace ImageSharp /// The starting index of the . void ToZyxwBytes(byte[] bytes, int startIndex); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index f1dae1066..56be64a86 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedByte2 : IPackedPixel, IEquatable + public struct NormalizedByte2 : IPixel, IPackedVector { /// /// The maximum byte value. @@ -47,7 +48,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// + /// public ushort PackedValue { get; set; } /// @@ -62,6 +63,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NormalizedByte2 left, NormalizedByte2 right) { return left.PackedValue == right.PackedValue; @@ -79,16 +81,21 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) { return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { return new Vector2( @@ -97,18 +104,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector2(), 0F, 1F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w); @@ -120,6 +130,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -135,6 +146,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -151,6 +163,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -166,6 +179,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -188,12 +202,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(NormalizedByte2 other) { return this.PackedValue == other.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -211,6 +227,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y) { int byte2 = ((ushort)Math.Round(x.Clamp(-1F, 1F) * 127F) & 0xFF) << 0; diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index 22eba5182..a1f9b8d84 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedByte4 : IPackedPixel, IEquatable + public struct NormalizedByte4 : IPixel, IPackedVector { /// /// The maximum byte value. @@ -49,7 +50,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// + /// public uint PackedValue { get; set; } /// @@ -64,6 +65,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NormalizedByte4 left, NormalizedByte4 right) { return left.PackedValue == right.PackedValue; @@ -81,18 +83,24 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) { return left.PackedValue != right.PackedValue; } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -103,6 +111,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w); @@ -114,6 +123,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -129,6 +139,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -145,6 +156,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -160,6 +172,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -182,12 +195,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(NormalizedByte4 other) { return this.PackedValue == other.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -207,6 +222,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { uint byte4 = ((uint)Math.Round(x.Clamp(-1F, 1F) * 127F) & 0xFF) << 0; diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index 34b2fc320..b34c1e88b 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedShort2 : IPackedPixel, IEquatable + public struct NormalizedShort2 : IPixel, IPackedVector { /// /// The maximum byte value. @@ -47,7 +48,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// + /// public uint PackedValue { get; set; } /// @@ -62,6 +63,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NormalizedShort2 left, NormalizedShort2 right) { return left.Equals(right); @@ -79,24 +81,31 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) { return !left.Equals(right); } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector2(), 0, 1); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w); @@ -108,6 +117,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -123,6 +133,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -139,6 +150,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -154,6 +166,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -174,6 +187,7 @@ namespace ImageSharp /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { const float MaxVal = 0x7FFF; @@ -190,12 +204,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(NormalizedShort2 other) { return this.PackedValue.Equals(other.PackedValue); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -213,6 +229,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y) { const float MaxPos = 0x7FFF; diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index 9742a5f34..f33ac25a6 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedShort4 : IPackedPixel, IEquatable + public struct NormalizedShort4 : IPixel, IPackedVector { /// /// The maximum byte value. @@ -49,7 +50,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// + /// public ulong PackedValue { get; set; } /// @@ -64,6 +65,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NormalizedShort4 left, NormalizedShort4 right) { return left.Equals(right); @@ -81,18 +83,24 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) { return !left.Equals(right); } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { const float MaxVal = 0x7FFF; @@ -105,6 +113,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w); @@ -116,6 +125,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -131,6 +141,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -147,6 +158,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -162,6 +174,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -184,12 +197,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(NormalizedShort4 other) { return this.PackedValue.Equals(other.PackedValue); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -209,6 +224,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Pack(float x, float y, float z, float w) { const float MaxPos = 0x7FFF; diff --git a/src/ImageSharp/Colors/PackedPixel/Rg32.cs b/src/ImageSharp/Colors/PackedPixel/Rg32.cs index d885a4470..f8486f7f2 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rg32.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rg32.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. /// - public struct Rg32 : IPackedPixel, IEquatable, IPackedVector + public struct Rg32 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -32,7 +33,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y); } - /// + /// public uint PackedValue { get; set; } /// @@ -47,6 +48,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rg32 left, Rg32 right) { return left.PackedValue == right.PackedValue; @@ -64,16 +66,21 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rg32 left, Rg32 right) { return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { return new Vector2( @@ -82,24 +89,28 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector2(), 0F, 1F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -110,6 +121,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -121,6 +133,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -131,6 +144,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -148,6 +162,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rg32 other) { return this.PackedValue == other.PackedValue; @@ -160,6 +175,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -171,6 +187,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y) { return (uint)( diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs index da7dbe1ee..56f304070 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -7,12 +7,13 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed vector type containing unsigned normalized values ranging from 0 to 1. /// The x, y and z components use 10 bits, and the w component uses 2 bits. /// - public struct Rgba1010102 : IPackedPixel, IEquatable, IPackedVector + public struct Rgba1010102 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -35,7 +36,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// + /// public uint PackedValue { get; set; } /// @@ -50,6 +51,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rgba1010102 left, Rgba1010102 right) { return left.PackedValue == right.PackedValue; @@ -67,12 +69,17 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rgba1010102 left, Rgba1010102 right) { return left.PackedValue != right.PackedValue; } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -83,18 +90,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -105,6 +115,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -116,6 +127,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -126,6 +138,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -143,6 +156,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rgba1010102 other) { return this.PackedValue == other.PackedValue; @@ -155,6 +169,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -168,6 +183,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { return (uint)( diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs index 64631f1e1..816401d4e 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 1. /// - public struct Rgba64 : IPackedPixel, IEquatable, IPackedVector + public struct Rgba64 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -34,7 +35,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// + /// public ulong PackedValue { get; set; } /// @@ -49,6 +50,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rgba64 left, Rgba64 right) { return left.PackedValue == right.PackedValue; @@ -66,12 +68,17 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rgba64 left, Rgba64 right) { return left.PackedValue != right.PackedValue; } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -82,18 +89,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -104,6 +114,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -115,6 +126,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -125,6 +137,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -142,6 +155,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rgba64 other) { return this.PackedValue == other.PackedValue; @@ -154,6 +168,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -167,6 +182,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Pack(float x, float y, float z, float w) { return (ulong)Math.Round(x.Clamp(0, 1) * 65535F) | diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index d45a80fcb..802df7c1d 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit signed integer values. /// - public struct Short2 : IPackedPixel, IEquatable + public struct Short2 : IPixel, IPackedVector { /// /// The maximum byte value. @@ -47,7 +48,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// + /// public uint PackedValue { get; set; } /// @@ -62,6 +63,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Short2 left, Short2 right) { return left.PackedValue == right.PackedValue; @@ -79,24 +81,31 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Short2 left, Short2 right) { return left.PackedValue != right.PackedValue; } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector2 vector = new Vector2(x, y) / 255; @@ -106,6 +115,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector2 vector = this.ToVector2(); @@ -121,6 +131,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector2 vector = this.ToVector2(); @@ -137,6 +148,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector2 vector = this.ToVector2(); @@ -152,6 +164,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector2 vector = this.ToVector2(); @@ -172,6 +185,7 @@ namespace ImageSharp /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { return new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); @@ -184,12 +198,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Short2 other) { return this == other; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -207,6 +223,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y) { // Largest two byte positive number 0xFFFF >> 1; diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs index cd112a90f..2517ef7a8 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -7,11 +7,12 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit signed integer values. /// - public struct Short4 : IPackedPixel, IEquatable + public struct Short4 : IPixel, IPackedVector { /// /// The maximum byte value. @@ -49,7 +50,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// + /// public ulong PackedValue { get; set; } /// @@ -64,6 +65,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Short4 left, Short4 right) { return left.PackedValue == right.PackedValue; @@ -81,18 +83,24 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Short4 left, Short4 right) { return left.PackedValue != right.PackedValue; } /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -103,6 +111,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w) / 255; @@ -112,6 +121,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -127,6 +137,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -143,6 +154,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -158,6 +170,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -180,6 +193,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Short4 other) { return this == other; @@ -189,6 +203,7 @@ namespace ImageSharp /// Gets the hash code for the current instance. /// /// Hash code for the instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -211,6 +226,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Pack(float x, float y, float z, float w) { // Largest two byte positive number 0xFFFF >> 1; diff --git a/src/ImageSharp/Common/Extensions/ArrayExtensions.cs b/src/ImageSharp/Common/Extensions/ArrayExtensions.cs deleted file mode 100644 index be3d3f1dc..000000000 --- a/src/ImageSharp/Common/Extensions/ArrayExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - /// - /// Extension methods for arrays. - /// - public static class ArrayExtensions - { - /// - /// Locks the pixel buffer providing access to the pixels. - /// - /// The pixel format. - /// The pixel buffer. - /// Gets the width of the image represented by the pixel buffer. - /// The height of the image represented by the pixel buffer. - /// The - public static PixelAccessor Lock(this TColor[] pixels, int width, int height) - where TColor : struct, IPackedPixel, IEquatable - { - return new PixelAccessor(width, height, pixels); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs new file mode 100644 index 000000000..0ca6f0912 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + + /// + /// Provides methods to protect against invalid parameters for a DEBUG build. + /// + [DebuggerStepThrough] + internal static class DebugGuard + { + /// + /// Verifies, that the method parameter with specified object value is not null + /// and throws an exception if it is found to be so. + /// + /// The target object, which cannot be null. + /// The name of the parameter that is to be checked. + /// is null + [Conditional("DEBUG")] + public static void NotNull(object target, string parameterName) + { + if (target == null) + { + throw new ArgumentNullException(parameterName); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index fc94c689f..b59cf54f5 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -37,6 +37,7 @@ namespace ImageSharp /// /// The /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetBitsNeededForColorDepth(int colors) { return (int)Math.Ceiling(Math.Log(colors, 2)); @@ -48,6 +49,7 @@ namespace ImageSharp /// The x provided to G(x). /// The spread of the blur. /// The Gaussian G(x) + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Gaussian(float x, float sigma) { const float Numerator = 1.0f; @@ -72,6 +74,7 @@ namespace ImageSharp /// /// The . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetBcValue(float x, float b, float c) { float temp; @@ -104,6 +107,7 @@ namespace ImageSharp /// /// The . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SinC(float x) { if (Math.Abs(x) > Constants.Epsilon) @@ -122,6 +126,7 @@ namespace ImageSharp /// /// The representing the degree as radians. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float DegreesToRadians(float degrees) { return degrees * (float)(Math.PI / 180); @@ -139,6 +144,7 @@ namespace ImageSharp /// /// The bounding . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) { return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); @@ -177,7 +183,7 @@ namespace ImageSharp /// The . /// public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = bitmap.Width; int height = bitmap.Height; diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs new file mode 100644 index 000000000..c80e22e21 --- /dev/null +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -0,0 +1,91 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Runtime.CompilerServices; + + /// + /// Utility methods for + /// + internal static class BufferPointer + { + /// + /// Gets a to the beginning of the raw data in 'buffer'. + /// + /// The element type + /// The input + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe BufferPointer Slice(this PinnedBuffer buffer) + where T : struct + { + return new BufferPointer(buffer.Array, (void*)buffer.Pointer); + } + + /// + /// Copy 'count' number of elements of the same type from 'source' to 'dest' + /// + /// The element type. + /// The input + /// The destination . + /// The number of elements to copy + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) + where T : struct + { + Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(count)); + } + + /// + /// Copy 'countInSource' elements of from 'source' into the raw byte buffer 'destination'. + /// + /// The element type. + /// The source buffer of elements to copy from. + /// The destination buffer. + /// The number of elements to copy from 'source' + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) + where T : struct + { + Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInSource)); + } + + /// + /// Copy 'countInDest' number of elements into 'dest' from a raw byte buffer defined by 'source'. + /// + /// The element type. + /// The raw source buffer to copy from"/> + /// The destination buffer"/> + /// The number of elements to copy. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInDest) + where T : struct + { + Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInDest)); + } + + /// + /// Gets the size of `count` elements in bytes. + /// + /// The element type. + /// The count of the elements + /// The size in bytes as int + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int SizeOf(int count) + where T : struct => Unsafe.SizeOf() * count; + + /// + /// Gets the size of `count` elements in bytes as UInt32 + /// + /// The element type. + /// The count of the elements + /// The size in bytes as UInt32 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint USizeOf(int count) + where T : struct + => (uint)SizeOf(count); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs new file mode 100644 index 000000000..fff4e513e --- /dev/null +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -0,0 +1,106 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Provides access to elements in an array from a given position. + /// This type shares many similarities with corefx System.Span<T> but there are significant differences in it's functionalities and semantics: + /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays + /// - It's possible to retrieve a reference to the array () so we can pass it to API-s like + /// - There is no bounds checking for performance reasons. Therefore we don't need to store length. (However this could be added as DEBUG-only feature.) + /// This makes an unsafe type! + /// - Currently the arrays provided to BufferPointer need to be pinned. This behaviour could be changed using C#7 features. + /// + /// The type of elements of the array + internal unsafe struct BufferPointer + where T : struct + { + /// + /// Initializes a new instance of the struct from a pinned array and an offset. + /// + /// The pinned array + /// Pointer to the beginning of array + /// The offset inside the array + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferPointer(T[] array, void* pointerToArray, int offset) + { + DebugGuard.NotNull(array, nameof(array)); + + this.Array = array; + this.Offset = offset; + this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * offset); + } + + /// + /// Initializes a new instance of the struct from a pinned array. + /// + /// The pinned array + /// Pointer to the start of 'array' + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferPointer(T[] array, void* pointerToArray) + { + DebugGuard.NotNull(array, nameof(array)); + + this.Array = array; + this.Offset = 0; + this.PointerAtOffset = (IntPtr)pointerToArray; + } + + /// + /// Gets the array + /// + public T[] Array { get; private set; } + + /// + /// Gets the offset inside + /// + public int Offset { get; private set; } + + /// + /// Gets the pointer to the offseted array position + /// + public IntPtr PointerAtOffset { get; private set; } + + /// + /// Convertes instance to a raw 'void*' pointer + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator void*(BufferPointer bufferPointer) + { + return (void*)bufferPointer.PointerAtOffset; + } + + /// + /// Convertes instance to a raw 'byte*' pointer + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator byte*(BufferPointer bufferPointer) + { + return (byte*)bufferPointer.PointerAtOffset; + } + + /// + /// Forms a slice out of the given BufferPointer, beginning at 'offset'. + /// + /// The offset in number of elements + /// The offseted (sliced) BufferPointer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferPointer Slice(int offset) + { + BufferPointer result = default(BufferPointer); + result.Array = this.Array; + result.Offset = this.Offset + offset; + result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs b/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs new file mode 100644 index 000000000..3455031fd --- /dev/null +++ b/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs @@ -0,0 +1,131 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + + /// + /// Provides fast access to 2D arrays. + /// + /// The type of elements in the array. + public struct Fast2DArray + { + /// + /// The 1D representation of the 2D array. + /// + public T[] Data; + + /// + /// Gets the width of the 2D array. + /// + public int Width; + + /// + /// Gets the height of the 2D array. + /// + public int Height; + + /// + /// Initializes a new instance of the struct. + /// + /// The width. + /// The height. + public Fast2DArray(int width, int height) + { + this.Height = height; + this.Width = width; + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Data = new T[this.Width * this.Height]; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The 2D array to provide access to. + public Fast2DArray(T[,] data) + { + Guard.NotNull(data, nameof(data)); + this.Height = data.GetLength(0); + this.Width = data.GetLength(1); + + Guard.MustBeGreaterThan(this.Width, 0, nameof(this.Width)); + Guard.MustBeGreaterThan(this.Height, 0, nameof(this.Height)); + + this.Data = new T[this.Width * this.Height]; + + for (int y = 0; y < this.Height; y++) + { + for (int x = 0; x < this.Width; x++) + { + this.Data[(y * this.Width) + x] = data[y, x]; + } + } + } + + /// + /// Gets or sets the item at the specified position. + /// + /// The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array. + /// The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array. + /// The at the specified position. + public T this[int row, int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + this.CheckCoordinates(row, column); + return this.Data[(row * this.Width) + column]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.CheckCoordinates(row, column); + this.Data[(row * this.Width) + column] = value; + } + } + + /// + /// Performs an implicit conversion from a 2D array to a . + /// + /// The source array. + /// + /// The represenation on the source data. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Fast2DArray(T[,] data) + { + return new Fast2DArray(data); + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The row-coordinate of the item. Must be greater than zero and smaller than the height of the array. + /// The column-coordinate of the item. Must be greater than zero and smaller than the width of the array. + /// + /// Thrown if the coordinates are not within the bounds of the array. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) + { + if (row < 0 || row >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the array bounds."); + } + + if (column < 0 || column >= this.Width) + { + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the array bounds."); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs new file mode 100644 index 000000000..04217a012 --- /dev/null +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -0,0 +1,164 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Buffers; + using System.Runtime.InteropServices; + + /// + /// Manages a pinned buffer of value type data 'T' as a Disposable resource. + /// The backing array is either pooled or comes from the outside. + /// + /// The value type. + internal class PinnedBuffer : IDisposable + where T : struct + { + /// + /// A handle that allows to access the managed as an unmanaged memory by pinning. + /// + private GCHandle handle; + + /// + /// A value indicating whether this instance should return the array to the pool. + /// + private bool isPoolingOwner; + + /// + /// Initializes a new instance of the class. + /// + /// The desired count of elements. (Minimum size for ) + public PinnedBuffer(int count) + { + this.Count = count; + this.Array = PixelDataPool.Rent(count); + this.isPoolingOwner = true; + this.Pin(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The array to pin. + public PinnedBuffer(T[] array) + { + this.Count = array.Length; + this.Array = array; + this.Pin(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The count of "relevant" elements in 'array'. + /// The array to pin. + public PinnedBuffer(int count, T[] array) + { + if (array.Length < count) + { + throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array)); + } + + this.Count = count; + this.Array = array; + this.Pin(); + } + + /// + /// Finalizes an instance of the class. + /// + ~PinnedBuffer() + { + this.UnPin(); + } + + /// + /// Gets a value indicating whether this instance is disposed, or has lost ownership of . + /// + public bool IsDisposedOrLostArrayOwnership { get; private set; } + + /// + /// Gets the count of "relevant" elements. Usually be smaller than 'Array.Length' when is pooled. + /// + public int Count { get; private set; } + + /// + /// Gets the backing pinned array. + /// + public T[] Array { get; private set; } + + /// + /// Gets a pointer to the pinned . + /// + public IntPtr Pointer { get; private set; } + + /// + /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. + /// + public void Dispose() + { + if (this.IsDisposedOrLostArrayOwnership) + { + return; + } + + this.IsDisposedOrLostArrayOwnership = true; + this.UnPin(); + + if (this.isPoolingOwner) + { + PixelDataPool.Return(this.Array); + } + + this.Array = null; + this.Count = 0; + + GC.SuppressFinalize(this); + } + + /// + /// Unpins and makes the object "quasi-disposed" so the array is no longer owned by this object. + /// If is rented, it's the callers responsibility to return it to it's pool. (Most likely ) + /// + /// The unpinned + public T[] UnPinAndTakeArrayOwnership() + { + if (this.IsDisposedOrLostArrayOwnership) + { + throw new InvalidOperationException("UnPinAndTakeArrayOwnership() is invalid: either PinnedBuffer is disposed or UnPinAndTakeArrayOwnership() has been called multiple times!"); + } + + this.IsDisposedOrLostArrayOwnership = true; + this.UnPin(); + T[] array = this.Array; + this.Array = null; + return array; + } + + /// + /// Pins . + /// + private void Pin() + { + this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); + this.Pointer = this.handle.AddrOfPinnedObject(); + } + + /// + /// Unpins . + /// + private void UnPin() + { + if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) + { + return; + } + + this.handle.Free(); + this.Pointer = IntPtr.Zero; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs new file mode 100644 index 000000000..a97d17fdb --- /dev/null +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Buffers; + + /// + /// Provides a resource pool that enables reusing instances of value type arrays . + /// will always return arrays initialized with 'default(T)' + /// + /// The value type. + public static class PixelDataPool + where T : struct + { + /// + /// The used to pool data. + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + + /// + /// Rents the pixel array from the pool. + /// + /// The minimum length of the array to return. + /// The + public static T[] Rent(int minimumLength) + { + return ArrayPool.Rent(minimumLength); + } + + /// + /// Returns the rented pixel array back to the pool. + /// + /// The array to return to the buffer pool. + public static void Return(T[] array) + { + ArrayPool.Return(array, true); + } + + /// + /// Heuristically calculates a reasonable maxArrayLength value for the backing . + /// + /// The maxArrayLength value + internal static int CalculateMaxArrayLength() + { + // ReSharper disable once SuspiciousTypeConversion.Global + if (default(T) is IPixel) + { + const int MaximumExpectedImageSize = 16384; + return MaximumExpectedImageSize * MaximumExpectedImageSize; + } + else + { + return int.MaxValue; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/DHALF.TXT b/src/ImageSharp/Dithering/DHALF.TXT new file mode 100644 index 000000000..ec6ba3d6e --- /dev/null +++ b/src/ImageSharp/Dithering/DHALF.TXT @@ -0,0 +1,1331 @@ +DHALF.TXT +June 20, 1991 + +Original name: DITHER.TXT +Original date: January 2, 1989 + + +===================================== +ORIGINAL FOREWORD BY LEE CROCKER + +What follows is everything you ever wanted to know (for the time being) +about digital halftoning, or dithering. I'm sure it will be out of date as +soon as it is released, but it does serve to collect data from a wide +variety of sources into a single document, and should save you considerable +searching time. + +Numbers in brackets (e.g. [4] or [12]) are references. A list of these +works appears at the end of this document. + +Because this document describes ideas and algorithms which are constantly +changing, I expect that it may have many editions, additions, and +corrections before it gets to you. I will list my name below as original +author, but I do not wish to deter others from adding their own thoughts and +discoveries. This is not copyrighted in any way, and was created solely +for the purpose of organizing my own knowledge on the subject, and sharing +this with others. Please distribute it to anyone who might be interested. + +If you add anything to this document, please feel free to include your name +below as a contributor or as a reference. I would particularly like to see +additions to the "Other books of interest" section. Please keep the text in +this simple format: no margins, no pagination, no lines longer than 79 +characters, and no non-ASCII or non-printing characters other than a CR/LF +pair at the end of each line. It is intended that this be read on as many +different machines as possible. + + +Original Author: + + Lee Daniel Crocker [73407,2030] + + +Contributors: + + Paul Boulay [72117,446] + + Mike Morra [76703,4051] + + +===================================== +COMMENTS BY MIKE MORRA + +I first entered the world of imaging in the fall of 1990 when my employer, +Epson America Inc., began shipping the ES-300C color flatbed scanner. +Suddenly, here I was, a field systems analyst who had worked almost +exclusively with printers and PCs, thrust into a new and arcane world of +look-up tables and dithering and color reduction and .GIF files! I realized +right away that I had a lot of catching up to do (and it needed to be done +quickly), so I began to frequent the CompuServe Information Service's +Graphics Support Forum on a very regular basis. + +Lee Crocker's excellent paper called DITHER.TXT was one of the first pieces +of information that I came across, and it went a very long way toward +answering a lot of questions that I'd had about the subject of dithering. +It also provided me with the names of other essential reference works upon +which Lee had based his paper, and I immediately began an eager search for +these other references. + +In the course of my self-study, however, I found that DITHER.TXT does +presume the reader's familiarity with some fundamental imaging concepts, +which meant that I needed to do a little "cramming." I get the impression +that Lee was directing his paper more toward graphics programmers than to +complete neophytes like me. I decided that I would rewrite and append to +DITHER.TXT and try to incorporate some of the more elementary information +that I'd absorbed along the way. In doing so, I hope that it will make it +even more comprehensive, and thus even more useful to first-time users. + +I elected to rename the revised file and chose the name DHALF.TXT in homage +to the term "digital halftoning," as used in Robert Ulichney's splendid +reference work. Notwithstanding, this paper is still very much Lee's +original work, and I certainly do not propose that I have created something +new and original here. It is also quite possible that in changing the +presentation of some of the material therein, I may have unwittingly +corrupted Lee's original intent and delivery, and this was also not my +intention. + +Accordingly, I've submitted this paper to the Graphics Support Forum as a +draft work only, at least for the time being. Quite honestly, I don't know +whether it would be appropriate as a replacement to DITHER.TXT, or as a +second, distinct document. Too, I may very well have misconstrued or +misinterpreted some factual information in my revision. As such, I welcome +criticism and comment from all the original authors and contributors, and +any readers, with the hope that their feedback will help me to address these +issues. + +If this revision it is received favorably, I will submit it to the public +domain; if it is met with brickbats (for whatever reason), I will withdraw +it. Whatever the outcome, though, it will at least represent a very +rewarding learning experience on my part! + +With the unselfish help of many of the denizens of the Graphics Support +Forum, I was ultimately able to thrash out (in my own mind) the answers to +my questions that I needed. I'd like to publicly thank the whole Forum +community in general for putting up with my unending barrage of questions +and inquiries over the past few months . In particular, I would thank +John Swenson, Chris Young, and (of course) Lee Crocker for their invaluable +assistance. + +Mike Morra [76703,4051] +June 20, 1991 + + +===================================== +What is Digital Halftoning? + +Throughout much of the course of computer imaging technology, experimenters +and users have been challenged with attempting to acceptably render +digitized images on display devices which were incapable of reproducing the +full spectrum of intensities or colors present in the source image. The +challenge is even more pronounced in today's world of personal computing +because of the technology gap between image generation and image rendering +equipment. + +Today, we now have affordable 24-bit image scanners which can generate +nearly true-to-life scans having as many as 256 shades of gray, or in excess +of 16.7 million colors. Mainstream display technology, however, still lags +behind with 16- and 256-color VGA/SVGA video monitors and printers with +binary (black/white) "marking engines" as the norm. Without specialized +techniques for color reduction -- the process of finding the "best fit" of +the display device's available gray shades and/or colors -- the imaging +experimenter would be plagued with blotchy, noisy, off-color images. + +(As of this writing, "true color" 24-bit video display devices, capable of +reproducing all of the color/intensity information in the source image, are +now beginning to migrate downward into the PC environment, but they exact a +premium in cost and processor power which many users are loathe to pay. So- +called "high-color" video displays -- typically 16-bit, with 32,768-color +capability -- are moving into the mainstream, but color reduction techniques +would still be required with these devices.) + +The science of digital halftoning (more commonly referred to as dithering, +or spatial dithering) is one of the techniques used to achieve satisfactory +image rendering and color reduction. Initially, it was principally +associated with the rendering of continuous-tone (grayscale) images on +"binary" (i.e. 1-bit) video displays which could only display full black or +full white pixels, or on printers which could produce only full black spots +on a printed page. Indeed, Ulichney [3] gives a definition of digital +halftoning as "... any algorithmic process which creates the illusion of +continuous-tone images from the judicious arrangement of binary picture +elements." + +Ulichney's study, as well as the earlier literature on the subject (and this +paper itself), discusses the process mostly in this context. Since we in +the PC world are still saddled primarily with black/white marking engines in +our hardcopy devices, this binary interpretation of digital halftoning is +still very pertinent. However, as we will see later in this discussion, the +concept can also be extended to include display devices (typically video +monitors) which support limited grayscale or color palettes. Accordingly, +we can broaden the traditional definition of digital halftoning to refer to +rendering an image on any display device which is unable to show the entire +range of colors or gray shades that are contained in the source image. + + +===================================== +Intensity/Color Resolution + +The concept of resolution is essential to the understanding of digital +halftoning. Resolution can be defined as "fineness" and is used to +describe the level of detail in a digitally sampled signal. + +Typically, when we hear the term "resolution" applied to images, we think of +what's known as "spatial resolution," which is the basic sampling rate for +the image. It describes the fineness of the "dots" (pixels or ink/toner +spots) which comprise the image, i.e. how many of them are present along +each horizontal and vertical inch. However, we can also speak of "intensity +resolution" or "color resolution," which describes the fineness of detail +available at each spot, i.e. the number of different gray shades or colors +in the image. (I will go back and forth between the two terms depending on +the type of image being discussed, but the reader should be aware that the +concepts are analogous to each other.) + +As you might expect, the higher the resolution of a digital sample, the +better it can reproduce high frequency detail in the particular domain +described by that resolution. A VGA display, for example, has a relatively +good spatial resolution of 640 x 480 and a relatively poor color resolution +of 8 bits (256 colors). By comparison, an NTSC color television receiver +has a spatial resolution of approximately 350 x 525 and an excellent, nearly +infinite color resolution. Thus, images rendered on a VGA screen will be +quite sharp, but rather blotchy in color. The same image displayed on the +television receiver will not be as crisp, but will have much more accurate +color rendition. + +It is often possible to "trade" one kind of resolution for another. If your +display device has a higher spatial resolution than the image you are trying +to reproduce, it can show a very good image even if its color resolution is +less. This is what most of us know as "dithering" and is the subject of +this paper. (The other tradeoff, i.e., trading color resolution for spatial +resolution, is called "anti-aliasing," and is not discussed here.) + + +For the following discussions I will assume that we are given a grayscale +image with 256 shades of gray, which are assigned intensity values from 0 +(black) through 255 (white), and that we are trying to reproduce it on a +black and white output device, e.g. something like an Epson impact dotmatrix +printer, or an HP LaserJet laser printer. Most of these methods can be +extended in obvious ways to deal with displays that have more than two +levels (but still fewer than the source image), or to color images. Where +such extension is not obvious, or where better results can be obtained, I +will go into more detail. + + +===================================== +Fixed Thresholding + +A good place to start is with the example of performing a simple (or fixed) +thresholding operation on our grayscale image in order to display it on our +black and white device. This is accomplished by establishing a demarcation +point, or threshold, at the 50% gray level. Each dot of the source image is +compared against this threshold value: if it is darker than the value, the +device plots it black, and if it's lighter, the device plots it white. + +What happens to the image during this operation? Well, some detail +survives, but our perception of gray levels is completely gone. This means +that a lot of the image content is obliterated. Take an area of the image +which is made up of various gray shades in the range of 60-90%. After fixed +thresholding, all of those shades (being darker than the 50% gray threshold) +will be mapped to solid black. So much for variations of intensity. + +Another portion of the image might show an object with an increasing, +diffused shadow across one of its surfaces, with gray shades in the range of +20-70%. This gradual variation in intensity will be lost in fixed +thresholding, giving way to two separate areas (one white, one black) and a +distinct, visible boundary between them. The situation where a transition +from one intensity or shade to another is very conspicuous is known as +contouring. + + +===================================== +Artifacts + +Phenomena like contouring, which are not present in the source image but +produced by the digital signal processing, are called artifacts. The most +common type of artifact is the Moire' pattern. If you display or print an +image of several lines, closely spaced and radiating from a single point, +you will see what appear to be flower-like patterns. These are not part of +the original image but are an illusion produced by the jaggedness of the +display. We will encounter and discuss other forms of artifacts later in +this paper. + + +===================================== +Error Noise + +Returning to our fixed-thresholded (and badly-rendered) image, how could we +document what has taken place to make this image so inaccurate? Expressing +it in technical terms, a relatively large amount of error "noise" is present +in the fixed-thresholded image. The error value is the difference between +the image's original intensity at a given dot and the intensity of the +displayed dot. Obviously, very dark values like 1 or 2 (which are almost +full black) incur very small errors when they are rendered as a 0 value +(black) dot. On the other hand, a gross error is incurred when a 129 value +dot (a medium gray) is displayed at 255 value (white), for instance. + +Simply put, digital halftoning redistributes this "noise energy" in a way +which makes it less visible. This brings up an important concept: digital +halftoning does not INCREASE the noise energy. In some of the literature, +reference is made to the "addition of dither noise," which might give this +impression. This is not the case, however: effective digital halftoning +acts upon the low-frequency component of the error noise (the component +which contributes to graininess) and scatters it in higher-frequency +components where it is not as obvious. + + +===================================== +Classes of digital halftoning algorithms + +The algorithms we will discuss in this paper can be subdivided into four +categories: + + 1. Random dither + 2. Patterning + 3. Ordered dither + 4. Error-diffusion halftoning + +Each of these methods is generally better than those listed before it, but +other considerations such as processing time, memory constraints, etc. may +weigh in favor of one of the simpler methods. + +To convert any of the first three methods into color, simply apply the +algorithm separately for each primary color and mix the resulting values. +This assumes that you have at least eight output colors: black, red, green, +blue, cyan, magenta, yellow, and white. Though this will work for error +diffusion as well, there are better methods which will be discussed in more +detail later. + + +===================================== +Random dither + +Random dithering could be termed the "bubblesort" of digital halftoning +algorithms. It was the first attempt (documented as far back as 1951) to +correct the contouring produced by fixed thresholding, and it has +traditionally been referenced for comparison in most studies of digital +halftoning. In fact, the name "ordered dither" (which will be discussed +later) was chosen to contrast random dither. + +While it is not really acceptable as a production method, it is very simple +to describe and implement. For each dot in our grayscale image, we generate +a random number in the range 0 - 255: if the random number is greater than +the image value at that dot, the display device plots the dot white; +otherwise, it plots it black. That's it. + +This generates a picture with a lot of "white noise", which looks like TV +picture "snow". Although inaccurate and grainy, the image is free from +artifacts. Interestingly enough, this digital halftoning method is useful +in reproducing very low-frequency images, where the absence of artifacts is +more important than noise. For example, a whole screen containing a +gradient of all levels from black to white would actually look best with a +random dither. With this image, other digital halftoning algorithms would +produce significant artifacts like diagonal patterns (in ordered dithering) +and clustering (in error diffusion halftones). + +I should mention, of course, that unless your computer has a hardware-based +random number generator (and most don't), there may be some artifacts from +the random number generation algorithm itself. For efficiency, you can take +the random number generator "out of the loop" by generating a list of random +numbers beforehand for use in the dither. Make sure that the list is larger +than the number of dots in the image or you may get artifacts from the reuse +of numbers. The worst case would be if the size of your list of random +numbers is a multiple or near-multiple of the horizontal size of the image; +in this case, unwanted vertical or diagonal lines will appear. + +As unattractive as it is, random dithering can actually be related to a +pleasing, centuries-old art know as mezzotinting (the name itself is an +Italianized derivative of the English "halftone"). In a mezzotint, the +skilled craftsman worked a soft metal (usually copper) printing plate, and +roughened or ground the dark regions of the image by hand and in a seemingly +random fashion. Analyzing it in scientific terms (which would surely insult +any mezzotinting artisan who might read this!) the pattern created is not +very regular or periodic at all, but the absence of low frequency noise +leads to a very attractive image without much graininess. A similar process +is still in use today, in the form of modern gravure printing. + + +===================================== +"Classical" halftoning + +Let's take a short departure from the digital domain and look at the +traditional or "classical" printing technique of halftoning. This technique +is over a century old, dating back to the weaving of silk pictures in the +mid 1800's. Modern halftone printing was invented in the late 1800's, and +halftones of that period are even today considered to be attractive +renditions of their subjects. + +Essentially, halftoning involves the printing of dots of different sizes in +an ordered and closely spaced pattern in order to simulate various +intensities. The early halftoning artisans realized that when we view a +very small area at normal viewing distances, our eyes perform a blending or +smoothing function on the fine detail within that area. As a result, we +perceive only the overall intensity of the area. This is known as spatial +integration. + +Although the tools of halftoning (the "screens" and screening process used +to generate the varying dots of the printed image) have undergone +improvements throughout the years, the fundamental principles remain +unchanged. This includes the 45-degree "screen angle" of the lines of dots, +which was known even to the earliest halftone artisans as giving more +pleasing images than dot lines running horizontally and vertically. + + +===================================== +Patterning + +This was the first digital technique to pay homage to the classical +halftone. It takes advantage of the fact that the spatial resolution of +display devices had improved to the point where one could trade some of it +for better intensity resolution. Like random dither, it is also a simple +concept, but is much more effective. + +For each possible value in the image, we create and display a pattern of +pixels (which can be either video pixels or printer "spots") that +approximates that value. Remembering the concept of spatial integration, if +we choose the appropriate patterns we can simulate the appearance of various +intensity levels -- even though our display can only generate a limited set +of intensities. + +For example, consider a 3 x 3 pattern. It can have one of 512 different +arrangements of pixels: however, in terms of intensity, not all of them are +unique. Since the number of black pixels in the pattern determines the +darkness of the pattern, we really have only 10 discrete intensity patterns +(including the all-white pattern), each one having one more black pixel than +the previous one. + +But which 10 patterns? Well, we can eliminate, right off the bat, patterns +like: + + --- X-- --X X-- + XXX or -X- or -X- or X-- + --- --X X-- X-- + + +because if they were repeated over a large area (a common occurrence in many +images [1]) they would create vertical, horizontal, or diagonal lines. +Also, studies [1] have shown that the patterns should form a "growth +sequence:" once a pixel is intensified for a particular value, it should +remain intensified for all subsequent values. In this fashion, each pattern +is a superset of the previous one; this similarity between adjacent +intensity patterns minimizes any contouring artifacts. + +Here is a good pattern for a 3-by-3 matrix which subscribes to the rules set +forth above: + + + --- --- --- -X- -XX -XX -XX -XX XXX XXX + --- -X- -XX -XX -XX -XX XXX XXX XXX XXX + --- --- --- --- --- -X- -X- XX- XX- XXX + + +This pattern matrix effectively simulates a screened halftone with dots of +various sizes. In large areas of constant value, the repetitive pattern +formed will be mostly artifact-free. + +No doubt, the reader will realize that applying this patterning process to +our image will triple its size in each direction. Because of this, +patterning can only be used where the display's spatial resolution is much +greater than that of the image. + +Another limitation of patterning is that the effective spatial resolution is +decreased, since a multiple-pixel "cell" is used to simulate the single, +larger halftone dot. The more intensity resolution we want, the larger the +halftone cell used and, by extension, the lower the spatial resolution. + +In the above example, using 3 x 3 patterning, we are able to simulate 10 +intensity levels (not a very good rendering) but we must reduce the spatial +resolution to 1/3 of the original figure. To get 64 intensity levels (a +very acceptable rendering), we would have to go to an 8 x 8 pattern and an +eight-fold decrease in spatial resolution. And to get the full 256 levels +of intensity in our source image, we would need a 16 x 16 pattern and would +incur a 16-fold reduction in spatial resolution. Because of this size +distortion of the image, and with the development of more effective digital +halftoning methods, patterning is only infrequently used today. + +To extend this method to color images, we would use patterns of colored +pixels to represent shades not directly printable by the hardware. For +example, if your hardware is capable of printing only red, green, blue, and +black (the minimal case for color dithering), other colors can be +represented with 2 x 2 patterns of these four: + + + Yellow = R G Cyan = G B Magenta = R B Gray = R G + G R B G B R B K + + +(B here represents blue, K is black). In this particular example, there are +a total of 31 such distinct patterns which can be used; their enumeration is +left "as an exercise for the reader" (don't you hate books that do that?). + + +===================================== +Clustered vs. dispersed patterns + +The pattern diagrammed above is called a "clustered" pattern, so called +because as new pixels are intensified in each pattern, they are placed +adjacent to the already-intensified pixels. Clustered-dot patterns were +used on many of the early display devices which could not render individual +pixels very distinctly, e.g. printing presses or other printers which smear +the printed spots slightly (a condition known as dot gain), or video +monitors which introduce some blurriness to the pixels. Clustered-dot +groupings tend to hide the effect of dot gain, but also produce a somewhat +grainy image. + +As video and hardcopy display technology improved, newer devices (such as +electrophotographic laser printers and high-res video displays) were better +able to accurately place and size their pixels. Further research showed +that, especially with larger patterns, the dispersed (non-clustered) layout +was more pleasing. Here is one such pattern: + + + --- X-- X-- X-- X-X X-X X-X XXX XXX XXX + --- --- --- --X --X X-X X-X X-X XXX XXX + --- --- -X- -X- -X- -X- XX- XX- XX- XXX + + + +Since clustering is not used, dispersed-dot patterns produce less grainy +images. + + +===================================== +Ordered dither + +While patterning was an important step toward the digital reproduction of +the classic halftone, its main shortcoming was the spatial enlargement (and +corresponding reduction in resolution) of the image. Ordered dither +represents a major improvement in digital halftoning where this spatial +distortion was eliminated and the image could then be rendered in its +original size. + +Obviously, in order to accomplish this, each dot in the source image must be +mapped to a pixel on the display device on a one-to-one basis. Accordingly, +the patterning concept was redefined so that instead of plotting the whole +pattern for each image dot, THE IMAGE DOT IS MAPPED ONLY TO ONE PIXEL IN THE +PATTERN. Returning to our example of a 3 x 3 pattern, this means that we +would be mapping NINE image dots into this pattern. + +The simplest way to do this in programming is to map the X and Y coordinates +of each image dot into the pixel (X mod 3, Y mod 3) in the pattern. + +Returning to our two patterns (clustered and dispersed) as defined earlier, +we can derive an effective mathematical algorithm that can be used to plot +the correct pixel patterns. Because each of the patterns above is a +superset of the previous, we can express the patterns in a compact array +form as the order of pixels added: + + + 8 3 4 1 7 4 + 6 1 2 and 5 8 3 + 7 5 9 6 2 9 + + +Then we can simply use the value in the array as a threshold. If the value +of the original image dot (scaled into the 0-9 range) is less than the +number in the corresponding cell of the matrix, we plot that pixel black; +otherwise, we plot it white. Note that in large areas of constant value, we +will get repetitions of the pattern just as we did with patterning. + +As before, clustered patterns should be used for those display devices which +blur the pixels. In fact, the clustered-dot ordered dither is the process +used by most newspapers, and in the computer imaging world the term +"halftoning" has come to refer to this method if not otherwise qualified. + + +As noted earlier, the dispersed-dot method (where the display hardware +allows) is preferred in order to decrease the graininess of the displayed +images. Bayer [2] has shown that for matrices of orders which are powers of +two there is an optimal pattern of dispersed dots which results in the +pattern noise being as high-frequency as possible. The pattern for a 2x2 +and 4x4 matrices are as follows: + + +1 3 1 9 3 11 These patterns (and their rotations +4 2 13 5 15 7 and reflections) are optimal for a + 4 12 2 10 dispersed-dot ordered dither. + 16 8 14 6 + + +Ulichney [3] shows a recursive technique can be used to generate the larger +patterns. (To fully reproduce our 256-level image, we would need to use an +8x8 pattern.) + +The Bayer ordered dither is in very common use and is easily identified by +the cross-hatch pattern artifacts it produces in the resulting display. +This artifacting is the major drawback of an otherwise powerful and very +fast technique. + + +===================================== +Dithering with "blue noise" + +Up to this point in our discussion, we have (with the exception of dithering +with white noise) discussed digital halftoning schemes which rely on the +application of some fairly regular mathematical processes in order to +redistribute the error noise of the image. Unfortunately, the regularity of +these algorithms leads to different kinds of artifacting which detracts from +the rendered image. In addition, these images all tend to reflect the +display device's row-and-column dot pattern to some extent, and this further +contributes to the "mechanical" character of the output image. + +Dithering with white noise, on the other hand, introduces enough randomness +to suppress the artifacting and the gridlike appearance, but the low- +frequency component of this noise introduces graininess. + +Obviously, what is needed is a method which falls somewhere in the middle of +these two extremes. In theoretical terms, if we could take white noise and +remove its low-frequency content, this would be an ideal way to disperse the +error content of our image. Many of the digital halftoning developers, +making an analogy to the audio world, refer to this concept as dithering +with blue noise. (In audio theory, "pink noise," which is often used as a +diagnostic and testing tool, is white noise from which some level of high- +frequency content has been filtered.) + +Alas, while an audio-frequency analog low-pass filter is a relatively simple +device to construct and operate, implementing a digital high-pass filter in +program code -- and one which operates efficiently enough so as not to +degrade display response time -- is no trivial task. + + +===================================== +Error-diffusion halftoning + +After considerable research, it was found that a set of techniques known as +error diffusion (also termed error dispersion or error distribution) +accomplished this quite effectively. In fact, error diffusion generates the +best results of any of the digital halftoning methods described here. Much +of the low-frequency noise component is suppressed, producing images with +very little grain. Error-diffusion halftones also display a very pleasing +randomness, without the visual sensation of rows and columns of dots; this +effect is known as the "grid defiance illusion." + +As in other areas of life, though, there ain't no such thing as a free +lunch. Error diffusion is, by nature, the slowest method of digital +halftoning. In fact, there are several variants of this technique, and the +better they get, the slower they are. However, one will realize a very +significant improvement in the quality of the processed images which easily +justifies the time and computational power required. + +Error diffusion is very simple to describe. For each point in our image, we +first find the closest intensity (or color) available. We then calculate +the difference between the image value at that point and that nearest +available intensity/color: this difference is our error value. Now we +divide up the error value and distribute it to some of the neighboring image +areas which we have not visited (or processed) yet. When we get to these +later dots, we add in the portions of error values which were distributed +there from the preceding dots, and clip the cumulative value to an allowed +range if needed. This new, modified value now becomes the image value that +we use for processing this point. + +If we are dithering our sample grayscale image for output to a black-and- +white device, the "find closest intensity/color" operation is just a simple +thresholding (the closest intensity is going to be either black or white). +In color imaging -- for instance, color-reducing a 24-bit true color Targa +file to an 8-bit, mapped GIF file -- this involves matching the input color +to the closest available hardware color. Depending on how the display +hardware manages its intensity/color palette, this matching process can be a +difficult task. (This is covered in more detail in the "Color issues" +section later in this paper.) + +Up till now, all other methods of digital halftoning were point operations, +where any adjustments that were made to a given dot had no effect on any of +the surrounding dots. With error diffusion, we are doing a "neighborhood +operation." Dispersing the error value over a larger area is the key to the +success of these methods. + +The different ways of dividing up the error can be expressed as patterns +called filters. In the following sections, I will list a number of the most +commonly-used filters and some info on each. + + +===================================== +The Floyd-Steinberg filter + +This is where it all began, with Floyd and Steinberg's [4] pioneering +research in 1975. The filter can be diagrammed thus: + + + * 7 + 3 5 1 (1/16) + + +In this (and all subsequent) filter diagrams, the "*" represents the pixel +currently being scanning, and the neighboring numbers (called weights) +represent the portion of the error distributed to the pixel in that +position. The expression in parentheses is the divisor used to break up the +error weights. In the Floyd-Steinberg filter, each pixel "communicates" +with 4 "neighbors." The pixel immediately to the right gets 7/16 of the +error value, the pixel directly below gets 5/16 of the error, and the +diagonally adjacent pixels get 3/16 and 1/16. + +The weighting shown is for the traditional left-to-right scanning of the +image. If the line were scanned right-to-left (more about this later), this +pattern would be reversed. In either case, the weights calculated for the +subsequent line must be held by the program, usually in an array of some +sort, until that line is visited later. + +Floyd and Steinberg carefully chose this filter so that it would produce a +checkerboard pattern in areas with intensity of 1/2 (or 128, in our sample +image). It is also fairly easy to execute in programming code, since the +division by 16 is accomplished by simple, fast bit-shifting instructions +(this is the case whenever the divisor is a power of 2). + + +===================================== +The "false" Floyd-Steinberg filter + +Occasionally, you will see the following filter erroneously called the +Floyd-Steinberg filter: + + + * 3 + 3 2 (1/8) + + +The output from this filter is nowhere near as good as that from the real +Floyd-Steinberg filter. There aren't enough weights to the dispersion, +which means that the error value isn't distributed finely enough. With the +entire image scanned left-to-right, the artifacting produced would be +totally unacceptable. + +Much better results would be obtained by using an alternating, or +serpentine, raster scan: processing the first line left-to-right, the next +line right-to-left, and so on (reversing the filter pattern appropriately). +Serpentine scanning -- which can be used with any of the error-diffusion +filters detailed here -- introduces an additional perturbation which +contributes more randomness to the resultant halftone. Even with serpentine +scanning, however, this filter would need additional perturbations (see +below) to give acceptable results. + + +===================================== +The Jarvis, Judice, and Ninke filter + +If the false Floyd-Steinberg filter fails because the error isn't +distributed well enough, then it follows that a filter with a wider +distribution would be better. This is exactly what Jarvis, Judice, and +Ninke [6] did in 1976 with their filter: + + + * 7 5 + 3 5 7 5 3 + 1 3 5 3 1 (1/48) + + +While producing nicer output than Floyd-Steinberg, this filter is much +slower to implement. With the divisor of 48, we can no longer use bit- +shifting to calculate the weights but must invoke actual DIV (divide) +processor instructions. This is further exacerbated by the fact that the +filter must communicate with 12 neighbors; three times as many in the Floyd- +Steinberg filter. Furthermore, with the errors distributed over three +lines, this means that the program must keep two forward error arrays, which +requires extra memory and time for processing. + + +===================================== +The Stucki filter + +P. Stucki [7] offered a rework of the Jarvis, Judice, and Ninke filter in +1981: + + + * 8 4 + 2 4 8 4 2 + 1 2 4 2 1 (1/42) + + +Once again, division by 42 is quite slow to calculate (requiring DIVs). +However, after the initial 8/42 is calculated, some time can be saved by +producing the remaining fractions by shifts. The Stucki filter has been +observed to give very clean, sharp output, which helps to offset the slow +processing time. + + +===================================== +The Burkes filter + +Daniel Burkes [5] of TerraVision undertook to improve upon the Stucki filter +in 1988: + + + * 8 4 The Burkes filter + 2 4 8 4 2 (1/32) + + +Notice that this is just a simplification of the Stucki filter with the +bottom row removed. The main improvement is that the divisor is now 32, +which allows the error values to be calculated using shifts once more, and +the number of neighbors communicated with has been reduced to seven. +Furthermore, the removal of one row reduces the memory requirements of the +filter by eliminating the second forward array which would otherwise be +needed. + + +===================================== +The Sierra filters + +In 1989, Frankie Sierra came out with his three-line filter: + + + * 5 3 The Sierra3 filter + 2 4 5 4 2 + 2 3 2 (1/32) + + +A year later, Sierra followed up with a two-line modification: + + + * 4 3 The Sierra2 filter + 1 2 3 2 1 (1/16) + + +and a very simple "Filter Lite," as he calls it: + + + * 2 The Sierra-2-4A filter + 1 1 (1/4) + + +Even this very simple filter, according to Sierra, produces better results +than the original Floyd-Steinberg filter. + + +===================================== +Miscellaneous filters + +Many image processing software packages offer one or more of the filters +listed above as dithering options. In nearly every case, the Floyd- +Steinberg filter (or a variant thereof) is included. The Bayer ordered +dither is sometimes offered, although the Floyd-Steinberg filter will do a +better job in essentially the same processing time. Higher-quality filters +like Burkes or Stucki are usually also present. + +All of the filters described above are used on display devices which have +"square pixels." This is to say that the display lays out the pixels in +rows and columns, aligned horizontally and vertically and spaced equally in +both directions. This applies to the commonly-used video modes in VGA and +SVGA: 640 x 480, 800 x 600, and 1024 x 768, with a 4:3 "aspect ratio." It +would also include HP-compatible and PostScript desktop laser printers using +300dpi marking engines. + +Some displays may use "rectangular pixels," where the horizontal and +vertical spacings are unequal. This would include various EGA and CGA video +modes and other specialized video displays, and most dot-matrix printers. +In many cases, the filters described earlier will do a decent job on +rectangular pixel grids, but an optimized filter would be preferred. +Slinkman [10] describes one such filter for his 640 x 240 monochrome display +with a 1:2 aspect ratio. + +In other cases, video displays might use a "hexagonal grid" of pixels, where +rows of pixels are offset or staggered, in much the same fashion used on +broadcast television receivers. This is illustrated below: + + + . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . + square/rectangular hexagonal + + +Hexagonal grids are given a very thorough treatment by Ulichney, should you +be interested in further information. + +While technically not an error-diffusion filter, a method proposed by Gozum +[11] offers color resolutions in excess of 256 colors by plotting red, +green, and blue pixel "triplets" or triads to simulate an "interlaced" +television display (sacrificing some horizontal resolution in the process). +Again, I would refer interested readers to his document for more +information. + + +===================================== +Special considerations + +The speed disadvantages of the more complex filters can be eliminated +somewhat by performing the divisions beforehand and using lookup tables +instead of doing the math inside the loop. This makes it harder to use +various filters in the same program, but the speed benefits are enormous. + +It is critical with all of these algorithms that when error values are added +to neighboring pixels, the resultant summed values must be truncated to fit +within the limits of hardware. Otherwise, an area of very intense color may +cause streaks into an adjacent area of less intense color. + +This truncation is known as "clipping," and is analogous to the audio +world's concept of the same name. As in the case of an audio amplifier, +clipping adds undesired noise to the data. Unlike the audio world, however, +the visual clipping performed in error-diffusion halftoning is acceptable +since it is not nearly so offensive as the color streaking that would occur +otherwise. It is mainly for this reason that the larger filters work better +-- they split the errors up more finely and produce less clipping noise. + +With all of these filters, it is also important to ensure that the sum of +the distributed error values is equal to the original error value. This is +most easily accomplished by subtracting each fraction, as it is calculated, +from the whole error value, and using the final remainder as the last +fraction. + + +===================================== +Further perturbations + +As alluded to earlier, there are various techniques for the reduction of +digital artifacts, most of which involve using a little randomness to +lightly "perturb" a regular algorithm (particularly the simpler ones). It +could be said that random dither takes this concept to the extreme. + +Serpentine scanning is one of these techniques, as noted earlier. Other +techniques include the addition of small amounts of white noise, or +randomizing the positions of the error weights (essentially, using a +constantly-varying pattern). As you might imagine, any of these methods +incur a penalty in processing time. + +Indeed, some of the above filters (particularly the simpler ones) can be +greatly improved by skewing the weights with a little randomness [3]. + + +===================================== +Nearest available color + +Calculating the nearest available intensity is trivial with a monochrome +image; calculating the nearest available color in a color image requires +more work. + +A table of RGB values of all available colors must be scanned sequentially +for each input pixel to find the closest. The "distance" formula most often +used is a simple pythagorean "least squares". The difference for each color +is squared, and the three squares added to produce the distance value. This +value is equivalent to the square of the distance between the points in RGB- +space. It is not necessary to compute the square root of this value because +we are not interested in the actual distance, only in which is smallest. +The square root function is a monotonic increasing function and does not +affect the order of its operands. If the total number of colors with which +you are dealing is small, this part of the algorithm can be replaced by a +lookup table as well. + +When your hardware allows you to select the available colors, very good +results can be achieved by selecting colors from the image itself. You must +reserve at least 8 colors for the primaries, secondaries, black, and white +for best results. If you do not know the colors in your image ahead of +time, or if you are going to use the same map to dither several different +images, you will have to fill your color map with a good range of colors. +This can be done either by assigning a certain number of bits to each +primary and computing all combinations, or by a smoother distribution as +suggested by Heckbert [8]. + +An alternate method of color selection, based on a tetrahedral color space, +has been proposed by Crawford [12]. His algorithm has been optimized for +either dispersed-dot ordered dither or Floyd-Steinberg error diffusion with +serpentine scan. + + +===================================== +Hardware halftoning + +In some cases, image scanning hardware may be able to digitally halftone and +dither the image "on the fly" as it is being scanned. The data produced by +the "raw" scan is then already in a 1- or 2-bit/pixel format. While this +feature would probably be unsuitable for cases where the image would need +further processing (see the "Loss of image information" section below), it +is very useful where the operator wants to generate a final image, ready for +printing or displaying, with little or no subsequent processing. + +As an example, the Epson ES-300C color scanner (and its European equivalent, +the Epson GT-6000) offers three internal halftone modes. One is a standard +"halftone" algorithm, i.e. a clustered-dot ordered dither. The other two +are error-diffusion filters (one "sharp," the other "soft") which are +proprietary Epson-developed filters. + + +===================================== +Loss of image information incurred by digital halftoning + +It is important to emphasize here that digital halftoning is a ONE-WAY +operation. Once an image has been halftoned or dithered, although it may +look like a good reproduction of the original, INFORMATION IS PERMANENTLY +LOST. Many image processing functions fail on dithered images; in fact, you +would not want to dither an image which had already been dithered to some +extent. + +For these reasons, digital halftoning must be considered primarily as a way +TO PRODUCE AN IMAGE ON HARDWARE THAT WOULD OTHERWISE BE INCAPABLE OF +DISPLAYING IT. This would hold true wherever a grayscale or color image +needs to be rendered on a bilevel display device. In this situation, one +would almost never want to store the dithered image. + +On the other hand, when color images are dithered for display on color +displays with a lower color resolution, the dithered images are more useful. +In fact, the bulk of today's scanned-image GIF files which abound on +electronic BBSs and information services are 8-bit (256 color), colormapped +and dithered files created from 24-bit true-color scans. Only rarely are +the 24-bit files exchanged, because of the huge amount of data contained in +them. + +In some cases, these mapped GIF files may be further processed with special +paint/processing utilities, with very respectable results. However, the +previous warning still applies: one can never obtain the same image fidelity +when operating on the mapped GIF file as they could if they were operating +on the true-color image file. + +Generally speaking, digital halftoning and dithering should be the last +stage in producing a physical display from a digitally stored image. The +data representing an image should always be kept in full detail in case you +should want to reprocess it in any way. As affordable display technology +improves, the day may soon come where you might possess the hardware to +allow you to use all of the original image information without the need for +digital halftoning or color reduction. + + +===================================== +Sample code + +Despite my best efforts in expository writing, nothing explains an algorithm +better than real code. With that in mind, presented here are a few programs +which implement some of the concepts presented in this paper. + + +1) This code (in the C programming language) dithers a 256-level + monochrome image onto a black-and-white display with the Bayer ordered + dither. + +/* Bayer-method ordered dither. The array line[] contains the intensity +** values for the line being processed. As you can see, the ordered +** dither is much simpler than the error dispersion dither. It is also +** many times faster, but it is not as accurate and produces cross-hatch +** patterns on the output. +*/ + +unsigned char line[WIDTH]; + +int pattern[8][8] = { + { 0, 32, 8, 40, 2, 34, 10, 42}, /* 8x8 Bayer ordered dithering */ + {48, 16, 56, 24, 50, 18, 58, 26}, /* pattern. Each input pixel */ + {12, 44, 4, 36, 14, 46, 6, 38}, /* is scaled to the 0..63 range */ + {60, 28, 52, 20, 62, 30, 54, 22}, /* before looking in this table */ + { 3, 35, 11, 43, 1, 33, 9, 41}, /* to determine the action. */ + {51, 19, 59, 27, 49, 17, 57, 25}, + {15, 47, 7, 39, 13, 45, 5, 37}, + {63, 31, 55, 23, 61, 29, 53, 21} }; + +int getline(); /* Function to read line[] from image */ + /* file; must return EOF when done. */ +putdot(int x, int y); /* Plot white dot at given x, y. */ + +dither() +{ + int x, y; + + while (getline() != EOF) { + for (x=0; x> 2; /* Scale value to 0..63 range */ + + if (c > pattern[x & 7][y & 7]) putdot(x, y); + } + ++y; + } +} + + +2) This program (also written in C) dithers a color image onto an 8-color + display by error-diffusion using the Burkes filter. + +/* Burkes filter error diffusion dithering algorithm in color. The array +** line[][] contains the RGB values for the current line being processed; +** line[0][x] = red, line[1][x] = green, line[2][x] = blue. +*/ + +unsigned char line[3][WIDTH]; +unsigned char colormap[3][COLORS] = { + 0, 0, 0, /* Black This color map should be replaced */ + 255, 0, 0, /* Red by one available on your hardware */ + 0, 255, 0, /* Green */ + 0, 0, 255, /* Blue */ + 255, 255, 0, /* Yellow */ + 255, 0, 255, /* Magenta */ + 0, 255, 255, /* Cyan */ + 255, 255, 255 }; /* White */ + +int getline(); /* Function to read line[][] from image */ + /* file; must return EOF when done. */ +putdot(int x, int y, int c); /* Plot dot of given color at given x, y. */ + +dither() +{ + static int ed[3][WIDTH] = {0}; /* Errors distributed down, i.e., */ + /* to the next line. */ + int x, y, h, c, nc, v, /* Working variables */ + e[4], /* Error parts (7/8,1/8,5/8,3/8). */ + ef[3]; /* Error distributed forward. */ + long dist, sdist; /* Used for least-squares match. */ + + for (x=0; x 255) v = 255; /* and clip. */ + line[c][x] = v; + } + + sdist = 255L * 255L * 255L + 1L; /* Compute the color */ + for (c=0; c> 1; /* half of v, e[1..4] */ + e[1] = (7 * h) >> 3; /* will be filled */ + e[2] = h - e[1]; /* with the Floyd and */ + h = v - h; /* Steinberg weights. */ + e[3] = (5 * h) >> 3; + e[4] = h = e[3]; + + ef[c] = e[1]; /* Distribute errors. */ + if (x < WIDTH-1) ed[c][x+1] = e[2]; + if (x == 0) ed[c][x] = e[3]; else ed[c][x] += e[3]; + if (x > 0) ed[c][x-1] += e[4]; + } + } + ++y; + } +} + + +3) This program (in somewhat incomplete, very inefficient pseudo-C) + implements error diffusion dithering with the Floyd and Steinberg + filter. It is not efficiently coded, but its purpose is to show the + method, which I believe it does. + +/* Floyd/Steinberg error diffusion dithering algorithm in color. The array +** line[][] contains the RGB values for the current line being processed; +** line[0][x] = red, line[1][x] = green, line[2][x] = blue. It uses the +** external functions getline() and putdot(), whose purpose should be easy +** to see from the code. +*/ + +unsigned char line[3][WIDTH]; +unsigned char colormap[3][COLORS] = { + 0, 0, 0, /* Black This color map should be replaced */ + 255, 0, 0, /* Red by one available on your hardware. */ + 0, 255, 0, /* Green It may contain any number of colors */ + 0, 0, 255, /* Blue as long as the constant COLORS is */ + 255, 255, 0, /* Yellow set correctly. */ + 255, 0, 255, /* Magenta */ + 0, 255, 255, /* Cyan */ + 255, 255, 255 }; /* White */ + +int getline(); /* Function to read line[] from image file; */ + /* must return EOF when done. */ +putdot(int x, int y, int c); /* Plot dot of color c at location x, y. */ + +dither() +{ + static int ed[3][WIDTH] = {0}; /* Errors distributed down, i.e., */ + /* to the next line. */ + int x, y, h, c, nc, v, /* Working variables */ + e[4], /* Error parts (7/8,1/8,5/8,3/8). */ + ef[3]; /* Error distributed forward. */ + long dist, sdist; /* Used for least-squares match. */ + + for (x=0; x 255) v = 255; /* and clip. */ + line[c][x] = v; + } + + sdist = 255L * 255L * 255L + 1L; /* Compute the color */ + for (c=0; c> 1; /* half of v, e[1..4] */ + e[1] = (7 * h) >> 3; /* will be filled */ + e[2] = h - e[1]; /* with the Floyd and */ + h = v - h; /* Steinberg weights. */ + e[3] = (5 * h) >> 3; + e[4] = h = e[3]; + + ef[c] = e[1]; /* Distribute errors. */ + if (x < WIDTH-1) ed[c][x+1] = e[2]; + if (x == 0) ed[c][x] = e[3]; else ed[c][x] += e[3]; + if (x > 0) ed[c][x-1] += e[4]; + } + } /* next x */ + + ++y; + } /* next y */ +} + + +===================================== +Bibliography + +[1] Foley, J.D. and A. van Dam, Fundamentals of Interactive Computer + Graphics, Addison-Wesley, Reading, MA, 1982. + + This is a standard reference for many graphic techniques which has + not declined with age. Highly recommended. This edition is out + of print but can be found in many university and engineering + libraries. NOTE: This book has been updated and rewritten, and + this new version is currently in print as: + + Foley, J.D., A. van Dam, S.K. Feiner, and J.F. Hughes; Computer + Graphics: Principles and Practice. Addison-Wesley, Reading, MA, 1990. + + This rewrite omits some of the more technical data of the 1982 + edition, but has been updated to include information on error- + diffusion and the Floyd-Steinberg filter. Currently on computer + bookstore shelves and rather expensive (around $75 list price). + +[2] Bayer, B.E., "An Optimum Method for Two-Level Rendition of Continuous + Tone Pictures," IEEE International Conference on Communications, + Conference Records, 1973, pp. 26-11 to 26-15. + + A short article proving the optimality of Bayer's pattern in the + dispersed-dot ordered dither. + +[3] Ulichney, R., Digital Halftoning, The MIT Press, Cambridge, MA, 1987. + + This is the best book I know of for describing the various black + and white dithering methods. It has clear explanations (a little + higher math may come in handy) and wonderful illustrations. It + does not contain any code, but don't let that keep you from + getting this book. Computer Literacy normally carries it but the + title is often sold out. + + [MFM note: I can't describe how much information I got from this + book! Several different writers have praised this reference to + the skies, and I can only concur. Some of it went right over my + head -- it's heavenly for someone who is thrilled by Fourier + analysis -- but the rest of it is a clear and excellent treatment + of the subject. I had to request it on an interlibrary loan, but + it was worth the two weeks' wait and the 25 cents it cost me for + the search. University or engineering libraries would be your + best bet, as would technical bookstores.] + +[4] Floyd, R.W. and L. Steinberg, "An Adaptive Algorithm for Spatial Gray + Scale." SID 1975, International Symposium Digest of Technical Papers, + vol 1975m, pp. 36-37. + + Short article in which Floyd and Steinberg introduce their filter. + +[5] Daniel Burkes is unpublished, but can be reached at this address: + + Daniel Burkes + TerraVision, Inc. + 2351 College Station Road, Suite 563 + Athens, GA 30305 + + or via CIS at UID# 72077,356. The Burkes error filter was submitted to + the public domain on September 15, 1988 in an unpublished document, + "Presentation of the Burkes error filter for use in preparing + continuous-tone images for presentation on bi-level devices." The file + BURKES.ARC, in LIB 15 (Publications) of the CIS Graphics Support Forum, + contains this document as well as sample images. + +[6] Jarvis, J.F., C.N. Judice, and W.H. Ninke, "A Survey of Techniques for + the Display of Continuous Tone Pictures on Bi-Level Displays," Computer + Graphics and Image Processing, vol. 5, pp. 13-40, 1976. + +[7] Stucki, P., "MECCA - a multiple-error correcting computation algorithm + for bilevel image hardcopy reproduction." Research Report RZ1060, IBM + Research Laboratory, Zurich, Switzerland, 1981. + +[8] Heckbert, P. "Color Image Quantization for Frame Buffer Display." + Computer Graphics (SIGGRAPH 82), vol. 16, pp. 297-307, 1982. + +[9] Frankie Sierra is unpublished, but can be reached via CIS at UID# + 76356,2254. Pictorial presentations of his filters can be found in LIB + 17 (Developer's Den) of the CIS Graphics Support Forum as the files + DITER1.GIF, DITER2.GIF, DITER6.GIF, DITER7.GIF, DITER8.GIF, and + DITER9.GIF. + +[10] J.F.R. "Frank" Slinkman is unpublished, but can be reached via CIS at + UID# 72411,650. The file NUDTHR.ARC in LIB 17 (Developer's Den) of the + CIS Graphics Support Forum contains his document "New Dithering Method + for Non-Square Pixels" as well as sample images and encoding program. + +[11] Lawrence Gozum is unpublished, but can be reached via CIS at UID# + 73437,2372. His document "Notes of IDTVGA Dithering Method" can be + found in LIB 17 (Developer's Den) of the CIS Graphics Support Forum as + the file IDTVGA.TXT. + +[12] Robert M. Crawford is unpublished, but can be reached via CIS at UID# + 76356,741. The file DGIF.ZIP in LIB 17 (Developer's Den) of the CIS + Graphics Support Forum contains documentation, sample images, and demo + program. + + +======================================================================== +Other works of interest: + +Knuth, D.E., "Digital Halftones by Dot Diffusion." ACM Transactions on +Graphics, Vol. 6, No. 4, October 1987, pp 245-273. + + Surveys the various methods available for mapping grayscale images to + B&W for high-quality phototypesetting and laser printer reproduction. + Presents an algorithm for smooth dot diffusion. (With 22 references.) + +Newman, W.M. and R.F.S. Sproull, Principles of Interactive Computer +Graphics, 2nd edition, McGraw-Hill, New York, 1979. + + Similar to Foley and van Dam in scope and content. + +Rogers, D.F., Procedural Elements for Computer Graphics, McGraw-Hill, New +York, 1985. + + More of a conceptual treatment of the subject -- for something with + more programming code, see the following work. Alas, the author errs + in his discussion of the Floyd-Steinberg filter and uses the "false" + filter pattern discussed earlier. + +Rogers, D.F. and J. A. Adams, Mathematical Elements for Computer Graphics, +McGraw-Hill, New York, 1976. + + A good detailed discussion of producing graphic images on a computer. + Plenty of sample code. + +Kuto, S., "Continuous Color Presentation Using a Low-Cost Ink Jet Printer," +Proc. Computer Graphics Tokyo 84, 24-27 April, 1984, Tokyo, Japan. + +Mitchell, W.J., R.S. Liggett, and T. Kvan, The Art of Computer Graphics +Programming, Van Nostrand Reinhold Co., New York, 1987. + +Pavlidis, T., Algorithms for Graphics and Image Processing, Computer Science +Press, Rockville, MD, 1982. + diff --git a/src/ImageSharp/Dithering/DITHER.TXT b/src/ImageSharp/Dithering/DITHER.TXT new file mode 100644 index 000000000..1f49fd6eb --- /dev/null +++ b/src/ImageSharp/Dithering/DITHER.TXT @@ -0,0 +1,547 @@ +DITHER.TXT + +What follows is everything you ever wanted to know (for the time being) about +dithering. I'm sure it will be out of date as soon as it is released, but it +does serve to collect data from a wide variety of sources into a single +document, and should save you considerable searching time. + +Numbers in brackets (like this [0]) are references. A list of these works +appears at the end of this document. + +Because this document describes ideas and algorithms which are constantly +changing, I expect that it may have many editions, additions, and corrections +before it gets to you. I will list my name below as original author, but I +do not wish to deter others from adding their own thoughts and discoveries. +This is not copyrighted in any way, and was created solely for the purpose of +organizing my own knowledge on the subject, and sharing this with others. +Please distribute it to anyonw who might be interested. + +If you add anything to this document, please feel free to include your name +below as a contributor or as a reference. I would particularly like to see +additions to the "Other books of interest" section. Please keep the text in +this simple format: no margins, no pagination, no lines longer that 79 +characters, and no non-ASCII or non-printing characters other than a CR/LF +pair at the end of each line. It is intended that this be read on as many +different machines as possible. + +Original Author: + +Lee Crocker I can be reached in the CompuServe Graphics +1380 Jewett Ave Support Forum (GO PICS) with ID # 73407,2030. +Pittsburg, CA 94565 + +Contributors: + +======================================================================== +What is Dithering? + +Dithering, also called Halftoning or Color Reduction, is the process of +rendering an image on a display device with fewer colors than are in the +image. The number of different colors in an image or on a device I will call +its Color Resolution. The term "resolution" means "fineness" and is used to +describe the level of detail in a digitally sampled signal. It is used most +often in referring to the Spatial Resolution, which is the basic sampling +rate for a digitized image. + +Spatial resolution describes the fineness of the "dots" used in an image. +Color resolution describes the fineness of detail available at each dot. The +higher the resolution of a digital sample, the better it can reproduce high +frequency detail. A compact disc, for example, has a temporal (time) +resolution of 44,000 samples per second, and a dynamic (volume) resolution of +16 bits (0..65535). It can therefore reproduce sounds with a vast dynamic +range (from barely audible to ear-splitting) with great detail, but it has +problems with very high-frequency sounds, like violins and piccolos. + +It is often possible to "trade" one kind of resolution for another. If your +display device has a higher spatial resolution than the image you are trying +to reproduce, it can show a very good image even if its color resolution is +less. This is what we will call "dithering" and is the subject of this +paper. The other tradeoff, i.e., trading color resolution for spatial +resolution, is called "anti-aliasing" and is not discussed here. + +It is important to emphasize here that dithering is a one-way operation. +Once an image has been dithered, although it may look like a good +reproduction of the original, information is permanently lost. Many image +processing functions fail on dithered images. For these reasons, dithering +must be considered only as a way to produce an image on hardware that would +otherwise be incapable of displaying it. The data representing an image +should always be kept in full detail. + + +======================================================================== +Classes of dithering algorithms + +The classes of dithering algorithms we will discuss here are these: + +1. Random +2. Pattern +3. Ordered +4. Error dispersion + +Each of these methods is generally better than those listed before it, but +other considerations such as processing time, memory constraints, etc. may +weigh in favor of one of the simpler methods. + +For the following discussions I will assume that we are given an image with +256 shades of gray (0=black..255=white) that we are trying to reproduce on a +black and white ouput device. Most of these methods can be extended in +obvious ways to deal with displays that have more than two levels but fewer +than the image, or to color images. Where such extension is not obvious, or +where better results can be obtained, I will go into more detail. + +To convert any of the first three methods into color, simply apply the +algorithm separately for each primary and mix the resulting values. This +assumes that you have at least eight output colors: black, red, green, blue, +cyan, magenta, yellow, and white. Though this will work for error dispersion +as well, there are better methods in this case. + + +======================================================================== +Random dither + +This is the bubblesort of dithering algorithms. It is not really acceptable +as a production method, but it is very simple to describe and implement. For +each value in the image, simply generate a random number 1..256; if it is +geater than the image value at that point, plot the point white, otherwise +plot it black. That's it. This generates a picture with a lot of "white +noise", which looks like TV picture "snow". Though the image produced is +very inaccurate and noisy, it is free from "artifacts" which are phenomena +produced by digital signal processing. + +The most common type of artifact is the Moire pattern (Contributors: please +resist the urge to put an accent on the "e", as no portable character set +exists for this). If you draw several lines close together radiating from a +single point on a computer display, you will see what appear to be flower- +like patterns. These patterns are not part of the original idea of lines, +but are an illusion produced by the jaggedness of the display. + +Many techniques exist for the reduction of digital artifacts like these, most +of which involve using a little randomness to "perturb" a regular algorithm a +little. Random dither obviously takes this to extreme. + +I should mention, of course, that unless your computer has a hardware-based +random number generator (and most don't) there may be some artifacts from the +random number generation algorithm itself. + +While random dither adds a lot of high-frequency noise to a picture, it is +useful in reproducing very low-frequency images where the absence of +artifacts is more important than noise. For example, a whole screen +containing a gradient of all levels from black to white would actually look +best with a random dither. In this case, ordered dithering would produce +diagonal patterns, and error dispersion would produce clustering. + +For efficiency, you can take the random number generator "out of the loop" by +generating a list of random numbers beforehand for use in the dither. Make +sure that the list is larger than the number of pixels in the image or you +may get artifacts from the reuse of numbers. The worst case would be if the +size of your list of random numbers is a multiple or near-multiple of the +horizontal size of the image, in which case unwanted vertical or diagonal +lines will appear. + + +======================================================================== +Pattern dither + +This is also a simple concept, but much more effective than random dither. +For each possible value in the image, create a pattern of dots that +approximates that value. For instance, a 3-by-3 block of dots can have one +of 512 patterns, but for our purposes, there are only 10; the number of black +dots in the pattern determines the darkness of the pattern. + +Which 10 patterns do we choose? Obviously, we need the all-white and all- +black patterns. We can eliminate those patterns which would create vertical +or horizontal lines if repeated over a large area because many images have +such regions of similar value [1]. It has been shown [1] that patterns for +adjacent colors should be similar to reduce an artifact called "contouring", +or visible edges between regions of adjacent values. One easy way to assure +this is to make each pattern a superset of the previous. Here are two good +sets of patterns for a 3-by-3 matrix: + + --- --- --- -X- -XX -XX -XX -XX XXX XXX + --- -X- -XX -XX -XX -XX XXX XXX XXX XXX + --- --- --- --- --- -X- -X- XX- XX- XXX +or + --- X-- X-- X-- X-X X-X X-X XXX XXX XXX + --- --- --- --X --X X-X X-X X-X XXX XXX + --- --- -X- -X- -X- -X- XX- XX- XX- XXX + +The first set of patterns above are "clustered" in that as new dots are added +to each pattern, they are added next to dots already there. The second set +is "dispersed" as the dots are spread out more. This distinction is more +important on larger patterns. Dispersed-dot patterns produce less grainy +images, but require that the output device render each dot distinctly. When +this is not the case, as with a printing press which smears the dots a +little, clustered patterns are better. + +For each pixel in the image we now print the pattern which is closest to its +value. This will triple the size of the image in each direction, so this +method can only be used where the display spatial resolution is much greater +than that of the image. + +We can exploit the fact that most images have large areas of similar value to +reduce our need for extra spatial resolution. Instead of plotting a whole +pattern for each pixel, map each pixel in the image to a dot in the pattern +an only plot the corresponding dot for each pixel. + +The simplest way to do this is to map the X and Y coordinates of each pixel +into the dot (X mod 3, Y mod 3) in the pattern. Large areas of constant +value will come out as repetitions of the pattern as before. + +To extend this method to color images, we must use patterns of colored dots +to represent shades not directly printable by the hardware. For example, if +your hardware is capable of printing only red, green, blue, and black (the +minimal case for color dithering), other colors can be represented with +patterns of these four: + + Yellow = R G Cyan = G B Magenta = R B Gray = R G + G R B G B R B K + +(B here represents blue, K is black). There are a total of 31 such distinct +patterns which can be used; I will leave their enumeration "as an exercise +for the reader" (don't you hate books that do that?). + + +======================================================================== +Ordered dither + +Because each of the patterns above is a superset of the previous, we can +express the patterns in compact form as the order of dots added: + + 8 3 4 and 1 7 4 + 6 1 2 5 8 3 + 7 5 9 6 2 9 + +Then we can simply use the value in the array as a threshhold. If the value +of the pixel (scaled into the 0-9 range) is less than the number in the +corresponding cell of the matrix, plot that pixel black, otherwise, plot it +white. This process is called ordered dither. As before, clustered patterns +should be used for devices which blur dots. In fact, the clustered pattern +ordered dither is the process used by most newspapers, and the term +halftoning refers to this method if not otherwise qualified. + +Bayer [2] has shown that for matrices of orders which are powers of two there +is an optimal pattern of dispersed dots which results in the pattern noise +being as high-frequency as possible. The pattern for a 2x2 and 4x4 matrices +are as follows: + + 1 3 1 9 3 11 These patterns (and their rotations + 4 2 13 5 15 7 and reflections) are optimal for a + 4 12 2 10 dispersed-pattern ordered dither. + 16 8 14 6 + +Ulichney [3] shows a recursive technique can be used to generate the larger +patterns. To fully reproduce our 256-level image, we would need to use the +8x8 pattern. + +Bayer's method is in very common use and is easily identified by the cross- +hatch pattern artifacts it produces in the resulting display. This +artifacting is the major drawback of the technique wich is otherwise very +fast and powerful. Ordered dithering also performs very badly on images +which have already been dithered to some extent. As stated earlier, +dithering should be the last stage in producing a physical display from a +digitally stored image. The dithered image should never be stored itself. + + +======================================================================== +Error dispersion + +This technique generates the best results of any method here, and is +naturally the slowest. In fact, there are many variants of this technique as +well, and the better they get, the slower they are. + +Error dispersion is very simple to describe: for each point in the image, +first find the closest color available. Calculate the difference between the +value in the image and the color you have. Now divide up these error values +and distribute them over the neighboring pixels which you have not visited +yet. When you get to these later pixels, just add the errors distributed +from the earlier ones, clip the values to the allowed range if needed, then +continue as above. + +If you are dithering a grayscale image for output to a black-and-white +device, the "find closest color" is just a simle threshholding operation. In +color, it involves matching the input color to the closest available hardware +color, which can be difficult depending on the hardware palette. + +There are many ways to distribute the errors and many ways to scan the +image, but I will deal here with only a few. The two basic ways to scan the +image are with a normal left-to-right, top-to-bottom raster, or with an +alternating left-to-right then right-to-left raster. The latter method +generally produces fewer artifacts and can be used with all the error +diffusion patterns discussed below. + +The different ways of dividing up the error can be expressed as patterns +(called filters, for reasons too boring to go into here). + + X 7 This is the Floyd and Steinberg [4] + 3 5 1 error diffusion filter. + +In this filter, the X represents the pixel you are currently scanning, and +the numbers (called weights, for equally boring reasons) represent the +proportion of the error distributed to the pixel in that position. Here, the +pixel immediately to the right gets 7/16 of the error (the divisor is 16 +because the weights add to 16), the pixel directly below gets 5/16 of the +error, and the diagonally adjacent pixels get 3/16 and 1/16. When scanning a +line right-to-left, this pattern is reversed. This pattern was chosen +carefully so that it would produce a checkerboard pattern in areas with +intensity of 1/2 (or 128 in our image). It is also fairly easy to calculate +when the division by 16 is replaced by shifts. + +Another filter in common use, but not recommended: + + X 3 A simpler filter. + 3 2 + +This is often erroneously called the Floyd-Steinberg filter, but it does not +produce as good results. An alternating raster scan of the image is +necessary with this filter to reduce artifacts. Additional perturbations of +the formula are frequently necessary also. + +Burke [5] suggests the following filter: + + X 8 4 The Burke filter. + 2 4 8 4 2 + +Notice that this is just a simplification of the Stucki filter (below) with +the bottom row removed. The main improvement is that the divisor is now 32, +which makes calculating the errors faster, and the removal of one row +reduces the memory requirements of the method. + +This is also fairly easy to calculate and produces better results than Floyd +and Steinberg. Jarvis, Judice, and Ninke [6] use the following: + + X 7 5 The Jarvis, et al. pattern. + 3 5 7 5 3 + 1 3 5 3 1 + +The divisor here is 48, which is a little more expensive to calculate, and +the errors are distributed over three lines, requiring extra memory and time +for processing. Probably the best filter is from Stucki [7]: + + X 8 4 The Stucki pattern. + 2 4 8 4 2 + 1 2 4 2 1 + +This one takes a division by 42 for each pixel and is therefore slow if math +is done inside the loop. After the initial 8/42 is calculated, some time can +be saved by producing the remaining fractions by shifts. + +The speed advantages of the simpler filters can be eliminated somewhat by +performing the divisions beforehand and using lookup tables instead of per- +forming math inside the loop. This makes it harder to use various filters +in the same program, but the speed benefits are enormous. + +It is critical with all of these algorithms that when error values are added +to neighboring pixels, the values must be truncated to fit within the limits +of hardware, otherwise and area of very intense color may cause streaks into +an adjacent area of less intense color. This truncation adds noise to the +image anagous to clipping in an audio amplifier, but it is not nearly so +offensive as the streaking. It is mainly for this reason that the larger +filters work better--they split the errors up more finely and produce less of +this clipping noise. + +With all of these filters, it is also important to ensure that the errors +you distribute properly add to the original error value. This is easiest to +accomplish by subtracting each fraction from the whole error as it is +calculated, and using the final remainder as the last fraction. + +Some of these methods (particularly the simpler ones) can be greatly improved +by skewing the weights with a little randomness [3]. + +Calculating the "nearest available color" is trivial with a monochrome image; +with color images it requires more work. A table of RGB values of all +available colors must be scanned sequentially for each input pixel to find +the closest. The "distance" formula most often used is a simple pythagorean +"least squares". The difference for each color is squared, and the three +squares added to produce the distance value. This value is equivalent to the +square of the distance between the points in RGB-space. It is not necessary +to compute the square root of this value because we are not interested in the +actual distance, only in which is smallest. The square root function is a +monotonic increasing function and does not affect the order of its operands. +If the total number of colors with which you are dealing is small, this part +of the algorithm can be replaced by a lookup table as well. + +When your hardware allows you to select the available colors, very good +results can be achieved by selecting colors from the image itself. You must +reserve at least 8 colors for the primaries, secondaries, black, and white +for best results. If you do not know the colors in your image ahead of time, +or if you are going to use the same map to dither several different images, +you will have to fill your color map with a good range of colors. This can +be done either by assigning a certain number of bits to each primary and +computing all combinations, or by a smoother distribution as suggested by +Heckbert [8]. + + +======================================================================== +Sample code + +Despite my best efforts in expository writing, nothing explains an algorithm +better than real code. With that in mind, presented here below is an +algorithm (in somewhat incomplete, very inefficient pseudo-C) which +implements error diffusion dithering with the Floyd and Steinberg filter. It +is not efficiently coded, but its purpose is to show the method, which I +believe it does. + +/* Floyd/Steinberg error diffusion dithering algorithm in color. The array +** line[][] contains the RGB values for the current line being processed; +** line[0][x] = red, line[1][x] = green, line[2][x] = blue. It uses the +** external functions getline() and putdot(), whose pupose should be easy +** to see from the code. +*/ + +unsigned char line[3][WIDTH]; +unsigned char colormap[3][COLORS] = { + 0, 0, 0, /* Black This color map should be replaced */ + 255, 0, 0, /* Red by one available on your hardware. */ + 0, 255, 0, /* Green It may contain any number of colors */ + 0, 0, 255, /* Blue as long as the constant COLORS is */ + 255, 255, 0, /* Yellow set correctly. */ + 255, 0, 255, /* Magenta */ + 0, 255, 255, /* Cyan */ + 255, 255, 255 }; /* White */ + +int getline(); /* Function to read line[] from image file; */ + /* must return EOF when done. */ +putdot(int x, int y, int c); /* Plot dot of color c at location x, y. */ + +dither() +{ + static int ed[3][WIDTH] = {0}; /* Errors distributed down, i.e., */ + /* to the next line. */ + int x, y, h, c, nc, v, /* Working variables */ + e[4], /* Error parts (7/8,1/8,5/8,3/8). */ + ef[3]; /* Error distributed forward. */ + long dist, sdist; /* Used for least-squares match. */ + + for (x=0; x 255) v = 255; /* and clip. */ + line[c][x] = v; + } + + sdist = 255L * 255L * 255L + 1L; /* Compute the color */ + for (c=0; c> 1; /* half of v, e[1..4] */ + e[1] = (7 * h) >> 3; /* will be filled */ + e[2] = h - e[1]; /* with the Floyd and */ + h = v - h; /* Steinberg weights. */ + e[3] = (5 * h) >> 3; + e[4] = h = e[3]; + + ef[c] = e[1]; /* Distribute errors. */ + if (x < WIDTH-1) ed[c][x+1] = e[2]; + if (x == 0) ed[c][x] = e[3]; else ed[c][x] += e[3]; + if (x > 0) ed[c][x-1] += e[4]; + } + } /* next x */ + + ++y; + } /* next y */ +} + + +======================================================================== +Bibliography + +[1] Foley, J. D. and Andries Van Dam (1982) + Fundamentals of Interactive Computer Graphics. Reading, MA: Addisson + Wesley. + + This is a standard reference for many graphic techniques which has not + declined with age. Highly recommended. + +[2] Bayer, B. E. (1973) + "An Optimum Method for Two-Level Rendition of Continuous Tone Pictures," + IEEE International Conference on Communications, Conference Records, pp. + 26-11 to 26-15. + + A short article proving the optimality of Bayer's pattern in the + dispersed-dot ordered dither. + +[3] Ulichney, R. (1987) + Digital Halftoning. Cambridge, MA: The MIT Press. + + This is the best book I know of for describing the various black and + white dithering methods. It has clear explanations (a little higher math + may come in handy) and wonderful illustrations. It does not contain any + code, but don't let that keep you from getting this book. Computer + Literacy carries it but is often sold out. + +[4] Floyd, R.W. and L. Steinberg (1975) + "An Adaptive Algorithm for Spatial Gray Scale." SID International + Symposium Digest of Technical Papers, vol 1975m, pp. 36-37. + + Short article in which Floyd and Steinberg introduce their filter. + +[5] Daniel Burkes is unpublished, but can be reached at this address: + + Daniel Burkes + TerraVision Inc. + 2351 College Station Road Suite 563 + Athens, GA 30305 + + or via CompuServe's Graphics Support Forum, ID # 72077,356. + +[6] Jarvis, J. F., C. N. Judice, and W. H. Ninke (1976) + "A Survey of Techniques for the Display of Continuous Tone Pictures on + Bi-Level Displays." Computer Graphics and Image Processing, vol. 5, pp. + 13-40. + +[7] Stucki, P. (1981) + "MECCA - a multiple-error correcting computation algorithm for bilevel + image hardcopy reproduction." Research Report RZ1060, IBM Research + Laboratory, Zurich, Switzerland. + +[8] Heckbert, Paul (9182) + "Color Image Quantization for Frame Buffer Display." Computer Graphics + (SIGGRAPH 82), vol. 16, pp. 297-307. + + +======================================================================== +Other works of interest: + +Newman, William M., and Robert F. S. Sproull (1979) +Principles of Interactive Computer Graphics. 2nd edition. New York: +McGraw-Hill. + +Rogers, David F. (1985) +Procedural Elements for Computer Graphics. New York: McGraw-Hill. + +Rogers, David F., and J. A. Adams (1976) +Mathematical Elements for Computer Graphics. New York: McGraw-Hill. + + +======================================================================== +About CompuServe Graphics Support Forum: + +CompuServe Information Service is a service of the H&R Block companies +providing computer users with electronic mail, teleconferencing, and many +other telecommunications services. Call 800-848-8199 for more information. + +The Graphics Support Forum is dedicated to helping its users get the most out +of their computers' graphics capabilities. It has a small staff and a large +number of "Developers" who create images and software on all types of +machines from Apple IIs to Sun workstations. While on CompuServe, type GO +PICS from any "!" prompt to gain access to the forum. \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs new file mode 100644 index 000000000..b94b87255 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. + /// + /// + public sealed class Atkinson : ErrorDiffuser + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray AtkinsonMatrix = + new float[,] + { + { 0, 0, 1, 1 }, + { 1, 1, 1, 0 }, + { 0, 1, 0, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Atkinson() + : base(AtkinsonMatrix, 8) + { + } + } +} diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs new file mode 100644 index 000000000..894b6e236 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Burks image dithering algorithm. + /// + /// + public sealed class Burks : ErrorDiffuser + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray BurksMatrix = + new float[,] + { + { 0, 0, 0, 8, 4 }, + { 2, 4, 8, 4, 2 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Burks() + : base(BurksMatrix, 32) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs new file mode 100644 index 000000000..20a45d4df --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// The base class for performing effor diffusion based dithering. + /// + public abstract class ErrorDiffuser : IErrorDiffuser + { + /// + /// The vector to perform division. + /// + private readonly Vector4 divisorVector; + + /// + /// The matrix width + /// + private readonly int matrixHeight; + + /// + /// The matrix height + /// + private readonly int matrixWidth; + + /// + /// The offset at which to start the dithering operation. + /// + private readonly int startingOffset; + + /// + /// Initializes a new instance of the class. + /// + /// The dithering matrix. + /// The divisor. + protected ErrorDiffuser(Fast2DArray matrix, byte divisor) + { + Guard.NotNull(matrix, nameof(matrix)); + Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); + + this.Matrix = matrix; + this.matrixWidth = this.Matrix.Width; + this.matrixHeight = this.Matrix.Height; + this.divisorVector = new Vector4(divisor); + + this.startingOffset = 0; + for (int i = 0; i < this.matrixWidth; i++) + { + // Good to disable here as we are not comparing matematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (matrix[0, i] != 0) + { + this.startingOffset = (byte)(i - 1); + break; + } + } + } + + /// + public Fast2DArray Matrix { get; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) + where TColor : struct, IPixel + { + // Assign the transformed pixel to the array. + pixels[x, y] = transformed; + + // Calculate the error + Vector4 error = source.ToVector4() - transformed.ToVector4(); + + // Loop through and distribute the error amongst neighbouring pixels. + for (int row = 0; row < this.matrixHeight; row++) + { + int matrixY = y + row; + + for (int col = 0; col < this.matrixWidth; col++) + { + int matrixX = x + (col - this.startingOffset); + + if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height) + { + float coefficient = this.Matrix[row, col]; + + // Good to disable here as we are not comparing matematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (coefficient == 0) + { + continue; + } + + Vector4 coefficientVector = new Vector4(coefficient); + Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4(); + Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; + result.W = offsetColor.W; + + TColor packed = default(TColor); + packed.PackFromVector4(result); + pixels[matrixX, matrixY] = packed; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs new file mode 100644 index 000000000..f7a93667f --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. + /// + /// + public sealed class FloydSteinberg : ErrorDiffuser + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray FloydSteinbergMatrix = + new float[,] + { + { 0, 0, 7 }, + { 3, 5, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public FloydSteinberg() + : base(FloydSteinbergMatrix, 16) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs new file mode 100644 index 000000000..4fb31c13e --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + using System; + + /// + /// Encapsulates properties and methods required to perfom diffused error dithering on an image. + /// + public interface IErrorDiffuser + { + /// + /// Gets the dithering matrix + /// + Fast2DArray Matrix { get; } + + /// + /// Transforms the image applying the dither matrix. This method alters the input pixels array + /// + /// The pixel accessor + /// The source pixel + /// The transformed pixel + /// The column index. + /// The row index. + /// The image width. + /// The image height. + /// The pixel format. + void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) + where TColor : struct, IPixel; + } +} diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs new file mode 100644 index 000000000..60fef8121 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm. + /// + /// + public sealed class JarvisJudiceNinke : ErrorDiffuser + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray JarvisJudiceNinkeMatrix = + new float[,] + { + { 0, 0, 0, 7, 5 }, + { 3, 5, 7, 5, 3 }, + { 1, 3, 5, 3, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public JarvisJudiceNinke() + : base(JarvisJudiceNinkeMatrix, 48) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs new file mode 100644 index 000000000..4325438e0 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. + /// + /// + public sealed class Sierra2 : ErrorDiffuser + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray Sierra2Matrix = + new float[,] + { + { 0, 0, 0, 4, 3 }, + { 1, 2, 3, 2, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Sierra2() + : base(Sierra2Matrix, 16) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs new file mode 100644 index 000000000..25ea70d0a --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. + /// + /// + public sealed class Sierra3 : ErrorDiffuser + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray Sierra3Matrix = + new float[,] + { + { 0, 0, 0, 5, 3 }, + { 2, 4, 5, 4, 2 }, + { 0, 2, 3, 2, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Sierra3() + : base(Sierra3Matrix, 32) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs new file mode 100644 index 000000000..c7b1d214f --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the SierraLite image dithering algorithm. + /// + /// + public sealed class SierraLite : ErrorDiffuser + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray SierraLiteMatrix = + new float[,] + { + { 0, 0, 2 }, + { 1, 1, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public SierraLite() + : base(SierraLiteMatrix, 4) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs new file mode 100644 index 000000000..93258c350 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Stucki image dithering algorithm. + /// + /// + public sealed class Stucki : ErrorDiffuser + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray StuckiMatrix = + new float[,] + { + { 0, 0, 0, 8, 4 }, + { 2, 4, 8, 4, 2 }, + { 1, 2, 4, 2, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Stucki() + : base(StuckiMatrix, 42) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs new file mode 100644 index 000000000..1027e51d9 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering.Ordered +{ + using System; + + /// + /// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix. + /// + /// + public class Bayer : IOrderedDither + { + /// + /// The threshold matrix. + /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 + /// + private static readonly Fast2DArray ThresholdMatrix = + new byte[,] + { + { 15, 143, 47, 175 }, + { 207, 79, 239, 111 }, + { 63, 191, 31, 159 }, + { 255, 127, 223, 95 } + }; + + /// + public Fast2DArray Matrix { get; } = ThresholdMatrix; + + /// + public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) + where TColor : struct, IPixel + { + source.ToXyzwBytes(bytes, 0); + pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs new file mode 100644 index 000000000..162cdb6a1 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + using System; + + /// + /// Encapsulates properties and methods required to perfom ordered dithering on an image. + /// + public interface IOrderedDither + { + /// + /// Gets the dithering matrix + /// + Fast2DArray Matrix { get; } + + /// + /// Transforms the image applying the dither matrix. This method alters the input pixels array + /// + /// The pixel accessor + /// The source pixel + /// The color to apply to the pixels above the threshold. + /// The color to apply to the pixels below the threshold. + /// The byte array to pack/unpack to. Must have a length of 4. Bytes are unpacked to Xyzw order. + /// The component index to test the threshold against. Must range from 0 to 3. + /// The column index. + /// The row index. + /// The image width. + /// The image height. + /// The pixel format. + void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) + where TColor : struct, IPixel; + } +} diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs new file mode 100644 index 000000000..aabca31aa --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering.Ordered +{ + using System; + + /// + /// Applies error diffusion based dithering using the 4x4 ordered dithering matrix. + /// + /// + public class Ordered : IOrderedDither + { + /// + /// The threshold matrix. + /// This is calculated by multiplying each value in the original matrix by 16 + /// + private static readonly Fast2DArray ThresholdMatrix = + new byte[,] + { + { 0, 128, 32, 160 }, + { 192, 64, 224, 96 }, + { 48, 176, 16, 144 }, + { 240, 112, 208, 80 } + }; + + /// + public Fast2DArray Matrix { get; } = ThresholdMatrix; + + /// + public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) + where TColor : struct, IPixel + { + source.ToXyzwBytes(bytes, 0); + pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs new file mode 100644 index 000000000..5257b07b3 --- /dev/null +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared decoder options. + /// + public class DecoderOptions : IDecoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public DecoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options + protected DecoderOptions(IDecoderOptions options) + { + if (options != null) + { + this.IgnoreMetadata = options.IgnoreMetadata; + } + } + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } = false; + } +} diff --git a/src/ImageSharp/Formats/EncoderOptions.cs b/src/ImageSharp/Formats/EncoderOptions.cs new file mode 100644 index 000000000..27a7e9781 --- /dev/null +++ b/src/ImageSharp/Formats/EncoderOptions.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared encoder options. + /// + public class EncoderOptions : IEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public EncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options + protected EncoderOptions(IEncoderOptions options) + { + if (options != null) + { + this.IgnoreMetadata = options.IgnoreMetadata; + } + } + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + public bool IgnoreMetadata { get; set; } = false; + } +} diff --git a/src/ImageSharp/Formats/IDecoderOptions.cs b/src/ImageSharp/Formats/IDecoderOptions.cs new file mode 100644 index 000000000..cdfd90d5e --- /dev/null +++ b/src/ImageSharp/Formats/IDecoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared decoder options. + /// + public interface IDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/IEncoderOptions.cs b/src/ImageSharp/Formats/IEncoderOptions.cs new file mode 100644 index 000000000..0fd3d1c43 --- /dev/null +++ b/src/ImageSharp/Formats/IEncoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared encoder options. + /// + public interface IEncoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index d723b82f0..df98870dd 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -19,7 +19,8 @@ namespace ImageSharp.Formats /// The pixel format. /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable; + /// The options for the decoder. + void Decode(Image image, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index edde5347e..918f0d273 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -19,7 +19,8 @@ namespace ImageSharp.Formats /// The pixel format. /// The to encode from. /// The to encode the image data to. - void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable; + /// The options for the encoder. + void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 7adea78b2..af31eff79 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -8,6 +8,8 @@ namespace ImageSharp using System.Diagnostics; using System.IO; + using Formats; + /// /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha /// packed into a single unsigned integer value. @@ -35,27 +37,179 @@ namespace ImageSharp /// /// The stream containing image information. /// + /// Thrown if the is null. + public Image(Stream stream) + : base(stream, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(Stream stream, IDecoderOptions options) + : base(stream, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(Stream stream, Configuration configuration) + : base(stream, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(Stream stream, Configuration configuration = null) - : base(stream, configuration) + public Image(Stream stream, IDecoderOptions options, Configuration configuration) + : base(stream, options, configuration) { } +#if !NETSTANDARD1_1 + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// Thrown if the is null. + public Image(string filePath) + : base(filePath, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options) + : base(filePath, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, Configuration configuration) + : base(filePath, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// + /// The options for the decoder. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options, Configuration configuration) + : base(filePath, options, configuration) + { + } +#endif + /// /// Initializes a new instance of the class. /// /// /// The byte array containing image information. /// + /// Thrown if the is null. + public Image(byte[] bytes) + : base(bytes, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(byte[] bytes, IDecoderOptions options) + : base(bytes, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(byte[] bytes, Configuration configuration) + : base(bytes, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(byte[] bytes, Configuration configuration = null) - : base(bytes, configuration) + public Image(byte[] bytes, IDecoderOptions options, Configuration configuration) + : base(bytes, options, configuration) { } diff --git a/src/ImageSharp/Image/IImage.cs b/src/ImageSharp/Image/IImage.cs new file mode 100644 index 000000000..55abdb244 --- /dev/null +++ b/src/ImageSharp/Image/IImage.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Formats; + + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + internal interface IImage : IImageBase + { + /// + /// Gets the currently loaded image format. + /// + IImageFormat CurrentImageFormat { get; } + + /// + /// Gets the meta data of the image. + /// + ImageMetaData MetaData { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/IImageBase.cs b/src/ImageSharp/Image/IImageBase.cs new file mode 100644 index 000000000..707fea235 --- /dev/null +++ b/src/ImageSharp/Image/IImageBase.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + public interface IImageBase + { + /// + /// Gets the representing the bounds of the image. + /// + Rectangle Bounds { get; } + + /// + /// Gets or sets the maximum allowable width in pixels. + /// + int MaxWidth { get; set; } + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + int MaxHeight { get; set; } + + /// + /// Gets the width in pixels. + /// + int Width { get; } + + /// + /// Gets the height in pixels. + /// + int Height { get; } + + /// + /// Gets the pixel ratio made up of the width and height. + /// + double PixelRatio { get; } + + /// + /// Gets the configuration providing initialization code which allows extending the library. + /// + Configuration Configuration { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/IImageBase{TColor}.cs b/src/ImageSharp/Image/IImageBase{TColor}.cs index bd5b31712..e894fba4a 100644 --- a/src/ImageSharp/Image/IImageBase{TColor}.cs +++ b/src/ImageSharp/Image/IImageBase{TColor}.cs @@ -11,11 +11,13 @@ namespace ImageSharp /// Encapsulates the basic properties and methods required to manipulate images in varying formats. /// /// The pixel format. - public interface IImageBase : IImageBase - where TColor : struct, IPackedPixel, IEquatable + public interface IImageBase : IImageBase, IDisposable + where TColor : struct, IPixel { /// /// Gets the pixels as an array of the given packed pixel format. + /// Important. Due to the nature in the way this is constructed do not rely on the length + /// of the array for calculations. Use Width * Height. /// TColor[] Pixels { get; } @@ -29,35 +31,6 @@ namespace ImageSharp /// void InitPixels(int width, int height); - /// - /// Sets the pixel array of the image to the given value. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// The array with pixels. Must be a multiple of the width and height. - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// - void SetPixels(int width, int height, TColor[] pixels); - - /// - /// Sets the pixel array of the image to the given value, creating a copy of - /// the original pixels. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// The array with pixels. Must be a multiple of four times the width and height. - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// - void ClonePixels(int width, int height, TColor[] pixels); - /// /// Locks the image providing access to the pixels. /// @@ -67,53 +40,4 @@ namespace ImageSharp /// The PixelAccessor Lock(); } - - /// - /// Encapsulates the basic properties and methods required to manipulate images. - /// - public interface IImageBase - { - /// - /// Gets the representing the bounds of the image. - /// - Rectangle Bounds { get; } - - /// - /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. - /// - int Quality { get; set; } - - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - int FrameDelay { get; set; } - - /// - /// Gets or sets the maximum allowable width in pixels. - /// - int MaxWidth { get; set; } - - /// - /// Gets or sets the maximum allowable height in pixels. - /// - int MaxHeight { get; set; } - - /// - /// Gets the width in pixels. - /// - int Width { get; } - - /// - /// Gets the height in pixels. - /// - int Height { get; } - - /// - /// Gets the pixel ratio made up of the width and height. - /// - double PixelRatio { get; } - } } \ No newline at end of file diff --git a/src/ImageSharp/Image/IImageFrame.cs b/src/ImageSharp/Image/IImageFrame.cs new file mode 100644 index 000000000..bf3261d93 --- /dev/null +++ b/src/ImageSharp/Image/IImageFrame.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + internal interface IImageFrame : IImageBase + { + /// + /// Gets the meta data of the image. + /// + ImageFrameMetaData MetaData { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/IImageFrame{TColor}.cs b/src/ImageSharp/Image/IImageFrame{TColor}.cs deleted file mode 100644 index 6ebda36c8..000000000 --- a/src/ImageSharp/Image/IImageFrame{TColor}.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - /// - /// Represents a single frame in a animation. - /// - /// The pixel format. - public interface IImageFrame : IImageBase - where TColor : struct, IPackedPixel, IEquatable - { - } -} diff --git a/src/ImageSharp/Image/IImageProcessor.cs b/src/ImageSharp/Image/IImageProcessor.cs index fae0c23b8..0440cdd76 100644 --- a/src/ImageSharp/Image/IImageProcessor.cs +++ b/src/ImageSharp/Image/IImageProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing /// /// The pixel format. public interface IImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets or sets the parallel options for processing tasks in parallel. diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 8feaabdab..2badc008a 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Diagnostics; + using Processing; /// /// The base class of all images. Encapsulates the basic properties and methods required to manipulate @@ -15,13 +16,24 @@ namespace ImageSharp /// The pixel format. [DebuggerDisplay("Image: {Width}x{Height}")] public abstract class ImageBase : IImageBase - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The image pixels /// private TColor[] pixelBuffer; + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + /// /// Initializes a new instance of the class. /// @@ -41,7 +53,7 @@ namespace ImageSharp /// /// The configuration providing initialization code which allows extending the library. /// - /// + /// /// Thrown if either or are less than or equal to 0. /// protected ImageBase(int width, int height, Configuration configuration = null) @@ -67,11 +79,12 @@ namespace ImageSharp this.Height = other.Height; this.CopyProperties(other); - // Copy the pixels. Unsafe.CopyBlock gives us a nice speed boost here. - this.pixelBuffer = new TColor[this.Width * this.Height]; + // Rent then copy the pixels. Unsafe.CopyBlock gives us a nice speed boost here. + this.RentPixels(); using (PixelAccessor sourcePixels = other.Lock()) using (PixelAccessor target = this.Lock()) { + // Check we can do this without crashing sourcePixels.CopyTo(target); } } @@ -97,17 +110,34 @@ namespace ImageSharp /// public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); - /// - public int Quality { get; set; } - - /// - public int FrameDelay { get; set; } - /// /// Gets the configuration providing initialization code which allows extending the library. /// public Configuration Configuration { get; private set; } + /// + /// Applies the processor. + /// + /// The processor. + /// The rectangle. + public virtual void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + processor.Apply(this, rectangle); + } + + /// + public void Dispose() + { + this.Dispose(true); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + /// public void InitPixels(int width, int height) { @@ -116,63 +146,91 @@ namespace ImageSharp this.Width = width; this.Height = height; - this.pixelBuffer = new TColor[width * height]; + this.RentPixels(); } /// - public void SetPixels(int width, int height, TColor[] pixels) + public virtual PixelAccessor Lock() { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - Guard.NotNull(pixels, nameof(pixels)); + return new PixelAccessor(this); + } - if (pixels.Length != width * height) - { - throw new ArgumentException("Pixel array must have the length of Width * Height."); - } + /// + /// Switches the buffers used by the image and the PixelAccessor meaning that the Image will "own" the buffer from the PixelAccessor and the PixelAccessor will now own the Images buffer. + /// + /// The pixel source. + internal void SwapPixelsBuffers(PixelAccessor pixelSource) + { + Guard.NotNull(pixelSource, nameof(pixelSource)); - this.Width = width; - this.Height = height; - this.pixelBuffer = pixels; + int newWidth = pixelSource.Width; + int newHeight = pixelSource.Height; + + // Push my memory into the accessor (which in turn unpins the old puffer ready for the images use) + TColor[] newPixels = pixelSource.ReturnCurrentPixelsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer); + this.Width = newWidth; + this.Height = newHeight; + this.pixelBuffer = newPixels; } - /// - public void ClonePixels(int width, int height, TColor[] pixels) + /// + /// Copies the properties from the other . + /// + /// + /// The other to copy the properties from. + /// + protected void CopyProperties(IImageBase other) { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - Guard.NotNull(pixels, nameof(pixels)); + DebugGuard.NotNull(other, nameof(other)); + + this.Configuration = other.Configuration; + } - if (pixels.Length != width * height) + /// + /// Releases any unmanaged resources from the inheriting class. + /// + protected virtual void ReleaseUnmanagedResources() + { + // TODO release unmanaged resources here + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + protected virtual void Dispose(bool disposing) + { + if (this.isDisposed) { - throw new ArgumentException("Pixel array must have the length of Width * Height."); + return; } - this.Width = width; - this.Height = height; + this.ReleaseUnmanagedResources(); - // Copy the pixels. TODO: use Unsafe.Copy. - this.pixelBuffer = new TColor[pixels.Length]; - Array.Copy(pixels, this.pixelBuffer, pixels.Length); + if (disposing) + { + this.ReturnPixels(); + } + + // Note disposing is done. + this.isDisposed = true; } - /// - public virtual PixelAccessor Lock() + /// + /// Rents the pixel array from the pool. + /// + private void RentPixels() { - return new PixelAccessor(this); + this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); } /// - /// Copies the properties from the other . + /// Returns the rented pixel array back to the pool. /// - /// - /// The other to copy the properties from. - /// - protected void CopyProperties(ImageBase other) + private void ReturnPixels() { - this.Configuration = other.Configuration; - this.Quality = other.Quality; - this.FrameDelay = other.FrameDelay; + PixelDataPool.Return(this.pixelBuffer); + this.pixelBuffer = null; } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageFrame{TColor}.cs b/src/ImageSharp/Image/ImageFrame{TColor}.cs index 809eca1a4..2712dc687 100644 --- a/src/ImageSharp/Image/ImageFrame{TColor}.cs +++ b/src/ImageSharp/Image/ImageFrame{TColor}.cs @@ -13,8 +13,8 @@ namespace ImageSharp /// Represents a single frame in a animation. /// /// The pixel format. - public class ImageFrame : ImageBase - where TColor : struct, IPackedPixel, IEquatable + public class ImageFrame : ImageBase, IImageFrame + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -38,6 +38,11 @@ namespace ImageSharp { } + /// + /// Gets the meta data of the frame. + /// + public ImageFrameMetaData MetaData { get; private set; } = new ImageFrameMetaData(); + /// public override string ToString() { @@ -51,15 +56,12 @@ namespace ImageSharp /// The pixel format. /// The public ImageFrame To(Func scaleFunc = null) - where TColor2 : struct, IPackedPixel, IEquatable + where TColor2 : struct, IPixel { scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); - ImageFrame target = new ImageFrame(this.Width, this.Height, this.Configuration) - { - Quality = this.Quality, - FrameDelay = this.FrameDelay - }; + ImageFrame target = new ImageFrame(this.Width, this.Height, this.Configuration); + target.CopyProperties(this); using (PixelAccessor pixels = this.Lock()) using (PixelAccessor targetPixels = target.Lock()) @@ -90,5 +92,18 @@ namespace ImageSharp { return new ImageFrame(this); } + + /// + /// Copies the properties from the other . + /// + /// + /// The other to copy the properties from. + /// + private void CopyProperties(IImageFrame other) + { + base.CopyProperties(other); + + this.MetaData = new ImageFrameMetaData(other.MetaData); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageProcessingExtensions.cs b/src/ImageSharp/Image/ImageProcessingExtensions.cs index d9073ad16..ff3ecd5ee 100644 --- a/src/ImageSharp/Image/ImageProcessingExtensions.cs +++ b/src/ImageSharp/Image/ImageProcessingExtensions.cs @@ -22,32 +22,9 @@ namespace ImageSharp /// The processor to apply to the image. /// The . public static Image Apply(this Image source, IImageProcessor processor) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - return Apply(source, source.Bounds, processor); - } - - /// - /// Applies the processor to the image. - /// This method does not resize the target image. - /// - /// The pixel format. - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The processors to apply to the image. - /// The . - public static Image Apply(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - where TColor : struct, IPackedPixel, IEquatable - { - processor.Apply(source, sourceRectangle); - - foreach (ImageFrame sourceFrame in source.Frames) - { - processor.Apply(sourceFrame, sourceRectangle); - } - + source.ApplyProcessor(processor, source.Bounds); return source; } } diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 6bde8c3a5..27dee5434 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -16,27 +16,16 @@ namespace ImageSharp using System.Threading.Tasks; using Formats; + using Processing; /// /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. /// /// The pixel format. [DebuggerDisplay("Image: {Width}x{Height}")] - public class Image : ImageBase - where TColor : struct, IPackedPixel, IEquatable + public class Image : ImageBase, IImage + where TColor : struct, IPixel { - /// - /// The default horizontal resolution value (dots per inch) in x direction. - /// The default value is 96 dots per inch. - /// - public const double DefaultHorizontalResolution = 96; - - /// - /// The default vertical resolution value (dots per inch) in y direction. - /// The default value is 96 dots per inch. - /// - public const double DefaultVerticalResolution = 96; - /// /// Initializes a new instance of the class /// with the height and the width of the image. @@ -63,15 +52,169 @@ namespace ImageSharp /// /// The stream containing image information. /// + /// Thrown if the is null. + public Image(Stream stream) + : this(stream, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(Stream stream, IDecoderOptions options) + : this(stream, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(Stream stream, Configuration configuration) + : this(stream, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(Stream stream, Configuration configuration = null) + public Image(Stream stream, IDecoderOptions options, Configuration configuration) : base(configuration) { Guard.NotNull(stream, nameof(stream)); - this.Load(stream); + this.Load(stream, options); + } + +#if !NETSTANDARD1_1 + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// Thrown if the is null. + public Image(string filePath) + : this(filePath, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options) + : this(filePath, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, Configuration configuration) + : this(filePath, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// + /// The options for the decoder. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options, Configuration configuration) + : base(configuration) + { + Guard.NotNull(filePath, nameof(filePath)); + using (var fs = File.OpenRead(filePath)) + { + this.Load(fs, options); + } + } +#endif + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// Thrown if the is null. + public Image(byte[] bytes) + : this(bytes, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(byte[] bytes, IDecoderOptions options) + : this(bytes, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(byte[] bytes, Configuration configuration) + : this(bytes, null, configuration) + { } /// @@ -80,18 +223,21 @@ namespace ImageSharp /// /// The byte array containing image information. /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(byte[] bytes, Configuration configuration = null) + public Image(byte[] bytes, IDecoderOptions options, Configuration configuration) : base(configuration) { Guard.NotNull(bytes, nameof(bytes)); using (MemoryStream stream = new MemoryStream(bytes, false)) { - this.Load(stream); + this.Load(stream, options); } } @@ -128,18 +274,9 @@ namespace ImageSharp } /// - /// Gets or sets the resolution of the image in x- direction. It is defined as - /// number of dots per inch and should be an positive value. - /// - /// The density of the image in x- direction. - public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; - - /// - /// Gets or sets the resolution of the image in y- direction. It is defined as - /// number of dots per inch and should be an positive value. + /// Gets the meta data of the image. /// - /// The density of the image in y- direction. - public double VerticalResolution { get; set; } = DefaultVerticalResolution; + public ImageMetaData MetaData { get; private set; } = new ImageMetaData(); /// /// Gets the width of the image in inches. It is calculated as the width of the image @@ -147,20 +284,7 @@ namespace ImageSharp /// the default value is used. /// /// The width of the image in inches. - public double InchWidth - { - get - { - double resolution = this.HorizontalResolution; - - if (resolution <= 0) - { - resolution = DefaultHorizontalResolution; - } - - return this.Width / resolution; - } - } + public double InchWidth => this.Width / this.MetaData.HorizontalResolution; /// /// Gets the height of the image in inches. It is calculated as the height of the image @@ -168,20 +292,7 @@ namespace ImageSharp /// the default value is used. /// /// The height of the image in inches. - public double InchHeight - { - get - { - double resolution = this.VerticalResolution; - - if (resolution <= 0) - { - resolution = DefaultVerticalResolution; - } - - return this.Height / resolution; - } - } + public double InchHeight => this.Height / this.MetaData.VerticalResolution; /// /// Gets a value indicating whether this image is animated. @@ -191,33 +302,31 @@ namespace ImageSharp /// public bool IsAnimated => this.Frames.Count > 0; - /// - /// Gets or sets the number of times any animation is repeated. - /// 0 means to repeat indefinitely. - /// - public ushort RepeatCount { get; set; } - /// /// Gets the other frames for the animation. /// /// The list of frame images. public IList> Frames { get; } = new List>(); - /// - /// Gets the list of properties for storing meta information about this image. - /// - /// A list of image properties. - public IList Properties { get; } = new List(); - /// /// Gets the currently loaded image format. /// public IImageFormat CurrentImageFormat { get; internal set; } /// - /// Gets or sets the Exif profile. + /// Applies the processor to the image. /// - public ExifProfile ExifProfile { get; set; } + /// The processor to apply to the image. + /// The structure that specifies the portion of the image object to draw. + public override void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + // we want to put this on on here as it gives us a really go place to test/verify processor settings + base.ApplyProcessor(processor, rectangle); + foreach (ImageFrame sourceFrame in this.Frames) + { + sourceFrame.ApplyProcessor(processor, rectangle); + } + } /// /// Saves the image to the given stream using the currently loaded image format. @@ -226,9 +335,21 @@ namespace ImageSharp /// Thrown if the stream is null. /// The public Image Save(Stream stream) + { + return this.Save(stream, (IEncoderOptions)null); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + /// The + public Image Save(Stream stream, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream); + this.CurrentImageFormat.Encoder.Encode(this, stream, options); return this; } @@ -237,12 +358,24 @@ namespace ImageSharp /// /// The stream to save the image to. /// The format to save the image as. - /// Thrown if the stream is null. /// The public Image Save(Stream stream, IImageFormat format) + { + return this.Save(stream, format, null); + } + + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// The options for the encoder. + /// The + public Image Save(Stream stream, IImageFormat format, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); - format.Encoder.Encode(this, stream); + Guard.NotNull(format, nameof(format)); + format.Encoder.Encode(this, stream, options); return this; } @@ -251,14 +384,30 @@ namespace ImageSharp /// /// The stream to save the image to. /// The encoder to save the image with. - /// Thrown if the stream is null. + /// Thrown if the stream or encoder is null. /// /// The . /// public Image Save(Stream stream, IImageEncoder encoder) + { + return this.Save(stream, encoder, null); + } + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// The options for the encoder. + /// Thrown if the stream or encoder is null. + /// + /// The . + /// + public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); - encoder.Encode(this, stream); + Guard.NotNull(encoder, nameof(encoder)); + encoder.Encode(this, stream, options); // Reset to the start of the stream. if (stream.CanSeek) @@ -269,6 +418,96 @@ namespace ImageSharp return this; } +#if !NETSTANDARD1_1 + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// Thrown if the stream is null. + /// The + public Image Save(string filePath) + { + return this.Save(filePath, (IEncoderOptions)null); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + /// The + public Image Save(string filePath, IEncoderOptions options) + { + string ext = Path.GetExtension(filePath).Trim('.'); + IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); + if (format == null) + { + throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); + } + + return this.Save(filePath, format); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The format to save the image as. + /// Thrown if the format is null. + /// The + public Image Save(string filePath, IImageFormat format) + { + return this.Save(filePath, format, null); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The format to save the image as. + /// The options for the encoder. + /// Thrown if the format is null. + /// The + public Image Save(string filePath, IImageFormat format, IEncoderOptions options) + { + Guard.NotNull(format, nameof(format)); + using (FileStream fs = File.Create(filePath)) + { + return this.Save(fs, format); + } + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the encoder is null. + /// The + public Image Save(string filePath, IImageEncoder encoder) + { + return this.Save(filePath, encoder, null); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The encoder to save the image with. + /// The options for the encoder. + /// Thrown if the encoder is null. + /// The + public Image Save(string filePath, IImageEncoder encoder, IEncoderOptions options) + { + Guard.NotNull(encoder, nameof(encoder)); + using (FileStream fs = File.Create(filePath)) + { + return this.Save(fs, encoder); + } + } +#endif + /// public override string ToString() { @@ -297,19 +536,12 @@ namespace ImageSharp /// The pixel format. /// The public Image To(Func scaleFunc = null) - where TColor2 : struct, IPackedPixel, IEquatable + where TColor2 : struct, IPixel { scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); - Image target = new Image(this.Width, this.Height, this.Configuration) - { - Quality = this.Quality, - FrameDelay = this.FrameDelay, - HorizontalResolution = this.HorizontalResolution, - VerticalResolution = this.VerticalResolution, - CurrentImageFormat = this.CurrentImageFormat, - RepeatCount = this.RepeatCount - }; + Image target = new Image(this.Width, this.Height, this.Configuration); + target.CopyProperties(this); using (PixelAccessor pixels = this.Lock()) using (PixelAccessor targetPixels = target.Lock()) @@ -329,57 +561,58 @@ namespace ImageSharp }); } - if (this.ExifProfile != null) + for (int i = 0; i < this.Frames.Count; i++) { - target.ExifProfile = new ExifProfile(this.ExifProfile); - } - - foreach (ImageFrame frame in this.Frames) - { - target.Frames.Add(frame.To()); + target.Frames.Add(this.Frames[i].To()); } return target; } /// - /// Copies the properties from the other . + /// Creates a new from this instance /// - /// - /// The other to copy the properties from. - /// - internal void CopyProperties(Image other) + /// The + internal virtual ImageFrame ToFrame() { - base.CopyProperties(other); - - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.CurrentImageFormat = other.CurrentImageFormat; - this.RepeatCount = other.RepeatCount; + return new ImageFrame(this); + } - if (other.ExifProfile != null) + /// + protected override void Dispose(bool disposing) + { + // ReSharper disable once ForCanBeConvertedToForeach + for (int i = 0; i < this.Frames.Count; i++) { - this.ExifProfile = new ExifProfile(other.ExifProfile); + this.Frames[i].Dispose(); } + + base.Dispose(disposing); } /// - /// Creates a new from this instance + /// Copies the properties from the other . /// - /// The - internal virtual ImageFrame ToFrame() + /// + /// The other to copy the properties from. + /// + private void CopyProperties(IImage other) { - return new ImageFrame(this); + base.CopyProperties(other); + + this.CurrentImageFormat = other.CurrentImageFormat; + this.MetaData = new ImageMetaData(other.MetaData); } /// /// Loads the image from the given stream. /// /// The stream containing image information. + /// The options for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// - private void Load(Stream stream) + private void Load(Stream stream, IDecoderOptions options) { if (!this.Configuration.ImageFormats.Any()) { @@ -393,7 +626,7 @@ namespace ImageSharp if (stream.CanSeek) { - if (this.Decode(stream)) + if (this.Decode(stream, options)) { return; } @@ -406,7 +639,7 @@ namespace ImageSharp stream.CopyTo(ms); ms.Position = 0; - if (this.Decode(ms)) + if (this.Decode(ms, options)) { return; } @@ -428,10 +661,11 @@ namespace ImageSharp /// Decodes the image stream to the current image. /// /// The stream. + /// The options for the decoder. /// /// The . /// - private bool Decode(Stream stream) + private bool Decode(Stream stream, IDecoderOptions options) { int maxHeaderSize = this.Configuration.MaxHeaderSize; if (maxHeaderSize <= 0) @@ -458,7 +692,7 @@ namespace ImageSharp return false; } - format.Decoder.Decode(this, stream); + format.Decoder.Decode(this, stream, options); this.CurrentImageFormat = format; return true; } diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 162891442..e104b8ae7 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -16,23 +16,13 @@ namespace ImageSharp /// /// The pixel format. public unsafe class PixelAccessor : IDisposable - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - /// - /// The pointer to the pixel buffer. - /// - private IntPtr dataPointer; - /// /// The position of the first pixel in the image. /// private byte* pixelsBase; - /// - /// Provides a way to access the pixels from unmanaged memory. - /// - private GCHandle pixelsHandle; - /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -44,6 +34,11 @@ namespace ImageSharp /// private bool isDisposed; + /// + /// The containing the pixel data. + /// + private PinnedBuffer pixelBuffer; + /// /// Initializes a new instance of the class. /// @@ -54,40 +49,34 @@ namespace ImageSharp Guard.MustBeGreaterThan(image.Width, 0, "image width"); Guard.MustBeGreaterThan(image.Height, 0, "image height"); - this.Width = image.Width; - this.Height = image.Height; - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.pixelsBase = (byte*)this.dataPointer.ToPointer(); - this.PixelSize = Unsafe.SizeOf(); - this.RowStride = this.Width * this.PixelSize; + this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels); this.ParallelOptions = image.Configuration.ParallelOptions; } /// /// Initializes a new instance of the class. /// - /// Gets the width of the image represented by the pixel buffer. + /// The width of the image represented by the pixel buffer. + /// The height of the image represented by the pixel buffer. + public PixelAccessor(int width, int height) + : this(width, height, new PinnedBuffer(width * height)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. /// The pixel buffer. - public PixelAccessor(int width, int height, TColor[] pixels) + private PixelAccessor(int width, int height, PinnedBuffer pixels) { Guard.NotNull(pixels, nameof(pixels)); Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - if (pixels.Length != width * height) - { - throw new ArgumentException("Pixel array must have the length of Width * Height."); - } + this.SetPixelBufferUnsafe(width, height, pixels); - this.Width = width; - this.Height = height; - this.pixelsHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned); - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.pixelsBase = (byte*)this.dataPointer.ToPointer(); - this.PixelSize = Unsafe.SizeOf(); - this.RowStride = this.Width * this.PixelSize; this.ParallelOptions = Configuration.Default.ParallelOptions; } @@ -99,30 +88,35 @@ namespace ImageSharp this.Dispose(); } + /// + /// Gets the pixel buffer array. + /// + public TColor[] PixelBuffer => this.pixelBuffer.Array; + /// /// Gets the pointer to the pixel buffer. /// - public IntPtr DataPointer => this.dataPointer; + public IntPtr DataPointer => this.pixelBuffer.Pointer; /// /// Gets the size of a single pixel in the number of bytes. /// - public int PixelSize { get; } + public int PixelSize { get; private set; } /// /// Gets the width of one row in the number of bytes. /// - public int RowStride { get; } + public int RowStride { get; private set; } /// /// Gets the width of the image. /// - public int Width { get; } + public int Width { get; private set; } /// /// Gets the height of the image. /// - public int Height { get; } + public int Height { get; private set; } /// /// Gets the global parallel options for processing tasks in parallel. @@ -132,8 +126,8 @@ namespace ImageSharp /// /// Gets or sets the pixel at the specified position. /// - /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. - /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. public TColor this[int x, int y] { @@ -221,17 +215,11 @@ namespace ImageSharp return; } - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - this.dataPointer = IntPtr.Zero; - this.pixelsBase = null; - // Note disposing is done. this.isDisposed = true; + this.pixelBuffer.Dispose(); + // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to // take this object off the finalization queue @@ -248,6 +236,21 @@ namespace ImageSharp Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height)); } + /// + /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! + /// + /// The width. + /// The height. + /// The pixels. + /// Returns the old pixel data thats has gust been replaced. + /// If is true then caller is responsible for ensuring is called. + internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels) + { + TColor[] oldPixels = this.pixelBuffer.UnPinAndTakeArrayOwnership(); + this.SetPixelBufferUnsafe(width, height, pixels); + return oldPixels; + } + /// /// Copies the pixels to another of the same size. /// @@ -472,6 +475,28 @@ namespace ImageSharp return this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf()); } + private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels) + { + this.SetPixelBufferUnsafe(width, height, new PinnedBuffer(width * height, pixels)); + } + + /// + /// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!! + /// + /// The width. + /// The height. + /// The pixel buffer + private void SetPixelBufferUnsafe(int width, int height, PinnedBuffer pixels) + { + this.pixelBuffer = pixels; + this.pixelsBase = (byte*)pixels.Pointer; + + this.Width = width; + this.Height = height; + this.PixelSize = Unsafe.SizeOf(); + this.RowStride = this.Width * this.PixelSize; + } + /// /// Copy an area of pixels to the image. /// @@ -540,8 +565,8 @@ namespace ImageSharp /// Checks that the given area and offset are within the bounds of the image. /// /// The area. - /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. - /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. + /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. /// /// Thrown if the dimensions are not within the bounds of the image. /// @@ -551,21 +576,21 @@ namespace ImageSharp int width = Math.Min(area.Width, this.Width - x); if (width < 1) { - throw new ArgumentOutOfRangeException(nameof(width), width, $"Invalid area size specified."); + throw new ArgumentOutOfRangeException(nameof(width), width, "Invalid area size specified."); } int height = Math.Min(area.Height, this.Height - y); if (height < 1) { - throw new ArgumentOutOfRangeException(nameof(height), height, $"Invalid area size specified."); + throw new ArgumentOutOfRangeException(nameof(height), height, "Invalid area size specified."); } } /// /// Checks the coordinates to ensure they are within bounds. /// - /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. - /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. + /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. /// /// Thrown if the coordinates are not within the bounds of the image. /// diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 99e927375..c54de12d6 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -16,23 +16,8 @@ namespace ImageSharp /// /// The pixel format. public sealed unsafe class PixelArea : IDisposable - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - /// - /// True if was rented from by the constructor - /// - private readonly bool isBufferRented; - - /// - /// Provides a way to access the pixels from unmanaged memory. - /// - private readonly GCHandle pixelsHandle; - - /// - /// The pointer to the pixel buffer. - /// - private IntPtr dataPointer; - /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -44,6 +29,11 @@ namespace ImageSharp /// private bool isDisposed; + /// + /// The underlying buffer containing the raw pixel data. + /// + private PinnedBuffer byteBuffer; + /// /// Initializes a new instance of the class. /// @@ -76,14 +66,10 @@ namespace ImageSharp this.Height = height; this.ComponentOrder = componentOrder; this.RowStride = width * GetComponentCount(componentOrder); - this.Bytes = bytes; - this.Length = bytes.Length; - this.isBufferRented = false; - this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned); + this.Length = bytes.Length; // TODO: Is this the right value for Length? - // TODO: Why is Resharper warning us about an impure method call? - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.PixelBase = (byte*)this.dataPointer.ToPointer(); + this.byteBuffer = new PinnedBuffer(bytes); + this.PixelBase = (byte*)this.byteBuffer.Pointer; } /// @@ -132,27 +118,15 @@ namespace ImageSharp this.ComponentOrder = componentOrder; this.RowStride = (width * GetComponentCount(componentOrder)) + padding; this.Length = this.RowStride * height; - this.Bytes = BytesPool.Rent(this.Length); - this.isBufferRented = true; - this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned); - // TODO: Why is Resharper warning us about an impure method call? - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.PixelBase = (byte*)this.dataPointer.ToPointer(); - } - - /// - /// Finalizes an instance of the class. - /// - ~PixelArea() - { - this.Dispose(false); + this.byteBuffer = new PinnedBuffer(this.Length); + this.PixelBase = (byte*)this.byteBuffer.Pointer; } /// /// Gets the data in bytes. /// - public byte[] Bytes { get; } + public byte[] Bytes => this.byteBuffer.Array; /// /// Gets the length of the buffer. @@ -167,7 +141,7 @@ namespace ImageSharp /// /// Gets the pointer to the pixel buffer. /// - public IntPtr DataPointer => this.dataPointer; + public IntPtr DataPointer => this.byteBuffer.Pointer; /// /// Gets the height. @@ -189,25 +163,18 @@ namespace ImageSharp /// public int Width { get; } - /// - /// Gets the pool used to rent bytes, when it's not coming from an external source. - /// - // TODO: Use own pool? - private static ArrayPool BytesPool => ArrayPool.Shared; - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { - this.Dispose(true); + if (this.isDisposed) + { + return; + } - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); + this.byteBuffer.Dispose(); + this.isDisposed = true; } /// @@ -282,37 +249,5 @@ namespace ImageSharp $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}."); } } - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// If true, the object gets disposed. - private void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (this.PixelBase == null) - { - return; - } - - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - if (disposing && this.isBufferRented) - { - BytesPool.Return(this.Bytes); - } - - this.dataPointer = IntPtr.Zero; - this.PixelBase = null; - - this.isDisposed = true; - } } } \ No newline at end of file diff --git a/src/ImageSharp/ImageProcessor.cs b/src/ImageSharp/ImageProcessor.cs index 79bc3ee1e..a0766d1ed 100644 --- a/src/ImageSharp/ImageProcessor.cs +++ b/src/ImageSharp/ImageProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing /// /// The pixel format. public abstract class ImageProcessor : IImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public virtual ParallelOptions ParallelOptions { get; set; } diff --git a/src/ImageSharp/MetaData/IMetaData.cs b/src/ImageSharp/MetaData/IMetaData.cs new file mode 100644 index 000000000..38fd31349 --- /dev/null +++ b/src/ImageSharp/MetaData/IMetaData.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the metadata of an image frame. + /// + internal interface IMetaData + { + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + int FrameDelay { get; set; } + } +} diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs new file mode 100644 index 000000000..50119096e --- /dev/null +++ b/src/ImageSharp/MetaData/ImageFrameMetaData.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the metadata of an image frame. + /// + public sealed class ImageFrameMetaData : IMetaData + { + /// + /// Initializes a new instance of the class. + /// + internal ImageFrameMetaData() + { + } + + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + internal ImageFrameMetaData(ImageFrameMetaData other) + { + DebugGuard.NotNull(other, nameof(other)); + + this.FrameDelay = other.FrameDelay; + } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } + } +} diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs new file mode 100644 index 000000000..de1e42476 --- /dev/null +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -0,0 +1,147 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Collections.Generic; + + /// + /// Encapsulates the metadata of an image. + /// + public sealed class ImageMetaData : IMetaData + { + /// + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultVerticalResolution = 96; + + private double horizontalResolution; + private double verticalResolution; + + /// + /// Initializes a new instance of the class. + /// + internal ImageMetaData() + { + this.horizontalResolution = DefaultHorizontalResolution; + this.verticalResolution = DefaultVerticalResolution; + } + + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + internal ImageMetaData(ImageMetaData other) + { + DebugGuard.NotNull(other, nameof(other)); + + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.Quality = other.Quality; + this.FrameDelay = other.FrameDelay; + this.RepeatCount = other.RepeatCount; + + foreach (ImageProperty property in other.Properties) + { + this.Properties.Add(new ImageProperty(property)); + } + + if (other.ExifProfile != null) + { + this.ExifProfile = new ExifProfile(other.ExifProfile); + } + } + + /// + /// Gets or sets the resolution of the image in x- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution + { + get + { + return this.horizontalResolution; + } + + set + { + if (value > 0) + { + this.horizontalResolution = value; + } + } + } + + /// + /// Gets or sets the resolution of the image in y- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution + { + get + { + return this.verticalResolution; + } + + set + { + if (value > 0) + { + this.verticalResolution = value; + } + } + } + + /// + /// Gets or sets the Exif profile. + /// + public ExifProfile ExifProfile { get; set; } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } + + /// + /// Gets the list of properties for storing meta information about this image. + /// + /// A list of image properties. + public IList Properties { get; } = new List(); + + /// + /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the number of times any animation is repeated. + /// 0 means to repeat indefinitely. + /// + public ushort RepeatCount { get; set; } + + /// + /// Synchronizes the profiles with the current meta data. + /// + internal void SyncProfiles() + { + this.ExifProfile?.Sync(this); + } + } +} diff --git a/src/ImageSharp/Image/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs similarity index 91% rename from src/ImageSharp/Image/ImageProperty.cs rename to src/ImageSharp/MetaData/ImageProperty.cs index 7fda749e9..178794283 100644 --- a/src/ImageSharp/Image/ImageProperty.cs +++ b/src/ImageSharp/MetaData/ImageProperty.cs @@ -27,6 +27,21 @@ namespace ImageSharp this.Value = value; } + /// + /// Initializes a new instance of the class + /// by making a copy from another property. + /// + /// + /// The other to create this instance from. + /// + internal ImageProperty(ImageProperty other) + { + DebugGuard.NotNull(other, nameof(other)); + + this.Name = other.Name; + this.Value = other.Value; + } + /// /// Gets the name of this indicating which kind of /// information this property stores. diff --git a/src/ImageSharp/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifDataType.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifParts.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifParts.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs similarity index 90% rename from src/ImageSharp/Profiles/Exif/ExifProfile.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 507463985..b363286b0 100644 --- a/src/ImageSharp/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -121,7 +121,7 @@ namespace ImageSharp /// The . /// public Image CreateThumbnail() - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.InitializeValues(); @@ -224,6 +224,26 @@ namespace ImageSharp return writer.GetData(); } + /// + /// Synchronizes the profiles with the specified meta data. + /// + /// The meta data. + internal void Sync(ImageMetaData metaData) + { + this.SyncResolution(ExifTag.XResolution, metaData.HorizontalResolution); + this.SyncResolution(ExifTag.YResolution, metaData.VerticalResolution); + } + + private void SyncResolution(ExifTag tag, double resolution) + { + ExifValue value = this.GetValue(tag); + if (value != null) + { + Rational newResolution = new Rational(resolution, false); + this.SetValue(tag, newResolution); + } + } + private void InitializeValues() { if (this.values != null) diff --git a/src/ImageSharp/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifReader.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifTag.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifTag.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifTagDescriptionAttribute.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifValue.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifWriter.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs diff --git a/src/ImageSharp/Profiles/Exif/README.md b/src/ImageSharp/MetaData/Profiles/Exif/README.md similarity index 100% rename from src/ImageSharp/Profiles/Exif/README.md rename to src/ImageSharp/MetaData/Profiles/Exif/README.md diff --git a/src/ImageSharp/Quantizers/IQuantizer.cs b/src/ImageSharp/Quantizers/IQuantizer.cs index 878e9775b..9ee307266 100644 --- a/src/ImageSharp/Quantizers/IQuantizer.cs +++ b/src/ImageSharp/Quantizers/IQuantizer.cs @@ -7,12 +7,14 @@ namespace ImageSharp.Quantizers { using System; + using ImageSharp.Dithering; + /// /// Provides methods for allowing quantization of images pixels. /// /// The pixel format. public interface IQuantizer : IQuantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Quantize an image and return the resulting output pixels. @@ -25,6 +27,24 @@ namespace ImageSharp.Quantizers QuantizedImage Quantize(ImageBase image, int maxColors); } + /// + /// Provides methods for allowing dithering of quantized image pixels. + /// + /// The pixel format. + public interface IDitheredQuantizer : IQuantizer + where TColor : struct, IPixel + { + /// + /// Gets or sets a value indicating whether to apply dithering to the output image. + /// + bool Dither { get; set; } + + /// + /// Gets or sets the dithering algorithm to apply to the output image. + /// + IErrorDiffuser DitherType { get; set; } + } + /// /// Provides methods for allowing quantization of images pixels. /// diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs index 2cd2de4f5..c047e1af4 100644 --- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public sealed class OctreeQuantizer : Quantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The pixel buffer, used to reduce allocations. @@ -437,7 +437,7 @@ namespace ImageSharp.Quantizers { if (this.leaf) { - // TODO: Test Vector4 here + // This seems faster than using Vector4 byte r = (this.red / this.pixelCount).ToByte(); byte g = (this.green / this.pixelCount).ToByte(); byte b = (this.blue / this.pixelCount).ToByte(); diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs index 74aa6aade..8af638de3 100644 --- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs @@ -6,19 +6,34 @@ namespace ImageSharp.Quantizers { using System; + using System.Collections.Generic; + using System.Numerics; + using System.Runtime.CompilerServices; + + using ImageSharp.Dithering; /// /// Encapsulates methods to calculate the color palette of an image. /// /// The pixel format. - public abstract class Quantizer : IQuantizer - where TColor : struct, IPackedPixel, IEquatable + public abstract class Quantizer : IDitheredQuantizer + where TColor : struct, IPixel { + /// + /// A lookup table for colors + /// + private readonly Dictionary colorMap = new Dictionary(); + /// /// Flag used to indicate whether a single pass or two passes are needed for quantization. /// private readonly bool singlePass; + /// + /// The reduced image palette + /// + private TColor[] palette; + /// /// Initializes a new instance of the class. /// @@ -35,6 +50,12 @@ namespace ImageSharp.Quantizers this.singlePass = singlePass; } + /// + public bool Dither { get; set; } = true; + + /// + public IErrorDiffuser DitherType { get; set; } = new SierraLite(); + /// public virtual QuantizedImage Quantize(ImageBase image, int maxColors) { @@ -44,7 +65,6 @@ namespace ImageSharp.Quantizers int height = image.Height; int width = image.Width; byte[] quantizedPixels = new byte[width * height]; - TColor[] palette; using (PixelAccessor pixels = image.Lock()) { @@ -57,12 +77,24 @@ namespace ImageSharp.Quantizers } // Get the palette - palette = this.GetPalette(); + this.palette = this.GetPalette(); - this.SecondPass(pixels, quantizedPixels, width, height); + if (this.Dither) + { + // We clone the image as we don't want to alter the original. + using (Image clone = new Image(image)) + using (PixelAccessor clonedPixels = clone.Lock()) + { + this.SecondPass(clonedPixels, quantizedPixels, width, height); + } + } + else + { + this.SecondPass(pixels, quantizedPixels, width, height); + } } - return new QuantizedImage(width, height, palette, quantizedPixels); + return new QuantizedImage(width, height, this.palette, quantizedPixels); } /// @@ -99,6 +131,14 @@ namespace ImageSharp.Quantizers // And loop through each column for (int x = 0; x < width; x++) { + if (this.Dither) + { + // Apply the dithering matrix + TColor sourcePixel = source[x, y]; + TColor transformedPixel = this.palette[this.GetClosestColor(sourcePixel, this.palette, this.colorMap)]; + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + } + output[(y * source.Width) + x] = this.QuantizePixel(source[x, y]); } } @@ -129,8 +169,55 @@ namespace ImageSharp.Quantizers /// Retrieve the palette for the quantized image /// /// - /// The new color palette + /// /// protected abstract TColor[] GetPalette(); + + /// + /// Returns the closest color from the palette to the given color by calculating the Euclidean distance. + /// + /// The color. + /// The color palette. + /// The cache to store the result in. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected byte GetClosestColor(TColor pixel, TColor[] colorPalette, Dictionary cache) + { + // Check if the color is in the lookup table + if (this.colorMap.ContainsKey(pixel)) + { + return this.colorMap[pixel]; + } + + // Not found - loop through the palette and find the nearest match. + byte colorIndex = 0; + float leastDistance = int.MaxValue; + Vector4 vector = pixel.ToVector4(); + + for (int index = 0; index < colorPalette.Length; index++) + { + float distance = Vector4.Distance(vector, colorPalette[index].ToVector4()); + + // Greater... Move on. + if (!(distance < leastDistance)) + { + continue; + } + + colorIndex = (byte)index; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (Math.Abs(distance) < Constants.Epsilon) + { + break; + } + } + + // Now I have the index, pop it into the cache for next time + this.colorMap.Add(pixel, colorIndex); + + return colorIndex; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs index 6edb7801b..19f10aabb 100644 --- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs @@ -7,15 +7,14 @@ namespace ImageSharp.Quantizers { using System; using System.Collections.Generic; - using System.Numerics; /// /// Encapsulates methods to create a quantized image based upon the given palette. /// /// /// The pixel format. - public class PaletteQuantizer : Quantizer - where TColor : struct, IPackedPixel, IEquatable + public sealed class PaletteQuantizer : Quantizer + where TColor : struct, IPixel { /// /// The pixel buffer, used to reduce allocations. @@ -73,42 +72,7 @@ namespace ImageSharp.Quantizers /// protected override byte QuantizePixel(TColor pixel) { - byte colorIndex = 0; - TColor colorHash = pixel; - - // Check if the color is in the lookup table - if (this.colorMap.ContainsKey(colorHash)) - { - colorIndex = this.colorMap[colorHash]; - } - else - { - // Not found - loop through the palette and find the nearest match. - float leastDistance = int.MaxValue; - Vector4 vector = pixel.ToVector4(); - - for (int index = 0; index < this.colors.Length; index++) - { - float distance = Vector4.Distance(vector, this.colors[index].ToVector4()); - - if (distance < leastDistance) - { - colorIndex = (byte)index; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (Math.Abs(distance) < .0001F) - { - break; - } - } - } - - // Now I have the color, pop it into the cache for next time - this.colorMap.Add(colorHash, colorIndex); - } - - return colorIndex; + return this.GetClosestColor(pixel, this.colors, this.colorMap); } /// diff --git a/src/ImageSharp/Quantizers/Quantize.cs b/src/ImageSharp/Quantizers/Quantize.cs index 7a42090c7..f45cd3f79 100644 --- a/src/ImageSharp/Quantizers/Quantize.cs +++ b/src/ImageSharp/Quantizers/Quantize.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The maximum number of colors to return. Defaults to 256. /// The . public static Image Quantize(this Image source, Quantization mode = Quantization.Octree, int maxColors = 256) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IQuantizer quantizer; switch (mode) @@ -54,26 +54,30 @@ namespace ImageSharp /// The maximum number of colors to return. /// The . public static Image Quantize(this Image source, IQuantizer quantizer, int maxColors) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { QuantizedImage quantized = quantizer.Quantize(source, maxColors); - - int pixelCount = quantized.Pixels.Length; int palleteCount = quantized.Palette.Length - 1; - TColor[] pixels = new TColor[pixelCount]; - Parallel.For( - 0, - pixelCount, - source.Configuration.ParallelOptions, - i => - { - TColor color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; - pixels[i] = color; - }); + using (PixelAccessor pixels = new PixelAccessor(quantized.Width, quantized.Height)) + { + Parallel.For( + 0, + pixels.Height, + source.Configuration.ParallelOptions, + y => + { + for (int x = 0; x < pixels.Width; x++) + { + int i = x + (y * pixels.Width); + TColor color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; + pixels[x, y] = color; + } + }); - source.SetPixels(source.Width, source.Height, pixels); - return source; + source.SwapPixelsBuffers(pixels); + return source; + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/QuantizedImage.cs b/src/ImageSharp/Quantizers/QuantizedImage.cs index cc740f9be..528da7717 100644 --- a/src/ImageSharp/Quantizers/QuantizedImage.cs +++ b/src/ImageSharp/Quantizers/QuantizedImage.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public class QuantizedImage - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs index 55743f81e..d632cdd73 100644 --- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public sealed class WuQuantizer : IQuantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The index bits. diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs new file mode 100644 index 000000000..694a26f3d --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ImageSharp.Benchmarks.Color.Bulk +{ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using BenchmarkDotNet.Attributes; + + using Color = ImageSharp.Color; + + /// + /// Benchmark to measure the effect of using virtual bulk-copy calls inside PixelAccessor methods + /// + public unsafe class PixelAccessorVirtualCopy + { + abstract class CopyExecutor + { + internal abstract void VirtualCopy(BufferPointer destination, BufferPointer source, int count); + } + + class UnsafeCopyExecutor : CopyExecutor + { + [MethodImpl(MethodImplOptions.NoInlining)] + internal override unsafe void VirtualCopy(BufferPointer destination, BufferPointer source, int count) + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)count*4); + } + } + + private PixelAccessor pixelAccessor; + + private PixelArea area; + + private CopyExecutor executor; + + [Params(64, 256, 512)] + public int Width { get; set; } + + public int Height { get; set; } = 256; + + + [Setup] + public void Setup() + { + this.pixelAccessor = new PixelAccessor(this.Width, this.Height); + this.area = new PixelArea(this.Width / 2, this.Height, ComponentOrder.Xyzw); + this.executor = new UnsafeCopyExecutor(); + } + + [Cleanup] + public void Cleanup() + { + this.pixelAccessor.Dispose(); + this.area.Dispose(); + } + + [Benchmark(Baseline = true)] + public void CopyRawUnsafeInlined() + { + uint byteCount = (uint)this.area.Width * 4; + + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + byte* source = this.area.PixelBase + (y * this.area.RowStride); + byte* destination = this.GetRowPointer(targetX, targetY + y); + + Unsafe.CopyBlock(destination, source, byteCount); + } + } + + [Benchmark] + public void CopyBufferPointerUnsafeInlined() + { + uint byteCount = (uint)this.area.Width * 4; + + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + BufferPointer source = this.GetAreaRow(y); + BufferPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + } + } + + [Benchmark] + public void CopyBufferPointerUnsafeVirtual() + { + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + BufferPointer source = this.GetAreaRow(y); + BufferPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); + this.executor.VirtualCopy(destination, source, this.area.Width); + } + } + + private byte* GetRowPointer(int x, int y) + { + return (byte*)this.pixelAccessor.DataPointer + (((y * this.pixelAccessor.Width) + x) * Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private BufferPointer GetPixelAccessorRow(int x, int y) + { + return new BufferPointer( + this.pixelAccessor.PixelBuffer, + (void*)this.pixelAccessor.DataPointer, + (y * this.pixelAccessor.Width) + x + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private BufferPointer GetAreaRow(int y) + { + return new BufferPointer(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs index 2dd3acb0a..a10417b90 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawBeziers(pen, new[] { new PointF(10, 500), new PointF(30, 10), @@ -47,18 +47,22 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Beziers")] public void DrawLinesCore() { - CoreImage image = new CoreImage(800, 800); - - image.DrawBeziers(CoreColor.HotPink, 10, new[] { + using (CoreImage image = new CoreImage(800, 800)) + { + image.DrawBeziers( + CoreColor.HotPink, + 10, + new[] { new Vector2(10, 500), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 500) - }); + }); - using (MemoryStream ms = new MemoryStream()) - { - image.SaveAsBmp(ms); + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs index b418feb5b..146def363 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawLines(pen, new[] { new PointF(10, 10), new PointF(550, 50), @@ -46,17 +46,21 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Lines")] public void DrawLinesCore() { - CoreImage image = new CoreImage(800, 800); - - image.DrawLines(CoreColor.HotPink, 10, new[] { - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400) - }); - - using (MemoryStream ms = new MemoryStream()) + using (CoreImage image = new CoreImage(800, 800)) { - image.SaveAsBmp(ms); + image.DrawLines( + CoreColor.HotPink, + 10, + new[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + }); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs index 46526330f..e6c1ac0d6 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawPolygon(pen, new[] { new PointF(10, 10), new PointF(550, 50), @@ -45,17 +45,21 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Polygon")] public void DrawPolygonCore() { - CoreImage image = new CoreImage(800, 800); - - image.DrawPolygon(CoreColor.HotPink, 10, new[] { - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400) - }); - - using (MemoryStream ms = new MemoryStream()) + using (CoreImage image = new CoreImage(800, 800)) { - image.SaveAsBmp(ms); + image.DrawPolygon( + CoreColor.HotPink, + 10, + new[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + }); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs index b774d73cb..1eafbe077 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs @@ -44,17 +44,20 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill Polygon")] public void DrawSolidPolygonCore() { - CoreImage image = new CoreImage(800, 800); - image.FillPolygon(CoreColor.HotPink, - new[] { - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400) - }); - - using (MemoryStream ms = new MemoryStream()) + using (CoreImage image = new CoreImage(800, 800)) { - image.SaveAsBmp(ms); + image.FillPolygon( + CoreColor.HotPink, + new[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + }); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs index d9782249a..691955e8e 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs @@ -28,7 +28,6 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); graphics.FillRectangle(Brushes.HotPink, new Rectangle(10, 10, 190, 140)); } return destination.Size; @@ -38,25 +37,29 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill Rectangle")] public CoreSize FillRactangleCore() { - CoreImage image = new CoreImage(800, 800); - - image.Fill(CoreColor.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new CoreRectangle(10, 10, 190, 140))); + using (CoreImage image = new CoreImage(800, 800)) + { + image.Fill(CoreColor.HotPink, new CoreRectangle(10, 10, 190, 140)); - return new CoreSize(image.Width, image.Height); + return new CoreSize(image.Width, image.Height); + } } [Benchmark(Description = "ImageSharp Fill Rectangle - As Polygon")] public CoreSize FillPolygonCore() { - CoreImage image = new CoreImage(800, 800); - - image.FillPolygon(CoreColor.HotPink, new[] { - new Vector2(10, 10), - new Vector2(200, 10), - new Vector2(200, 150), - new Vector2(10, 150) }); + using (CoreImage image = new CoreImage(800, 800)) + { + image.FillPolygon( + CoreColor.HotPink, + new[] { + new Vector2(10, 10), + new Vector2(200, 10), + new Vector2(200, 150), + new Vector2(10, 150) }); - return new CoreSize(image.Width, image.Height); + return new CoreSize(image.Width, image.Height); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs index e5676cd13..589ac0cd4 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks using (Graphics graphics = Graphics.FromImage(destination)) { graphics.SmoothingMode = SmoothingMode.AntiAlias; - var brush = new HatchBrush(HatchStyle.BackwardDiagonal, Color.HotPink); + var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.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()) @@ -38,12 +38,14 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill with Pattern")] public void DrawPatternPolygon3Core() { - CoreImage image = new CoreImage(800, 800); - image.Fill(CoreBrushes.BackwardDiagonal(CoreColor.HotPink)); - - using (MemoryStream ms = new MemoryStream()) + using (CoreImage image = new CoreImage(800, 800)) { - image.SaveAsBmp(ms); + image.Fill(CoreBrushes.BackwardDiagonal(CoreColor.HotPink)); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs new file mode 100644 index 000000000..fce92e9be --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -0,0 +1,108 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Benchmarks.General +{ + using System; + + using BenchmarkDotNet.Attributes; + + public class Array2D + { + private float[] flatArray; + + private float[,] array2D; + + private float[][] jaggedData; + + private Fast2DArray fastData; + + [Params(4, 16, 128)] + public int Count { get; set; } + + public int Min { get; private set; } + public int Max { get; private set; } + + [Setup] + public void SetUp() + { + this.flatArray = new float[this.Count * this.Count]; + this.array2D = new float[this.Count, this.Count]; + this.jaggedData = new float[this.Count][]; + + for (int i = 0; i < this.Count; i++) + { + this.jaggedData[i] = new float[this.Count]; + } + + this.fastData = new Fast2DArray(this.array2D); + + this.Min = this.Count / 2 - 10; + this.Min = Math.Max(0, this.Min); + this.Max = this.Min + Math.Min(10, this.Count); + } + + [Benchmark(Description = "Emulated 2D array access using flat array")] + public float FlatArrayIndex() + { + float[] a = this.flatArray; + float s = 0; + int count = this.Count; + for (int i = this.Min; i < this.Max; i++) + { + for (int j = this.Min; j < this.Max; j++) + { + s += a[count * i + j]; + } + } + return s; + } + + [Benchmark(Baseline = true, Description = "Array access using 2D array")] + public float Array2DIndex() + { + float s = 0; + float[,] a = this.array2D; + for (int i = this.Min; i < this.Max; i++) + { + for (int j = this.Min; j < this.Max; j++) + { + s += a[i, j]; + } + } + return s; + } + + [Benchmark(Description = "Array access using a jagged array")] + public float ArrayJaggedIndex() + { + float s = 0; + float[][] a = this.jaggedData; + for (int i = this.Min; i < this.Max; i++) + { + for (int j = this.Min; j < this.Max; j++) + { + s += a[i][j]; + } + } + return s; + } + + [Benchmark(Description = "Array access using Fast2DArray")] + public float ArrayFastIndex() + { + float s = 0; + Fast2DArray a = this.fastData; + for (int i = this.Min; i < this.Max; i++) + { + for (int j = this.Min; j < this.Max; j++) + { + s += a[i, j]; + } + } + return s; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs index 88d47db51..72fd6dc24 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs @@ -6,12 +6,14 @@ namespace ImageSharp.Benchmarks.General { using System; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; + [Config(typeof(Config.Short))] public class ArrayCopy { - [Params(100, 1000, 10000)] + [Params(10, 100, 1000, 10000)] public int Count { get; set; } byte[] source; @@ -41,6 +43,12 @@ namespace ImageSharp.Benchmarks.General } } + [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() { @@ -50,5 +58,15 @@ namespace ImageSharp.Benchmarks.General 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); + } + } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs new file mode 100644 index 000000000..dd20e85d5 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class BitwiseOrUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint) i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] | v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i+=Vector.Count) + { + Vector a = new Vector(this.input, i); + a = Vector.BitwiseOr(a, v); + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs new file mode 100644 index 000000000..61582b7dc --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class DivFloat + { + private float[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + private float testValue; + + [Setup] + public void Setup() + { + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a / v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs new file mode 100644 index 000000000..75fa03c04 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class DivUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a / v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs new file mode 100644 index 000000000..151145e12 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class MulFloat + { + private float[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + private float testValue; + + [Setup] + public void Setup() + { + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs new file mode 100644 index 000000000..f7d6cf9b9 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class MulUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs new file mode 100644 index 000000000..b0ca181cd --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -0,0 +1,62 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + using System.Runtime.InteropServices; + + using BenchmarkDotNet.Attributes; + + public class ReinterpretUInt32AsFloat + { + private uint[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + [StructLayout(LayoutKind.Explicit)] + struct UIntFloatUnion + { + [FieldOffset(0)] + public float f; + + [FieldOffset(0)] + public uint i; + } + + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new float[this.InputSize]; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + UIntFloatUnion u = default(UIntFloatUnion); + for (int i = 0; i < this.input.Length; i++) + { + u.i = this.input[i]; + this.result[i] = u.f; + } + } + + [Benchmark] + public void Simd() + { + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + Vector b = Vector.AsVectorSingle(a); + b.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs index ae732d053..335d8247d 100644 --- a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs +++ b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs @@ -17,24 +17,26 @@ namespace ImageSharp.Benchmarks.Image [Benchmark(Description = "Copy by Pixel")] public CoreColor CopyByPixel() { - CoreImage source = new CoreImage(1024, 768); - CoreImage target = new CoreImage(1024, 768); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (CoreImage source = new CoreImage(1024, 768)) + using (CoreImage target = new CoreImage(1024, 768)) { - Parallel.For( - 0, - source.Height, - Configuration.Default.ParallelOptions, - y => - { - for (int x = 0; x < source.Width; x++) - { - targetPixels[x, y] = sourcePixels[x, y]; - } - }); + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + for (int x = 0; x < source.Width; x++) + { + targetPixels[x, y] = sourcePixels[x, y]; + } + }); - return targetPixels[0, 0]; + return targetPixels[0, 0]; + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs index b8f433a14..431bbeb07 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs @@ -43,8 +43,10 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.bmpBytes)) { - CoreImage image = new CoreImage(memoryStream); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs index 5f7050fb0..517915bac 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs @@ -29,37 +29,40 @@ namespace ImageSharp.Benchmarks.Image this.filter4 = new MemoryStream(File.ReadAllBytes("../ImageSharp.Tests/TestImages/Formats/Png/filter4.png")); } - private Image LoadPng(MemoryStream stream) + private Size LoadPng(MemoryStream stream) { - return new Image(stream); + using (Image image = new Image(stream)) + { + return new Size(image.Width, image.Height); + } } [Benchmark(Baseline = true, Description = "None-filtered PNG file")] - public Image PngFilter0() + public Size PngFilter0() { return LoadPng(filter0); } [Benchmark(Description = "Sub-filtered PNG file")] - public Image PngFilter1() + public Size PngFilter1() { return LoadPng(filter1); } [Benchmark(Description = "Up-filtered PNG file")] - public Image PngFilter2() + public Size PngFilter2() { return LoadPng(filter2); } [Benchmark(Description = "Average-filtered PNG file")] - public Image PngFilter3() + public Size PngFilter3() { return LoadPng(filter3); } [Benchmark(Description = "Paeth-filtered PNG file")] - public Image PngFilter4() + public Size PngFilter4() { return LoadPng(filter4); } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs index 3da6eb362..cb70213da 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs @@ -43,8 +43,10 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.gifBytes)) { - CoreImage image = new CoreImage(memoryStream); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs index b5a44d919..cbbe9c9f2 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs @@ -43,8 +43,10 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes)) { - CoreImage image = new CoreImage(memoryStream); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodePng.cs b/tests/ImageSharp.Benchmarks/Image/DecodePng.cs index f3bda758d..79c8dbc23 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodePng.cs @@ -43,8 +43,10 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.pngBytes)) { - CoreImage image = new CoreImage(memoryStream); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs index 5a77fbca0..b0a3b4499 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs @@ -31,6 +31,14 @@ namespace ImageSharp.Benchmarks.Image } } + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] public void BmpSystemDrawing() { diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Image/EncodeBmpMultiple.cs index 1775f144d..852e17572 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeBmpMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeBmpMultiple.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Benchmarks.Image protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] - public void EncodeGifImageSharp() + public void EncodeBmpImageSharp() { this.ForEachImageSharpImage( (img, ms) => @@ -29,7 +29,7 @@ namespace ImageSharp.Benchmarks.Image } [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] - public void EncodeGifSystemDrawing() + public void EncodeBmpSystemDrawing() { this.ForEachSystemDrawingImage( (img, ms) => diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs index 42feb085f..0810f3fe1 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs @@ -31,6 +31,14 @@ namespace ImageSharp.Benchmarks.Image } } + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + [Benchmark(Baseline = true, Description = "System.Drawing Gif")] public void GifSystemDrawing() { diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs index f1e3b2114..f835f9666 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs @@ -31,6 +31,14 @@ namespace ImageSharp.Benchmarks.Image } } + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] public void JpegSystemDrawing() { diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index 44ffba679..dd1882c80 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -31,6 +31,14 @@ namespace ImageSharp.Benchmarks.Image } } + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + [Benchmark(Baseline = true, Description = "System.Drawing Png")] public void PngSystemDrawing() { diff --git a/tests/ImageSharp.Benchmarks/Image/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Image/GetSetPixel.cs index ffd476007..78295e27d 100644 --- a/tests/ImageSharp.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageSharp.Benchmarks/Image/GetSetPixel.cs @@ -28,11 +28,13 @@ namespace ImageSharp.Benchmarks.Image [Benchmark(Description = "ImageSharp GetSet pixel")] public CoreColor ResizeCore() { - CoreImage image = new CoreImage(400, 400); - using (PixelAccessor imagePixels = image.Lock()) + using (CoreImage image = new CoreImage(400, 400)) { - imagePixels[200, 200] = CoreColor.White; - return imagePixels[200, 200]; + using (PixelAccessor imagePixels = image.Lock()) + { + imagePixels[200, 200] = CoreColor.White; + return imagePixels[200, 200]; + } } } } diff --git a/tests/ImageSharp.Benchmarks/Program.cs b/tests/ImageSharp.Benchmarks/Program.cs index 7ecf63274..789068426 100644 --- a/tests/ImageSharp.Benchmarks/Program.cs +++ b/tests/ImageSharp.Benchmarks/Program.cs @@ -6,6 +6,9 @@ namespace ImageSharp.Benchmarks { using BenchmarkDotNet.Running; + + using ImageSharp.Formats; + using System.Reflection; public class Program { @@ -17,7 +20,7 @@ namespace ImageSharp.Benchmarks /// public static void Main(string[] args) { - new BenchmarkSwitcher(typeof(Program).Assembly).Run(args); + new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs index 3a98569e7..a3cdef92e 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs @@ -38,9 +38,11 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Crop")] public CoreSize CropResizeCore() { - CoreImage image = new CoreImage(800, 800); - image.Crop(100, 100); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(800, 800)) + { + image.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 be76c13fd..28bec5124 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -28,6 +28,12 @@ namespace ImageSharp.Benchmarks } } + [Cleanup] + public void Cleanup() + { + this.image.Dispose(); + } + [Benchmark(Description = "ImageSharp DetectEdges")] public void ImageProcessorCoreDetectEdges() { diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs index 10fe1fc75..04570ce8f 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs @@ -37,17 +37,21 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Resize")] public CoreSize ResizeCore() { - CoreImage image = new CoreImage(2000, 2000); - image.Resize(400, 400); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(2000, 2000)) + { + image.Resize(400, 400); + return new CoreSize(image.Width, image.Height); + } } [Benchmark(Description = "ImageSharp Compand Resize")] public CoreSize ResizeCoreCompand() { - CoreImage image = new CoreImage(2000, 2000); - image.Resize(400, 400, true); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(2000, 2000)) + { + image.Resize(400, 400, true); + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/benchmark.sh b/tests/ImageSharp.Benchmarks/benchmark.sh new file mode 100755 index 000000000..1966475bc --- /dev/null +++ b/tests/ImageSharp.Benchmarks/benchmark.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Build in release mode +dotnet build -c Release -f netcoreapp1.1 + +# Run benchmarks +dotnet bin/Release/netcoreapp1.1/ImageSharp.Benchmarks.dll \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/project.json b/tests/ImageSharp.Benchmarks/project.json new file mode 100644 index 000000000..6a8be9f89 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/project.json @@ -0,0 +1,67 @@ +{ + "version": "1.0.0-*", + "description": "ImageSharp.Benchmarks Console Application", + "authors": [ "James.South" ], + "packOptions": { + "projectUrl": "https://github.com/JimBobSquarePants/ImageSharp", + "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", + "tags": [ + "Image Resize Crop Gif Jpg Jpeg Bitmap Png Core" + ] + }, + "buildOptions": { + "emitEntryPoint": true, + "allowUnsafe": true + }, + "dependencies": { + "ImageSharp": { + "target": "project" + }, + "ImageSharp.Drawing": { + "target": "project" + }, + "ImageSharp.Drawing.Paths": { + "target": "project" + }, + "ImageSharp.Formats.Jpeg": { + "target": "project" + }, + "ImageSharp.Formats.Png": { + "target": "project" + }, + "ImageSharp.Formats.Bmp": { + "target": "project" + }, + "ImageSharp.Formats.Gif": { + "target": "project" + }, + "ImageSharp.Processing": { + "target": "project" + } + }, + "commands": { + "ImageSharp.Benchmarks": "ImageSharp.Benchmarks" + }, + "frameworks": { + "net46": { + "dependencies": { + "BenchmarkDotNet.Diagnostics.Windows": "0.10.1" + }, + "frameworkAssemblies": { + "System.Drawing": "" + } + }, + "netcoreapp1.1": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.1.0-*" + }, + "BenchmarkDotNet": "0.10.2", + "CoreCompat.System.Drawing": "1.0.0-beta006", + "runtime.linux-x64.CoreCompat.System.Drawing": "1.0.0-beta009", + "System.Reflection": "4.3.0" + } + } + } +} diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 5391fff90..a1ae6bb0d 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -109,6 +109,10 @@ ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll True + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.3.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + True + ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll True @@ -173,6 +177,44 @@ + + ..\..\src\ImageSharp\bin\$(Configuration)\net461\ImageSharp.dll + + + ..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\ImageSharp.Drawing.dll + + + ..\..\src\ImageSharp.Drawing.Paths\bin\$(Configuration)\net461\ImageSharp.Drawing.Paths.dll + + + ..\..\src\ImageSharp.Drawing.Paths\bin\$(Configuration)\net461\SixLabors.Shapes.dll + + + ..\..\src\ImageSharp.Formats.Bmp\bin\$(Configuration)\net461\ImageSharp.Formats.Bmp.dll + + + ..\..\src\ImageSharp.Formats.Gif\bin\$(Configuration)\net461\ImageSharp.Formats.Gif.dll + + + ..\..\src\ImageSharp.Formats.Jpeg\bin\$(Configuration)\net461\ImageSharp.Formats.Jpeg.dll + + + ..\..\src\ImageSharp.Formats.Png\bin\$(Configuration)\net461\ImageSharp.Formats.Png.dll + + + ..\..\src\ImageSharp.Processing\bin\$(Configuration)\net461\ImageSharp.Processing.dll + + + + + Benchmarks\PixelAccessorVirtualCopy.cs + + + Tests\Colors\BulkPixelOperationsTests.cs + + + Tests\Common\PinnedBufferTests.cs + Tests\Drawing\PolygonTests.cs @@ -209,8 +251,8 @@ Tests\Formats\Jpg\YCbCrImageTests.cs - - Tests\Image\ImagePropertyTests.cs + + Tests\MetaData\ImagePropertyTests.cs Tests\Image\ImageTests.cs @@ -298,9 +340,7 @@ - - - + diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 48219902b..4d6d15925 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Sandbox46 using System; using System.Runtime.DesignerServices; + using ImageSharp.Benchmarks.Color.Bulk; using ImageSharp.Tests; using Xunit.Abstractions; @@ -36,7 +37,20 @@ namespace ImageSharp.Sandbox46 /// public static void Main(string[] args) { - RunDecodeJpegProfilingTests(); + // RunDecodeJpegProfilingTests(); + TestPixelAccessorCopyFromXyzw(); + Console.ReadLine(); + } + + private static void TestPixelAccessorCopyFromXyzw() + { + PixelAccessorVirtualCopy benchmark = new PixelAccessorVirtualCopy(); + benchmark.Width = 64; + benchmark.Setup(); + + benchmark.CopyRawUnsafeInlined(); + + benchmark.Cleanup(); } private static void RunDecodeJpegProfilingTests() diff --git a/tests/ImageSharp.Sandbox46/app.config b/tests/ImageSharp.Sandbox46/app.config index 5a049c66e..3328297a5 100644 --- a/tests/ImageSharp.Sandbox46/app.config +++ b/tests/ImageSharp.Sandbox46/app.config @@ -10,6 +10,10 @@ + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/packages.config b/tests/ImageSharp.Sandbox46/packages.config index 65ad74fa6..426f5f1b5 100644 --- a/tests/ImageSharp.Sandbox46/packages.config +++ b/tests/ImageSharp.Sandbox46/packages.config @@ -29,6 +29,7 @@ + diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs new file mode 100644 index 000000000..80d5952a1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -0,0 +1,340 @@ +namespace ImageSharp.Tests.Colors +{ + using System; + using System.Numerics; + + using Xunit; + + public class BulkPixelOperationsTests + { + public class Color : BulkPixelOperationsTests + { + // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: + public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + } + + public class Argb : BulkPixelOperationsTests + { + // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: + public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + } + + [Theory] + [WithBlankImages(1, 1, PixelTypes.All)] + public void GetGlobalInstance(TestImageProvider dummy) + where TColor:struct, IPixel + { + Assert.NotNull(BulkPixelOperations.Instance); + } + } + + public abstract class BulkPixelOperationsTests + where TColor : struct, IPixel + { + public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromVector4(int count) + { + Vector4[] source = CreateVector4TestData(count); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + expected[i].PackFromVector4(source[i]); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromVector4(s, d, count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackToVector4(int count) + { + TColor[] source = CreatePixelTestData(count); + Vector4[] expected = new Vector4[count]; + + for (int i = 0; i < count; i++) + { + expected[i] = source[i].ToVector4(); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.ToVector4(s, d, count) + ); + } + + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromXyzBytes(int count) + { + byte[] source = CreateByteTestData(count * 3); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + + expected[i].PackFromBytes(source[i3 + 0], source[i3 + 1], source[i3 + 2], 255); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromXyzBytes(s, d, count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackToXyzBytes(int count) + { + TColor[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 3]; + + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + source[i].ToXyzBytes(expected, i3); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.ToXyzBytes(s, d, count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromXyzwBytes(int count) + { + byte[] source = CreateByteTestData(count * 4); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + + expected[i].PackFromBytes(source[i4 + 0], source[i4 + 1], source[i4 + 2], source[i4 + 3]); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromXyzwBytes(s, d, count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackToXyzwBytes(int count) + { + TColor[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + source[i].ToXyzwBytes(expected, i4); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.ToXyzwBytes(s, d, count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromZyxBytes(int count) + { + byte[] source = CreateByteTestData(count * 3); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + + expected[i].PackFromBytes(source[i3 + 2], source[i3 + 1], source[i3 + 0], 255); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromZyxBytes(s, d, count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackToZyxBytes(int count) + { + TColor[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 3]; + + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + source[i].ToZyxBytes(expected, i3); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.ToZyxBytes(s, d, count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromZyxwBytes(int count) + { + byte[] source = CreateByteTestData(count * 4); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + + expected[i].PackFromBytes(source[i4 + 2], source[i4 + 1], source[i4 + 0], source[i4 + 3]); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromZyxwBytes(s, d, count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackToZyxwBytes(int count) + { + TColor[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + source[i].ToZyxwBytes(expected, i4); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.ToZyxwBytes(s, d, count) + ); + } + + + private class TestBuffers : IDisposable + where TSource : struct + where TDest : struct + { + public PinnedBuffer SourceBuffer { get; } + public PinnedBuffer ActualDestBuffer { get; } + public PinnedBuffer ExpectedDestBuffer { get; } + + public BufferPointer Source => this.SourceBuffer.Slice(); + public BufferPointer ActualDest => this.ActualDestBuffer.Slice(); + + public TestBuffers(TSource[] source, TDest[] expectedDest) + { + this.SourceBuffer = new PinnedBuffer(source); + this.ExpectedDestBuffer = new PinnedBuffer(expectedDest); + this.ActualDestBuffer = new PinnedBuffer(expectedDest.Length); + } + + public void Dispose() + { + this.SourceBuffer.Dispose(); + this.ActualDestBuffer.Dispose(); + this.ExpectedDestBuffer.Dispose(); + } + + public void Verify() + { + int count = this.ExpectedDestBuffer.Count; + TDest[] expected = this.ExpectedDestBuffer.Array; + TDest[] actual = this.ActualDestBuffer.Array; + for (int i = 0; i < count; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } + } + + private static void TestOperation( + TSource[] source, + TDest[] expected, + Action, BufferPointer, BufferPointer> action) + where TSource : struct + where TDest : struct + { + using (var buffers = new TestBuffers(source, expected)) + { + action(BulkPixelOperations.Instance, buffers.Source, buffers.ActualDest); + buffers.Verify(); + } + } + + private static Vector4[] CreateVector4TestData(int length) + { + Vector4[] result = new Vector4[length]; + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) + { + result[i] = GetVector(rnd); + } + return result; + } + + private static TColor[] CreatePixelTestData(int length) + { + TColor[] result = new TColor[length]; + + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetVector(rnd); + result[i].PackFromVector4(v); + } + + return result; + } + + private static byte[] CreateByteTestData(int length) + { + byte[] result = new byte[length]; + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) + { + result[i] = (byte)rnd.Next(255); + } + return result; + } + + private static Vector4 GetVector(Random rnd) + { + return new Vector4( + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble() + ); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs b/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs index ac1c3f351..08b9375f8 100644 --- a/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs @@ -132,7 +132,7 @@ namespace ImageSharp.Tests.Colors [MemberData(nameof(Vector3Data))] [MemberData(nameof(Float4Data))] [MemberData(nameof(Float3Data))] - public void ConstructorToVector4(IPackedVector packedVector, float[] expectedVector4Components) + public void ConstructorToVector4(IPixel packedVector, float[] expectedVector4Components) { // Arrange var precision = 2; diff --git a/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs new file mode 100644 index 000000000..899ce4f77 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Reflection; + + using ImageSharp.Colors.Spaces; + using Xunit; + public class ColorDefinitionTests + { + public static IEnumerable ColorNames => typeof(NamedColors).GetTypeInfo().GetFields().Select(x => new[] { x.Name }); + + [Theory] + [MemberData(nameof(ColorNames))] + public void AllColorsAreOnGenericAndBaseColor(string name) + { + FieldInfo generic = typeof(NamedColors).GetTypeInfo().GetField(name); + FieldInfo specific = typeof(Color).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"); + Color expected = (Color)generic.GetValue(null); + Color actual = (Color)specific.GetValue(null); + Assert.Equal(expected, actual); + } + } +} diff --git a/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs b/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs index a1057a2c2..ac5f8d6b4 100644 --- a/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs @@ -69,7 +69,7 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(Vector4PackData))] [MemberData(nameof(Vector3PackData))] - public void FromVector4ToVector4(IPackedVector packedVector, float[] vector4ComponentsToPack) + public void FromVector4ToVector4(IPixel packedVector, float[] vector4ComponentsToPack) { // Arrange var precision = 2; diff --git a/tests/ImageSharp.Tests/Colors/ColorTests.cs b/tests/ImageSharp.Tests/Colors/ColorTests.cs index ec75ae6f7..312e66466 100644 --- a/tests/ImageSharp.Tests/Colors/ColorTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorTests.cs @@ -23,10 +23,10 @@ namespace ImageSharp.Tests { Color color1 = new Color(0, 0, 0); Color color2 = new Color(0, 0, 0, 1F); - Color color3 = new Color("#000"); - Color color4 = new Color("#000F"); - Color color5 = new Color("#000000"); - Color color6 = new Color("#000000FF"); + Color color3 = Color.FromHex("#000"); + Color color4 = Color.FromHex("#000F"); + Color color5 = Color.FromHex("#000000"); + Color color6 = Color.FromHex("#000000FF"); Assert.Equal(color1, color2); Assert.Equal(color1, color3); @@ -43,9 +43,9 @@ namespace ImageSharp.Tests { Color color1 = new Color(255, 0, 0, 255); Color color2 = new Color(0, 0, 0, 255); - Color color3 = new Color("#000"); - Color color4 = new Color("#000000"); - Color color5 = new Color("#FF000000"); + Color color3 = Color.FromHex("#000"); + Color color4 = Color.FromHex("#000000"); + Color color5 = Color.FromHex("#FF000000"); Assert.NotEqual(color1, color2); Assert.NotEqual(color1, color3); @@ -71,12 +71,6 @@ namespace ImageSharp.Tests Assert.Equal(Math.Round(.133f * 255), color2.B); Assert.Equal(255, color2.A); - Color color3 = new Color("#FF0000"); - Assert.Equal(255, color3.R); - Assert.Equal(0, color3.G); - Assert.Equal(0, color3.B); - Assert.Equal(255, color3.A); - Color color4 = new Color(new Vector3(1, .1f, .133f)); Assert.Equal(255, color4.R); Assert.Equal(Math.Round(.1f * 255), color4.G); diff --git a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index a79ef620e..3e2b6fcd5 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests.Colors { using System; + using System.Diagnostics; using System.Numerics; using Xunit; diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs new file mode 100644 index 000000000..4b5847d53 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -0,0 +1,167 @@ +// ReSharper disable ObjectCreationAsStatement +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Common +{ + using System; + using System.Runtime.CompilerServices; + + using Xunit; + + public unsafe class BufferPointerTests + { + public struct Foo + { + public int A; + + public double B; + + internal static Foo[] CreateArray(int size) + { + Foo[] result = new Foo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new Foo() { A = i, B = i }; + } + return result; + } + } + + [Fact] + public void ConstructWithoutOffset() + { + Foo[] array = Foo.CreateArray(3); + fixed (Foo* p = array) + { + // Act: + BufferPointer ap = new BufferPointer(array, p); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal((IntPtr)p, ap.PointerAtOffset); + } + } + + [Fact] + public void ConstructWithOffset() + { + Foo[] array = Foo.CreateArray(3); + int offset = 2; + fixed (Foo* p = array) + { + // Act: + BufferPointer ap = new BufferPointer(array, p, offset); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal(offset, ap.Offset); + Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); + } + } + + [Fact] + public void Slice() + { + Foo[] array = Foo.CreateArray(5); + int offset0 = 2; + int offset1 = 2; + int totalOffset = offset0 + offset1; + fixed (Foo* p = array) + { + BufferPointer ap = new BufferPointer(array, p, offset0); + + // Act: + ap = ap.Slice(offset1); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal(totalOffset, ap.Offset); + Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset); + } + } + + public class Copy + { + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void GenericToOwnType(int count) + { + Foo[] source = Foo.CreateArray(count + 2); + Foo[] dest = new Foo[count + 5]; + + fixed (Foo* pSource = source) + fixed (Foo* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource); + BufferPointer apDest = new BufferPointer(dest, pDest); + + BufferPointer.Copy(apSource, apDest, count); + } + + Assert.Equal(source[0], dest[0]); + Assert.Equal(source[count-1], dest[count-1]); + Assert.NotEqual(source[count], dest[count]); + } + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void GenericToBytes(int count) + { + int destCount = count * sizeof(Foo); + Foo[] source = Foo.CreateArray(count + 2); + byte[] dest = new byte[destCount + sizeof(Foo) + 1]; + + fixed (Foo* pSource = source) + fixed (byte* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource); + BufferPointer apDest = new BufferPointer(dest, pDest); + + BufferPointer.Copy(apSource, apDest, count); + } + + Assert.True(ElementsAreEqual(source, dest, 0)); + Assert.True(ElementsAreEqual(source, dest, count - 1)); + Assert.False(ElementsAreEqual(source, dest, count)); + } + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void BytesToGeneric(int count) + { + int destCount = count * sizeof(Foo); + byte[] source = new byte[destCount + sizeof(Foo) + 1]; + Foo[] dest = Foo.CreateArray(count + 2); + + fixed(byte* pSource = source) + fixed (Foo* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource); + BufferPointer apDest = new BufferPointer(dest, pDest); + + BufferPointer.Copy(apSource, apDest, count); + } + + Assert.True(ElementsAreEqual(dest, source, 0)); + Assert.True(ElementsAreEqual(dest, source, count - 1)); + Assert.False(ElementsAreEqual(dest, source, count)); + } + + private static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) + { + fixed (Foo* pArray = array) + fixed (byte* pRaw = rawArray) + { + Foo* pCasted = (Foo*)pRaw; + + Foo val1 = pArray[index]; + Foo val2 = pCasted[index]; + + return val1.Equals(val2); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs b/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs new file mode 100644 index 000000000..7db7a4820 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Common +{ + using System; + + using Xunit; + + public class Fast2DArrayTests + { + private static readonly float[,] FloydSteinbergMatrix = + { + { 0, 0, 7 }, + { 3, 5, 1 } + }; + + [Fact] + public void Fast2DArrayThrowsOnNullInitializer() + { + Assert.Throws(() => + { + Fast2DArray fast = new Fast2DArray(null); + }); + } + + [Fact] + public void Fast2DArrayThrowsOnEmptyZeroWidth() + { + Assert.Throws(() => + { + Fast2DArray fast = new Fast2DArray(0, 10); + }); + } + + [Fact] + public void Fast2DArrayThrowsOnEmptyZeroHeight() + { + Assert.Throws(() => + { + Fast2DArray fast = new Fast2DArray(10, 0); + }); + } + + [Fact] + public void Fast2DArrayThrowsOnEmptyInitializer() + { + Assert.Throws(() => + { + Fast2DArray fast = new Fast2DArray(new float[0, 0]); + }); + } + + [Fact] + public void Fast2DArrayReturnsCorrectDimensions() + { + Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix); + Assert.True(fast.Width == FloydSteinbergMatrix.GetLength(1)); + Assert.True(fast.Height == FloydSteinbergMatrix.GetLength(0)); + } + + [Fact] + public void Fast2DArrayGetReturnsCorrectResults() + { + Fast2DArray fast = FloydSteinbergMatrix; + + for (int row = 0; row < fast.Height; row++) + { + for (int column = 0; column < fast.Width; column++) + { + Assert.True(Math.Abs(fast[row, column] - FloydSteinbergMatrix[row, column]) < Constants.Epsilon); + } + } + } + + [Fact] + public void Fast2DArrayGetSetReturnsCorrectResults() + { + Fast2DArray fast = new Fast2DArray(4, 4); + const float Val = 5F; + + fast[3, 3] = Val; + + Assert.True(Math.Abs(Val - fast[3, 3]) < Constants.Epsilon); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs new file mode 100644 index 000000000..65077ae7f --- /dev/null +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -0,0 +1,94 @@ +namespace ImageSharp.Tests.Common +{ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using Xunit; + + public unsafe class PinnedBufferTests + { + public struct Foo + { + public int A; + + public double B; + } + + [Theory] + [InlineData(42)] + [InlineData(1111)] + public void ConstructWithOwnArray(int count) + { + using (PinnedBuffer buffer = new PinnedBuffer(count)) + { + Assert.False(buffer.IsDisposedOrLostArrayOwnership); + Assert.NotNull(buffer.Array); + Assert.Equal(count, buffer.Count); + Assert.True(buffer.Array.Length >= count); + + VerifyPointer(buffer); + } + } + + [Theory] + [InlineData(42)] + [InlineData(1111)] + public void ConstructWithExistingArray(int count) + { + Foo[] array = new Foo[count]; + using (PinnedBuffer buffer = new PinnedBuffer(array)) + { + Assert.False(buffer.IsDisposedOrLostArrayOwnership); + Assert.Equal(array, buffer.Array); + Assert.Equal(count, buffer.Count); + + VerifyPointer(buffer); + } + } + + [Fact] + public void Dispose() + { + PinnedBuffer buffer = new PinnedBuffer(42); + buffer.Dispose(); + + Assert.True(buffer.IsDisposedOrLostArrayOwnership); + } + + [Fact] + public void Slice() + { + Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; + + using (PinnedBuffer buffer = new PinnedBuffer(a)) + { + var arrayPtr = buffer.Slice(); + + Assert.Equal(a, arrayPtr.Array); + Assert.Equal(0, arrayPtr.Offset); + Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset); + } + } + + [Fact] + public void UnPinAndTakeArrayOwnership() + { + Foo[] data = null; + using (PinnedBuffer buffer = new PinnedBuffer(42)) + { + data = buffer.UnPinAndTakeArrayOwnership(); + Assert.True(buffer.IsDisposedOrLostArrayOwnership); + } + + Assert.NotNull(data); + Assert.True(data.Length >= 42); + } + + private static void VerifyPointer(PinnedBuffer buffer) + { + IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]); + Assert.Equal(ptr, buffer.Pointer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs new file mode 100644 index 000000000..001785d60 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Linq; + + using Xunit; + + /// + /// Tests the class. + /// + public class PixelDataPoolTests + { + [Fact] + public void PixelDataPoolRentsMinimumSize() + { + Color[] pixels = PixelDataPool.Rent(1024); + + Assert.True(pixels.Length >= 1024); + } + + [Fact] + public void PixelDataPoolRentsEmptyArray() + { + for (int i = 16; i < 1024; i += 16) + { + Color[] pixels = PixelDataPool.Rent(i); + + Assert.True(pixels.All(p => p == default(Color))); + + PixelDataPool.Return(pixels); + } + + for (int i = 16; i < 1024; i += 16) + { + Color[] pixels = PixelDataPool.Rent(i); + + Assert.True(pixels.All(p => p == default(Color))); + + PixelDataPool.Return(pixels); + } + } + + [Fact] + public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() + { + Color[] pixels = new Color[1024]; + + PixelDataPool.Return(pixels); + + Assert.True(pixels.Length >= 1024); + } + + [Fact] + public void PixelDataPoolCleansRentedArray() + { + Color[] pixels = PixelDataPool.Rent(256); + + for (int i = 0; i < pixels.Length; i++) + { + pixels[i] = Color.Azure; + } + + Assert.True(pixels.All(p => p == Color.Azure)); + + PixelDataPool.Return(pixels); + + Assert.True(pixels.All(p => p == default(Color))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CalculateMaxArrayLength(bool isRawData) + { + int max = isRawData ? PixelDataPool.CalculateMaxArrayLength() + : PixelDataPool.CalculateMaxArrayLength(); + + Assert.Equal(max < int.MaxValue, !isRawData); + } + + [Fact] + public void RentNonIPixelData() + { + byte[] data = PixelDataPool.Rent(16384); + + Assert.True(data.Length >= 16384); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs index c219f91aa..a1d4d3fd5 100644 --- a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs +++ b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs @@ -15,89 +15,88 @@ namespace ImageSharp.Tests.Drawing public class Beziers : FileTestBase { - - [Fact] public void ImageShouldBeOverlayedByBezierLine() { - string path = CreateOutputDirectory("Drawing","BezierLine"); -var image = new Image(500, 500); - -using (FileStream output = File.OpenWrite($"{path}/Simple.png")) -{ - image - .BackgroundColor(Color.Blue) - .DrawBeziers(Color.HotPink, 5, new[] { - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - }) - .Save(output); -} - - using (var sourcePixels = image.Lock()) + string path = this.CreateOutputDirectory("Drawing", "BezierLine"); + using (Image image = new Image(500, 500)) { - //top of curve - Assert.Equal(Color.HotPink, sourcePixels[138,115]); - - //start points - Assert.Equal(Color.HotPink, sourcePixels[10, 400]); - Assert.Equal(Color.HotPink, sourcePixels[300, 400]); - - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); - Assert.Equal(Color.Blue, sourcePixels[240, 30]); - - // inside shape should be empty - Assert.Equal(Color.Blue, sourcePixels[200, 250]); + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image.BackgroundColor(Color.Blue) + .DrawBeziers(Color.HotPink, 5, + new[] { + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + }) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + //top of curve + Assert.Equal(Color.HotPink, sourcePixels[138, 115]); + + //start points + Assert.Equal(Color.HotPink, sourcePixels[10, 400]); + Assert.Equal(Color.HotPink, sourcePixels[300, 400]); + + //curve points should not be never be set + Assert.Equal(Color.Blue, sourcePixels[30, 10]); + Assert.Equal(Color.Blue, sourcePixels[240, 30]); + + // inside shape should be empty + Assert.Equal(Color.Blue, sourcePixels[200, 250]); + } } - } [Fact] public void ImageShouldBeOverlayedBezierLineWithOpacity() { - string path = CreateOutputDirectory("Drawing", "BezierLine"); + string path = this.CreateOutputDirectory("Drawing", "BezierLine"); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawBeziers(color, 10, new[] { - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - }) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image.BackgroundColor(Color.Blue) + .DrawBeziers(color, + 10, + new[] { + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + }) + .Save(output); + } + + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + + using (PixelAccessor sourcePixels = image.Lock()) + { + //top of curve + Assert.Equal(mergedColor, sourcePixels[138, 115]); + + //start points + Assert.Equal(mergedColor, sourcePixels[10, 400]); + Assert.Equal(mergedColor, sourcePixels[300, 400]); + + //curve points should not be never be set + Assert.Equal(Color.Blue, sourcePixels[30, 10]); + Assert.Equal(Color.Blue, sourcePixels[240, 30]); + + // inside shape should be empty + Assert.Equal(Color.Blue, sourcePixels[200, 250]); + } } - - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f/255f)); - - using (var sourcePixels = image.Lock()) - { - //top of curve - Assert.Equal(mergedColor, sourcePixels[138, 115]); - - //start points - Assert.Equal(mergedColor, sourcePixels[10, 400]); - Assert.Equal(mergedColor, sourcePixels[300, 400]); - - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); - Assert.Equal(Color.Blue, sourcePixels[240, 30]); - - // inside shape should be empty - Assert.Equal(Color.Blue, sourcePixels[200, 250]); - } - } - + } } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index 5e5a6897f..3a59de624 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Tests { using System.IO; - using System.Linq; using Xunit; @@ -15,18 +14,20 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyDrawImageFilter() { - string path = CreateOutputDirectory("Drawing", "DrawImage"); + string path = this.CreateOutputDirectory("Drawing", "DrawImage"); - Image blend = TestFile.Create(TestImages.Bmp.Car).CreateImage(); - - foreach (TestFile file in Files) + using (Image blend = TestFile.Create(TestImages.Bmp.Car).CreateImage()) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + foreach (TestFile file in Files) { - image.DrawImage(blend, 75, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) - .Save(output); + using (Image image = file.CreateImage()) + { + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.DrawImage(blend, 75, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) + .Save(output); + } + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index 61080b815..fc231a89d 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -7,12 +7,13 @@ namespace ImageSharp.Tests.Drawing { using Drawing; using ImageSharp.Drawing; - using CorePath = ImageSharp.Drawing.Paths.Path; - using ImageSharp.Drawing.Paths; + using ShapePath = SixLabors.Shapes.Path; + using SixLabors.Shapes; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Numerics; + using Xunit; public class DrawPathTests : FileTestBase @@ -20,81 +21,82 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPath() { - string path = CreateOutputDirectory("Drawing", "Path"); - 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 BezierLineSegment(new Vector2(50, 300), - new Vector2(500, 500), - new Vector2(60, 10), - new Vector2(10, 400)); - - var p = new CorePath(linerSegemnt, bazierSegment); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + string path = this.CreateOutputDirectory("Drawing", "Path"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPath(Color.HotPink, 5, p) - .Save(output); + LinearLineSegment linerSegemnt = new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300)); + BezierLineSegment bazierSegment = new BezierLineSegment(new Vector2(50, 300), + new Vector2(500, 500), + new Vector2(60, 10), + new Vector2(10, 400)); + + ShapePath p = new ShapePath(linerSegemnt, bazierSegment); + + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .Draw(Color.HotPink, 5, p) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + + Assert.Equal(Color.Blue, sourcePixels[50, 50]); + } } - - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); - - Assert.Equal(Color.Blue, sourcePixels[50, 50]); - } - } [Fact] public void ImageShouldBeOverlayedPathWithOpacity() { - string path = CreateOutputDirectory("Drawing", "Path"); + string path = this.CreateOutputDirectory("Drawing", "Path"); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var linerSegemnt = new LinearLineSegment( + LinearLineSegment linerSegemnt = new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) ); - var bazierSegment = new BezierLineSegment(new Vector2(50, 300), + + BezierLineSegment bazierSegment = new BezierLineSegment(new Vector2(50, 300), new Vector2(500, 500), new Vector2(60, 10), new Vector2(10, 400)); - var p = new CorePath(linerSegemnt, bazierSegment); + ShapePath p = new ShapePath(linerSegemnt, bazierSegment); - var image = new Image(500, 500); - - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPath(color, 10, p) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .Draw(color, 10, p) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[9, 9]); - Assert.Equal(mergedColor, sourcePixels[199, 149]); + Assert.Equal(mergedColor, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); + } } } diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index 5bb93a55c..d3c1877ab 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -15,55 +15,56 @@ namespace ImageSharp.Tests.Drawing public class FillPatternBrushTests : FileTestBase { - private Image Test(string name, Color background, IBrush brush, Color[,] expectedPattern) + private void Test(string name, Color background, IBrush brush, Color[,] expectedPattern) { - string path = CreateOutputDirectory("Fill", "PatternBrush"); - Image image = new Image(20, 20); - image - .Fill(background) - .Fill(brush); - - using (FileStream output = File.OpenWrite($"{path}/{name}.png")) - { - image.Save(output); - } - using (var sourcePixels = image.Lock()) + string path = this.CreateOutputDirectory("Fill", "PatternBrush"); + using (Image image = new Image(20, 20)) { - // lets pick random spots to start checking - var r = new Random(); - var xStride = expectedPattern.GetLength(1); - var yStride = expectedPattern.GetLength(0); - var offsetX = r.Next(image.Width / xStride) * xStride; - var offsetY = r.Next(image.Height / yStride) * yStride; - for (var x = 0; x < xStride; x++) + image + .Fill(background) + .Fill(brush); + + using (FileStream output = File.OpenWrite($"{path}/{name}.png")) { - for (var y = 0; y < yStride; y++) + image.Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + // lets pick random spots to start checking + Random r = new Random(); + int xStride = expectedPattern.GetLength(1); + int yStride = expectedPattern.GetLength(0); + int offsetX = r.Next(image.Width / xStride) * xStride; + int offsetY = r.Next(image.Height / yStride) * yStride; + for (int x = 0; x < xStride; x++) { - var actualX = x + offsetX; - var actualY = y + offsetY; - var expected = expectedPattern[y, x]; // inverted pattern - var actual = sourcePixels[actualX, actualY]; - if (expected != actual) + for (int y = 0; y < yStride; y++) { - Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})"); + int actualX = x + offsetX; + int actualY = y + offsetY; + Color expected = expectedPattern[y, x]; // inverted pattern + Color actual = sourcePixels[actualX, actualY]; + if (expected != actual) + { + Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})"); + } } } } + using (FileStream output = File.OpenWrite($"{path}/{name}x4.png")) + { + image.Resize(80, 80).Save(output); + } } - using (FileStream output = File.OpenWrite($"{path}/{name}x4.png")) - { - image.Resize(80, 80).Save(output); - } - - - - return image; } [Fact] public void ImageShouldBeFloodFilledWithPercent10() { - Test("Percent10", Color.Blue, Brushes.Percent10(Color.HotPink, Color.LimeGreen), new Color[,] { + this.Test("Percent10", Color.Blue, Brushes.Percent10(Color.HotPink, Color.LimeGreen), + new[,] + { { Color.HotPink , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}, { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}, { Color.LimeGreen, Color.LimeGreen, Color.HotPink , Color.LimeGreen}, diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs index b7551d63a..bafc84b69 100644 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -18,71 +18,73 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeFloodFilledWithColorOnDefaultBackground() { - string path = CreateOutputDirectory("Fill", "SolidBrush"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/DefaultBack.png")) + string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + using (Image image = new Image(500, 500)) { - image - .Fill(Color.HotPink) - .Save(output); - } - - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + using (FileStream output = File.OpenWrite($"{path}/DefaultBack.png")) + { + image + .Fill(Color.HotPink) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + } } } [Fact] public void ImageShouldBeFloodFilledWithColor() { - string path = CreateOutputDirectory("Fill", "SolidBrush"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink) - .Save(output); - } - - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + } } } [Fact] public void ImageShouldBeFloodFilledWithColorOpacity() { - string path = CreateOutputDirectory("Fill", "SolidBrush"); - var image = new Image(500, 500); - - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Color.Blue) - .Fill(color) - .Save(output); - } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - - - using (var sourcePixels = image.Lock()) + string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + using (Image image = new Image(500, 500)) { - Assert.Equal(mergedColor, sourcePixels[9, 9]); - Assert.Equal(mergedColor, sourcePixels[199, 149]); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(color) + .Save(output); + } + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[9, 9]); + Assert.Equal(mergedColor, sourcePixels[199, 149]); + } } - } } diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index cdcb5ebe4..6153cb310 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -5,113 +5,115 @@ namespace ImageSharp.Tests.Drawing { - using System; - using System.Diagnostics.CodeAnalysis; using System.IO; using Xunit; using Drawing; using ImageSharp.Drawing; using System.Numerics; - using ImageSharp.Drawing.Shapes; using ImageSharp.Drawing.Pens; + using SixLabors.Shapes; + public class LineComplexPolygonTests : FileTestBase { [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + + var simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - var hole1 = new LinearPolygon( + var hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); + new Vector2(65, 137))); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[10, 10]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[10, 10]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 300]); + Assert.Equal(Color.HotPink, sourcePixels[50, 300]); - Assert.Equal(Color.HotPink, sourcePixels[37, 85]); + Assert.Equal(Color.HotPink, sourcePixels[37, 85]); - Assert.Equal(Color.HotPink, sourcePixels[93, 85]); + Assert.Equal(Color.HotPink, sourcePixels[93, 85]); - Assert.Equal(Color.HotPink, sourcePixels[65, 137]); + Assert.Equal(Color.HotPink, sourcePixels[65, 137]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); - //inside shape - Assert.Equal(Color.Blue, sourcePixels[100, 192]); + //inside shape + Assert.Equal(Color.Blue, sourcePixels[100, 192]); + } } } [Fact] public void ImageShouldBeOverlayedByPolygonOutlineNoOverlapping() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - var hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(207, 25), new Vector2(263, 25), - new Vector2(235, 57)); - - var image = new Image(500, 500); + new Vector2(235, 57))); - using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png")) + { + image + .BackgroundColor(Color.Blue) + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[10, 10]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[10, 10]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 300]); + Assert.Equal(Color.HotPink, sourcePixels[50, 300]); - //Assert.Equal(Color.HotPink, sourcePixels[37, 85]); + //Assert.Equal(Color.HotPink, sourcePixels[37, 85]); - //Assert.Equal(Color.HotPink, sourcePixels[93, 85]); + //Assert.Equal(Color.HotPink, sourcePixels[93, 85]); - //Assert.Equal(Color.HotPink, sourcePixels[65, 137]); + //Assert.Equal(Color.HotPink, sourcePixels[65, 137]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); - //inside shape - Assert.Equal(Color.Blue, sourcePixels[100, 192]); + //inside shape + Assert.Equal(Color.Blue, sourcePixels[100, 192]); + } } } @@ -119,44 +121,45 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutlineOverlapping() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - var hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(130, 40), - new Vector2(65, 137)); + new Vector2(65, 137))); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) + { + image + .BackgroundColor(Color.Blue) + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[10, 10]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[10, 10]); + + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[50, 300]); - Assert.Equal(Color.HotPink, sourcePixels[50, 300]); + Assert.Equal(Color.Blue, sourcePixels[130, 41]); - Assert.Equal(Color.Blue, sourcePixels[130, 41]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); - //inside shape - Assert.Equal(Color.Blue, sourcePixels[100, 192]); + //inside shape + Assert.Equal(Color.Blue, sourcePixels[100, 192]); + } } } @@ -164,25 +167,26 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutlineDashed() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - var hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); + new Vector2(65, 137))); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Pens.Dash(Color.HotPink, 5), new ComplexPolygon(simplePath, hole1)) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) + { + image + .BackgroundColor(Color.Blue) + .Draw(Pens.Dash(Color.HotPink, 5), simplePath.Clip(hole1)) + .Save(output); + } } } @@ -190,54 +194,55 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - var hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - - var image = new Image(500, 500); + new Vector2(65, 137))); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(color, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .Draw(color, 5, simplePath.Clip(hole1)) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[10, 10]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[10, 10]); - Assert.Equal(mergedColor, sourcePixels[200, 150]); + Assert.Equal(mergedColor, sourcePixels[200, 150]); - Assert.Equal(mergedColor, sourcePixels[50, 300]); + Assert.Equal(mergedColor, sourcePixels[50, 300]); - Assert.Equal(mergedColor, sourcePixels[37, 85]); + Assert.Equal(mergedColor, sourcePixels[37, 85]); - Assert.Equal(mergedColor, sourcePixels[93, 85]); + Assert.Equal(mergedColor, sourcePixels[93, 85]); - Assert.Equal(mergedColor, sourcePixels[65, 137]); + Assert.Equal(mergedColor, sourcePixels[65, 137]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); - //inside shape - Assert.Equal(Color.Blue, sourcePixels[100, 192]); + //inside shape + Assert.Equal(Color.Blue, sourcePixels[100, 192]); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/LineTests.cs b/tests/ImageSharp.Tests/Drawing/LineTests.cs index 46b315164..81efd933b 100644 --- a/tests/ImageSharp.Tests/Drawing/LineTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineTests.cs @@ -5,11 +5,9 @@ namespace ImageSharp.Tests.Drawing { - using Drawing; using ImageSharp.Drawing; using ImageSharp.Drawing.Pens; - using System; - using System.Diagnostics.CodeAnalysis; + using System.IO; using System.Numerics; using Xunit; @@ -19,123 +17,132 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPath() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Color.HotPink, 5, new[] { + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Color.HotPink, 5, + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); - } + }) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); + } } } [Fact] public void ImageShouldBeOverlayedByPath_NoAntialias() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Color.HotPink, 5, new[] { + using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Color.HotPink, 5, + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }, new GraphicsOptions(false)) - .Save(output); - } + }, + new GraphicsOptions(false)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); + } } } [Fact] public void ImageShouldBeOverlayedByPathDashed() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Pens.Dash(Color.HotPink, 5), new[] { + using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Pens.Dash(Color.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save(output); + } } - } [Fact] public void ImageShouldBeOverlayedByPathDotted() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Dot.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Pens.Dot(Color.HotPink, 5), new[] { + using (FileStream output = File.OpenWrite($"{path}/Dot.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Pens.Dot(Color.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save(output); + } } } [Fact] public void ImageShouldBeOverlayedByPathDashDot() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/DashDot.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Pens.DashDot(Color.HotPink, 5), new[] { + using (FileStream output = File.OpenWrite($"{path}/DashDot.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Pens.DashDot(Color.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save(output); + } } - } [Fact] public void ImageShouldBeOverlayedByPathDashDotDot() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); + string path = this.CreateOutputDirectory("Drawing", "Lines"); + Image image = new Image(500, 500); using (FileStream output = File.OpenWrite($"{path}/DashDotDot.png")) { @@ -153,12 +160,12 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPathWithOpacity() { - string path = CreateOutputDirectory("Drawing", "Lines"); + string path = this.CreateOutputDirectory("Drawing", "Lines"); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + + Image image = new Image(500, 500); - var image = new Image(500, 500); - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) { @@ -173,9 +180,9 @@ namespace ImageSharp.Tests.Drawing } //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f/255f)); + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f/255f)); - using (var sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { Assert.Equal(mergedColor, sourcePixels[9, 9]); @@ -188,9 +195,9 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPathOutline() { - string path = CreateOutputDirectory("Drawing", "Lines"); + string path = this.CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); + Image image = new Image(500, 500); using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) { @@ -205,7 +212,7 @@ namespace ImageSharp.Tests.Drawing .Save(output); } - using (var sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { Assert.Equal(Color.HotPink, sourcePixels[8, 8]); @@ -216,6 +223,6 @@ namespace ImageSharp.Tests.Drawing Assert.Equal(Color.Blue, sourcePixels[50, 50]); } } - + } } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs new file mode 100644 index 000000000..82e2f72a2 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs @@ -0,0 +1,164 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawBeziersTests : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Firebrick, 99.9f); + Vector2[] points = new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + private ProcessorWatchingImage img; + + public DrawBeziersTests() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushThicknessAndPoints() + { + img.DrawBeziers(brush, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + Assert.NotNull(path.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsBrushThicknessPointsAndOptions() + { + img.DrawBeziers(brush, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsColorThicknessAndPoints() + { + img.DrawBeziers(color, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorThicknessPointsAndOptions() + { + img.DrawBeziers(color, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsPenAndPoints() + { + img.DrawBeziers(pen, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void CorrectlySetsPenPointsAndOptions() + { + img.DrawBeziers(pen, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs new file mode 100644 index 000000000..cc126614f --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs @@ -0,0 +1,162 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawLinesTests : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + Vector2[] points = new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + private ProcessorWatchingImage img; + + public DrawLinesTests() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushThicknessAndPoints() + { + img.DrawLines(brush, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsBrushThicknessPointsAndOptions() + { + img.DrawLines(brush, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsColorThicknessAndPoints() + { + img.DrawLines(color, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorThicknessPointsAndOptions() + { + img.DrawLines(color, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsPenAndPoints() + { + img.DrawLines(pen, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void CorrectlySetsPenPointsAndOptions() + { + img.DrawLines(pen, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs new file mode 100644 index 000000000..6c1c06813 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs @@ -0,0 +1,150 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawPath : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + private ProcessorWatchingImage img; + + public DrawPath() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushThicknessAndPath() + { + img.Draw(brush, thickness, path); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + Assert.Equal(path, shapepath.Path); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsBrushThicknessPathAndOptions() + { + img.Draw(brush, thickness, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + Assert.Equal(path, shapepath.Path); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsColorThicknessAndPath() + { + img.Draw(color, thickness, path); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + Assert.Equal(path, shapepath.Path); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorThicknessPathAndOptions() + { + img.Draw(color, thickness, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + Assert.Equal(path, shapepath.Path); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsPenAndPath() + { + img.Draw(pen, path); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + Assert.Equal(path, shapepath.Path); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void CorrectlySetsPenPathAndOptions() + { + img.Draw(pen, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + Assert.Equal(path, shapepath.Path); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs new file mode 100644 index 000000000..9de052331 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs @@ -0,0 +1,162 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawPolygon : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + Vector2[] points = new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + private ProcessorWatchingImage img; + + public DrawPolygon() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushThicknessAndPoints() + { + img.DrawPolygon(brush, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + Polygon vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsBrushThicknessPointsAndOptions() + { + img.DrawPolygon(brush, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + Polygon vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsColorThicknessAndPoints() + { + img.DrawPolygon(color, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + Polygon vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorThicknessPointsAndOptions() + { + img.DrawPolygon(color, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + Polygon vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsPenAndPoints() + { + img.DrawPolygon(pen, points); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + Polygon vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void CorrectlySetsPenPointsAndOptions() + { + img.DrawPolygon(pen, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath path = Assert.IsType(processor.Path); + + Polygon vector = Assert.IsType(path.Path); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs new file mode 100644 index 000000000..215d5a7c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs @@ -0,0 +1,181 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawRectangle : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + ImageSharp.Rectangle rectangle = new ImageSharp.Rectangle(10, 10, 98, 324); + + private ProcessorWatchingImage img; + + public DrawRectangle() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushThicknessAndRectangle() + { + img.Draw(brush, thickness, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsBrushThicknessRectangleAndOptions() + { + img.Draw(brush, thickness, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void CorrectlySetsColorThicknessAndRectangle() + { + img.Draw(color, thickness, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorThicknessRectangleAndOptions() + { + img.Draw(color, thickness, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Pen pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + SolidBrush brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsPenAndRectangle() + { + img.Draw(pen, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void CorrectlySetsPenRectangleAndOptions() + { + img.Draw(pen, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapePath shapepath = Assert.IsType(processor.Path); + + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs b/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs new file mode 100644 index 000000000..a18dd90bc --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs @@ -0,0 +1,33 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class Extensions + { + [Theory] + [InlineData(0.5, 0.5, 5, 5, 0,0,6,6)] + [InlineData(1, 1, 5, 5, 1,1,5,5)] + public void ConvertRectangle(float x, float y, float width, float height, int expectedX, int expectedY, int expectedWidth, int expectedHeight) + { + SixLabors.Shapes.Rectangle src = new SixLabors.Shapes.Rectangle(x, y, width, height); + ImageSharp.Rectangle actual = src.Convert(); + + Assert.Equal(expectedX, actual.X); + Assert.Equal(expectedY, actual.Y); + Assert.Equal(expectedWidth, actual.Width); + Assert.Equal(expectedHeight, actual.Height); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs new file mode 100644 index 000000000..5ba6580bd --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs @@ -0,0 +1,112 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class FillPath : IDisposable + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + private ProcessorWatchingImage img; + + public FillPath() + { + this.img = new ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushAndPath() + { + img.Fill(brush, path); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + + // path is converted to a polygon before filling + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsBrushPathOptions() + { + img.Fill(brush, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsColorAndPath() + { + img.Fill(color, path); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorPathAndOptions() + { + img.Fill(color, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs new file mode 100644 index 000000000..ad72d4c4e --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs @@ -0,0 +1,110 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class FillPolygon : IDisposable + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Vector2[] path = new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + private ProcessorWatchingImage img; + + public FillPolygon() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushAndPath() + { + img.FillPolygon(brush, path); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsBrushPathAndOptions() + { + img.FillPolygon(brush, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsColorAndPath() + { + img.FillPolygon(color, path); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorPathAndOptions() + { + img.FillPolygon(color, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs new file mode 100644 index 000000000..f6b1c4ade --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs @@ -0,0 +1,118 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class FillRectangle : IDisposable + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + ImageSharp.Rectangle rectangle = new ImageSharp.Rectangle(10, 10, 77, 76); + + private ProcessorWatchingImage img; + + public FillRectangle() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushAndRectangle() + { + img.Fill(brush, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + SixLabors.Shapes.Rectangle rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsBrushRectangleAndOptions() + { + img.Fill(brush, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + SixLabors.Shapes.Rectangle rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsColorAndRectangle() + { + img.Fill(color, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + SixLabors.Shapes.Rectangle rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorRectangleAndOptions() + { + img.Fill(color, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + SixLabors.Shapes.Rectangle rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs new file mode 100644 index 000000000..3bb3b3e77 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs @@ -0,0 +1,42 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using Processing; + using System.Collections.Generic; + + /// + /// Watches but does not actually run the processors against the image. + /// + /// + public class ProcessorWatchingImage : Image + { + public List ProcessorApplications { get; } = new List(); + + public ProcessorWatchingImage(int width, int height) + : base(width, height, Configuration.CreateDefaultInstance()) + { + } + + public override void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + ProcessorApplications.Add(new ProcessorDetails + { + processor = processor, + rectangle = rectangle + }); + + // doesn't really apply the processor to the fake images as this is supposed + // to be just used to test which processor was finally applied and to interogate + // its settings + } + + public struct ProcessorDetails + { + public IImageProcessor processor; + public Rectangle rectangle; + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs new file mode 100644 index 000000000..494e2a672 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs @@ -0,0 +1,126 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using Moq; + using System.Collections.Immutable; + + public class ShapePathTests + { + private readonly Mock pathMock1; + private readonly Mock pathMock2; + private readonly SixLabors.Shapes.Rectangle bounds1; + + public ShapePathTests() + { + this.pathMock2 = new Mock(); + this.pathMock1 = new Mock(); + + this.bounds1 = new SixLabors.Shapes.Rectangle(10.5f, 10, 10, 10); + pathMock1.Setup(x => x.Bounds).Returns(this.bounds1); + pathMock2.Setup(x => x.Bounds).Returns(this.bounds1); + // wire up the 2 mocks to reference eachother + pathMock1.Setup(x => x.AsClosedPath()).Returns(() => pathMock2.Object); + } + + [Fact] + public void ShapePathFromPathConvertsBoundsDoesNotProxyToShape() + { + ShapePath region = new ShapePath(pathMock1.Object); + + Assert.Equal(Math.Floor(bounds1.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(bounds1.Right), region.Bounds.Right); + + pathMock1.Verify(x => x.Bounds); + } + + [Fact] + public void ShapePathFromPathMaxIntersectionsProxyToShape() + { + ShapePath region = new ShapePath(pathMock1.Object); + + int i = region.MaxIntersections; + pathMock1.Verify(x => x.MaxIntersections); + } + + [Fact] + public void ShapePathFromPathScanXProxyToShape() + { + int xToScan = 10; + ShapePath region = new ShapePath(pathMock1.Object); + + pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(xToScan, s.X); + Assert.Equal(xToScan, e.X); + Assert.True(s.Y < bounds1.Top); + Assert.True(e.Y > bounds1.Bottom); + }).Returns(0); + + int i = region.ScanX(xToScan, new float[0], 0, 0); + + pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapePathFromPathScanYProxyToShape() + { + int yToScan = 10; + ShapePath region = new ShapePath(pathMock1.Object); + + pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < bounds1.Left); + Assert.True(e.X > bounds1.Right); + }).Returns(0); + + int i = region.ScanY(yToScan, new float[0], 0, 0); + + pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + + [Fact] + public void ShapePathFromShapeScanXProxyToShape() + { + int xToScan = 10; + ShapePath region = new ShapePath(pathMock1.Object); + + pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(xToScan, s.X); + Assert.Equal(xToScan, e.X); + Assert.True(s.Y < bounds1.Top); + Assert.True(e.Y > bounds1.Bottom); + }).Returns(0); + + int i = region.ScanX(xToScan, new float[0], 0, 0); + + pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void GetPointInfoCallSinglePathForPath() + { + ShapePath region = new ShapePath(pathMock1.Object); + + ImageSharp.Drawing.PointInfo info = region.GetPointInfo(10, 1); + + pathMock1.Verify(x => x.Distance(new Vector2(10, 1)), Times.Once); + pathMock2.Verify(x => x.Distance(new Vector2(10, 1)), Times.Never); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs new file mode 100644 index 000000000..aa7c0575c --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -0,0 +1,167 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using Moq; + using System.Collections.Immutable; + + public class ShapeRegionTests + { + private readonly Mock pathMock; + private readonly SixLabors.Shapes.Rectangle bounds; + + public ShapeRegionTests() + { + this.pathMock = new Mock(); + + this.bounds = new SixLabors.Shapes.Rectangle(10.5f, 10, 10, 10); + pathMock.Setup(x => x.Bounds).Returns(this.bounds); + // wire up the 2 mocks to reference eachother + pathMock.Setup(x => x.AsClosedPath()).Returns(() => pathMock.Object); + } + + [Fact] + public void ShapeRegionWithPathCallsAsShape() + { + new ShapeRegion(pathMock.Object); + + pathMock.Verify(x => x.AsClosedPath()); + } + + [Fact] + public void ShapeRegionWithPathRetainsShape() + { + ShapeRegion region = new ShapeRegion(pathMock.Object); + + Assert.Equal(pathMock.Object, region.Shape); + } + + [Fact] + public void ShapeRegionFromPathConvertsBoundsProxyToShape() + { + ShapeRegion region = new ShapeRegion(pathMock.Object); + + Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); + + pathMock.Verify(x => x.Bounds); + } + + [Fact] + public void ShapeRegionFromPathMaxIntersectionsProxyToShape() + { + ShapeRegion region = new ShapeRegion(pathMock.Object); + + int i = region.MaxIntersections; + pathMock.Verify(x => x.MaxIntersections); + } + + [Fact] + public void ShapeRegionFromPathScanXProxyToShape() + { + int xToScan = 10; + ShapeRegion region = new ShapeRegion(pathMock.Object); + + pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(xToScan, s.X); + Assert.Equal(xToScan, e.X); + Assert.True(s.Y < bounds.Top); + Assert.True(e.Y > bounds.Bottom); + }).Returns(0); + + int i = region.ScanX(xToScan, new float[0], 0, 0); + + pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapeRegionFromPathScanYProxyToShape() + { + int yToScan = 10; + ShapeRegion region = new ShapeRegion(pathMock.Object); + + pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < bounds.Left); + Assert.True(e.X > bounds.Right); + }).Returns(0); + + int i = region.ScanY(yToScan, new float[0], 0, 0); + + pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + + [Fact] + public void ShapeRegionFromShapeScanXProxyToShape() + { + int xToScan = 10; + ShapeRegion region = new ShapeRegion(pathMock.Object); + + pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(xToScan, s.X); + Assert.Equal(xToScan, e.X); + Assert.True(s.Y < bounds.Top); + Assert.True(e.Y > bounds.Bottom); + }).Returns(0); + + int i = region.ScanX(xToScan, new float[0], 0, 0); + + pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapeRegionFromShapeScanYProxyToShape() + { + int yToScan = 10; + ShapeRegion region = new ShapeRegion(pathMock.Object); + + pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < bounds.Left); + Assert.True(e.X > bounds.Right); + }).Returns(0); + + int i = region.ScanY(yToScan, new float[0], 0, 0); + + pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapeRegionFromShapeConvertsBoundsProxyToShape() + { + ShapeRegion region = new ShapeRegion(pathMock.Object); + + Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); + + pathMock.Verify(x => x.Bounds); + } + + [Fact] + public void ShapeRegionFromShapeMaxIntersectionsProxyToShape() + { + ShapeRegion region = new ShapeRegion(pathMock.Object); + + int i = region.MaxIntersections; + pathMock.Verify(x => x.MaxIntersections); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs index 68027d0bf..3e06ca918 100644 --- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs @@ -18,97 +18,101 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = CreateOutputDirectory("Drawing", "Polygons"); + string path = this.CreateOutputDirectory("Drawing", "Polygons"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new[] { - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300) - }) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(Color.HotPink, 5, + new[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = CreateOutputDirectory("Drawing", "Polygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "Polygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(color, 10, simplePath) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(color, 10, simplePath) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[9, 9]); - Assert.Equal(mergedColor, sourcePixels[199, 149]); + Assert.Equal(mergedColor, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] public void ImageShouldBeOverlayedByRectangleOutline() { - string path = CreateOutputDirectory("Drawing", "Polygons"); + string path = this.CreateOutputDirectory("Drawing", "Polygons"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 10, new Rectangle(10, 10, 190, 140)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) + { + image + .BackgroundColor(Color.Blue) + .Draw(Color.HotPink, 10, new Rectangle(10, 10, 190, 140)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[8, 8]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[8, 8]); - Assert.Equal(Color.HotPink, sourcePixels[198, 10]); + Assert.Equal(Color.HotPink, sourcePixels[198, 10]); - Assert.Equal(Color.HotPink, sourcePixels[10, 50]); + Assert.Equal(Color.HotPink, sourcePixels[10, 50]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs index 2edd05be1..0b450d166 100644 --- a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs @@ -16,18 +16,19 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldRecolorYellowToHotPink() { - string path = CreateOutputDirectory("Drawing", "RecolorImage"); + string path = this.CreateOutputDirectory("Drawing", "RecolorImage"); - var brush = new RecolorBrush(Color.Yellow, Color.HotPink, 0.2f); + RecolorBrush brush = new RecolorBrush(Color.Yellow, Color.HotPink, 0.2f); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (Image image = file.CreateImage()) { - image.Fill(brush) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Fill(brush) + .Save(output); + } } } } @@ -35,19 +36,20 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldRecolorYellowToHotPinkInARectangle() { - string path = CreateOutputDirectory("Drawing", "RecolorImage"); + string path = this.CreateOutputDirectory("Drawing", "RecolorImage"); - var brush = new RecolorBrush(Color.Yellow, Color.HotPink, 0.2f); + RecolorBrush brush = new RecolorBrush(Color.Yellow, Color.HotPink, 0.2f); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/Shaped_{file.FileName}")) + using (Image image = file.CreateImage()) { - var imageHeight = image.Height; - image.Fill(brush, new Rectangle(0, imageHeight/2 - imageHeight/4, image.Width, imageHeight/2)) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/Shaped_{file.FileName}")) + { + int imageHeight = image.Height; + image.Fill(brush, new Rectangle(0, imageHeight/2 - imageHeight/4, image.Width, imageHeight/2)) + .Save(output); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index f6bcf4906..80713468d 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -5,13 +5,11 @@ namespace ImageSharp.Tests.Drawing { - using Drawing; - using ImageSharp.Drawing; - using ImageSharp.Drawing.Shapes; - using System; - using System.Diagnostics.CodeAnalysis; using System.IO; using System.Numerics; + + using SixLabors.Shapes; + using Xunit; public class SolidBezierTests : FileTestBase @@ -19,83 +17,84 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygon() { - string path = CreateOutputDirectory("Drawing", "FilledBezier"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledBezier"); + Vector2[] simplePath = new[] { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) }; - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink,new BezierPolygon(simplePath)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new Polygon(new BezierLineSegment(simplePath))) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - //top of curve - Assert.Equal(Color.HotPink, sourcePixels[138, 116]); + using (PixelAccessor sourcePixels = image.Lock()) + { + //top of curve + Assert.Equal(Color.HotPink, sourcePixels[138, 116]); - //start points - Assert.Equal(Color.HotPink, sourcePixels[10, 400]); - Assert.Equal(Color.HotPink, sourcePixels[300, 400]); + //start points + Assert.Equal(Color.HotPink, sourcePixels[10, 400]); + Assert.Equal(Color.HotPink, sourcePixels[300, 400]); - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); - Assert.Equal(Color.Blue, sourcePixels[240, 30]); + //curve points should not be never be set + Assert.Equal(Color.Blue, sourcePixels[30, 10]); + Assert.Equal(Color.Blue, sourcePixels[240, 30]); - // inside shape should not be empty - Assert.Equal(Color.HotPink, sourcePixels[200, 250]); + // inside shape should not be empty + Assert.Equal(Color.HotPink, sourcePixels[200, 250]); + } } } [Fact] public void ImageShouldBeOverlayedByFilledPolygonOpacity() { - string path = CreateOutputDirectory("Drawing", "FilledBezier"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledBezier"); + Vector2[] simplePath = new[] { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) }; - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - - var image = new Image(500, 500); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(color, new BezierPolygon(simplePath)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(color, new Polygon(new BezierLineSegment(simplePath))) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - //top of curve - Assert.Equal(mergedColor, sourcePixels[138, 116]); + using (PixelAccessor sourcePixels = image.Lock()) + { + //top of curve + Assert.Equal(mergedColor, sourcePixels[138, 116]); - //start points - Assert.Equal(mergedColor, sourcePixels[10, 400]); - Assert.Equal(mergedColor, sourcePixels[300, 400]); + //start points + Assert.Equal(mergedColor, sourcePixels[10, 400]); + Assert.Equal(mergedColor, sourcePixels[300, 400]); - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); - Assert.Equal(Color.Blue, sourcePixels[240, 30]); + //curve points should not be never be set + Assert.Equal(Color.Blue, sourcePixels[30, 10]); + Assert.Equal(Color.Blue, sourcePixels[240, 30]); - // inside shape should not be empty - Assert.Equal(mergedColor, sourcePixels[200, 250]); + // inside shape should not be empty + Assert.Equal(mergedColor, sourcePixels[200, 250]); + } } } - } } diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index cbd44d7d1..cd3182d6c 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -5,55 +5,58 @@ namespace ImageSharp.Tests.Drawing { - using System; - using System.Diagnostics.CodeAnalysis; using System.IO; using Xunit; using Drawing; using ImageSharp.Drawing; using System.Numerics; - using ImageSharp.Drawing.Shapes; + + using SixLabors.Shapes; public class SolidComplexPolygonTests : FileTestBase { [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = CreateOutputDirectory("Drawing", "ComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - var hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); - - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + new Vector2(65, 137))); + var clipped = simplePath.Clip(hole1); + // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, clipped) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[70, 137]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.HotPink, sourcePixels[35, 100]); + Assert.Equal(Color.HotPink, sourcePixels[35, 100]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); + } } } @@ -61,86 +64,88 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOverlap() { - string path = CreateOutputDirectory("Drawing", "ComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - var hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(130, 40), - new Vector2(65, 137)); - - var image = new Image(500, 500); + new Vector2(65, 137))); - using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } - - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, simplePath.Clip(hole1)) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.HotPink, sourcePixels[35, 100]); + Assert.Equal(Color.HotPink, sourcePixels[35, 100]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); + } } } [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = CreateOutputDirectory("Drawing", "ComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - var hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + new Vector2(65, 137))); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(color, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(color, simplePath.Clip(hole1)) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[11, 11]); - Assert.Equal(mergedColor, sourcePixels[200, 150]); + Assert.Equal(mergedColor, sourcePixels[200, 150]); - Assert.Equal(mergedColor, sourcePixels[50, 50]); + Assert.Equal(mergedColor, sourcePixels[50, 50]); - Assert.Equal(mergedColor, sourcePixels[35, 100]); + Assert.Equal(mergedColor, sourcePixels[35, 100]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 0d3b721d5..9c6c6d234 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -13,161 +13,260 @@ namespace ImageSharp.Tests.Drawing using System.Numerics; using Xunit; using ImageSharp.Drawing.Brushes; + using SixLabors.Shapes; public class SolidPolygonTests : FileTestBase { [Fact] public void ImageShouldBeOverlayedByFilledPolygon() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var image = new Image(500, 500); - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.NotEqual(Color.HotPink, sourcePixels[2, 2]); + } } } [Fact] - public void ImageShouldBeOverlayedByFilledPolygon_NoAntialias() + public void ImageShouldBeOverlayedByFilledPolygonNoAntialias() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var image = new Image(500, 500); + using (Image image = new Image(500, 500)) using (FileStream output = File.OpenWrite($"{path}/Simple_NoAntialias.png")) { image .BackgroundColor(Color.Blue) .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(false)) .Save(output); - } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[199, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] - public void ImageShouldBeOverlayedByFilledPolygon_Image() + public void ImageShouldBeOverlayedByFilledPolygonImage() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var brush = new ImageBrush(TestFile.Create(TestImages.Bmp.Car).CreateImage()); - var image = new Image(500, 500); - + using (Image brushImage = TestFile.Create(TestImages.Bmp.Car).CreateImage()) + using (Image image = new Image(500, 500)) using (FileStream output = File.OpenWrite($"{path}/Image.png")) { + ImageBrush brush = new ImageBrush(brushImage); + image - .BackgroundColor(Color.Blue) - .FillPolygon(brush, simplePath) - .Save(output); + .BackgroundColor(Color.Blue) + .FillPolygon(brush, simplePath) + .Save(output); } } [Fact] public void ImageShouldBeOverlayedByFilledPolygonOpacity() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .FillPolygon(color, simplePath) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .FillPolygon(color, simplePath) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[11, 11]); - Assert.Equal(mergedColor, sourcePixels[200, 150]); + Assert.Equal(mergedColor, sourcePixels[200, 150]); - Assert.Equal(mergedColor, sourcePixels[50, 50]); + Assert.Equal(mergedColor, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] public void ImageShouldBeOverlayedByFilledRectangle() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { - new Vector2(10, 10), - new Vector2(200, 10), - new Vector2(200, 150), - new Vector2(10, 150) - }; + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + + using (Image image = new Image(500, 500)) + { + using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new SixLabors.Shapes.Rectangle(10, 10, 190, 140)) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + + Assert.Equal(Color.HotPink, sourcePixels[198, 10]); + + Assert.Equal(Color.HotPink, sourcePixels[10, 50]); + + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } + } + } - var image = new Image(500, 500); + [Fact] + public void ImageShouldBeOverlayedByFilledTriangle() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); - using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) + using (Image image = new Image(100, 100)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new Rectangle(10,10, 190, 140))) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/Triangle.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new RegularPolygon(50, 50, 3, 30)) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[25, 35]); + + Assert.Equal(Color.HotPink, sourcePixels[50, 79]); + + Assert.Equal(Color.HotPink, sourcePixels[75, 35]); + + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + + Assert.Equal(Color.Blue, sourcePixels[28, 60]); + + Assert.Equal(Color.Blue, sourcePixels[67, 67]); + } } + } - using (var sourcePixels = image.Lock()) + [Fact] + public void ImageShouldBeOverlayedByFilledSeptagon() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + + var config = Configuration.CreateDefaultInstance(); + config.ParallelOptions.MaxDegreeOfParallelism = 1; + using (Image image = new Image(100, 100, config)) { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (FileStream output = File.OpenWrite($"{path}/Septagon.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI)) + .Save(output); + } + } + } - Assert.Equal(Color.HotPink, sourcePixels[198, 10]); + [Fact] + public void ImageShouldBeOverlayedByFilledEllipse() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); - Assert.Equal(Color.HotPink, sourcePixels[10, 50]); + var config = Configuration.CreateDefaultInstance(); + config.ParallelOptions.MaxDegreeOfParallelism = 1; + using (Image image = new Image(100, 100, config)) + { + using (FileStream output = File.OpenWrite($"{path}/ellipse.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new Ellipse(50, 50, 30, 50) + .Rotate((float)(Math.PI / 3))) + .Save(output); + } + } + } - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + [Fact] + public void ImageShouldBeOverlayedBySquareWithCornerClipped() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + var config = Configuration.CreateDefaultInstance(); + config.ParallelOptions.MaxDegreeOfParallelism = 1; + using (Image image = new Image(200, 200, config)) + { + using (FileStream output = File.OpenWrite($"{path}/clipped-corner.png")) + { + image + .Fill(Color.Blue) + .FillPolygon(Color.HotPink, new[] + { + new Vector2( 8, 8 ), + new Vector2( 64, 8 ), + new Vector2( 64, 64 ), + new Vector2( 120, 64 ), + new Vector2( 120, 120 ), + new Vector2( 8, 120 ) + } ) + .Save(output); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs new file mode 100644 index 000000000..2a2cb8a07 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs @@ -0,0 +1,194 @@ + +namespace ImageSharp.Tests.Drawing.Text +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using SixLabors.Fonts; + using Paths; + + public class DrawText : IDisposable + { + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + private ProcessorWatchingImage img; + private readonly FontCollection FontCollection; + private readonly Font Font; + + public DrawText() + { + this.FontCollection = new FontCollection(); + this.Font = FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")); + this.img = new ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPen() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPenDefaultOptions() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + + [Fact] + public void FillsForEachACharachterWhenBrushSet() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetDefaultOptions() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenColorSet() + { + img.DrawText("123", this.Font, Color.Red, Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Color.Red, brush.Color); + } + + [Fact] + public void FillsForEachACharachterWhenColorSetDefaultOptions() + { + img.DrawText("123", this.Font, Color.Red, Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); + Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Color.Red, brush.Color); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrush() + { + img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrushDefaultOptions() + { + img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + + [Fact] + public void DrawForEachACharachterWhenPenSet() + { + img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetDefaultOptions() + { + img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSet() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(6, img.ProcessorApplications.Count); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSetDefaultOptions() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(6, img.ProcessorApplications.Count); + } + + [Fact] + public void BrushAppliesBeforPen() + { + img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(2, img.ProcessorApplications.Count); + Assert.IsType>(img.ProcessorApplications[0].processor); + Assert.IsType>(img.ProcessorApplications[1].processor); + } + + [Fact] + public void BrushAppliesBeforPenDefaultOptions() + { + img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(2, img.ProcessorApplications.Count); + Assert.IsType>(img.ProcessorApplications[0].processor); + Assert.IsType>(img.ProcessorApplications[1].processor); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs new file mode 100644 index 000000000..1faa5edd3 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs @@ -0,0 +1,68 @@ + +namespace ImageSharp.Tests.Drawing.Text +{ + using ImageSharp.Drawing; + using SixLabors.Fonts; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Numerics; + using System.Threading.Tasks; + using Xunit; + + public class GlyphBuilderTests + { + [Fact] + public void OriginUsed() + { + // Y axis is inverted as it expects to be drawing for bottom left + var fullBuilder = new GlyphBuilder(new System.Numerics.Vector2(10, 99)); + IGlyphRenderer builder = fullBuilder; + + builder.BeginGlyph(); + builder.BeginFigure(); + builder.MoveTo(new Vector2(0, 0)); + builder.LineTo(new Vector2(0, 10)); // becomes 0, -10 + + builder.CubicBezierTo( + new Vector2(15, 15), // control point - will not be in the final point collection + new Vector2(15, 10), // control point - will not be in the final point collection + new Vector2(10, 10));// becomes 10, -10 + + builder.QuadraticBezierTo( + new Vector2(10, 5), // control point - will not be in the final point collection + new Vector2(10, 0)); + + builder.EndFigure(); + builder.EndGlyph(); + + var points = fullBuilder.Paths.Single().Flatten().Single().Points; + + Assert.Contains(new Vector2(10, 99), points); + Assert.Contains(new Vector2(10, 109), points); + Assert.Contains(new Vector2(20, 99), points); + Assert.Contains(new Vector2(20, 109), points); + } + + [Fact] + public void EachGlypeCausesNewPath() + { + // Y axis is inverted as it expects to be drawing for bottom left + GlyphBuilder fullBuilder = new GlyphBuilder(); + IGlyphRenderer builder = fullBuilder; + for (var i = 0; i < 10; i++) + { + builder.BeginGlyph(); + builder.BeginFigure(); + builder.MoveTo(new Vector2(0, 0)); + builder.LineTo(new Vector2(0, 10)); // becomes 0, -10 + builder.LineTo(new Vector2(10, 10));// becomes 10, -10 + builder.LineTo(new Vector2(10, 0)); + builder.EndFigure(); + builder.EndGlyph(); + } + + Assert.Equal(10, fullBuilder.Paths.Count()); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs new file mode 100644 index 000000000..ae007727a --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs @@ -0,0 +1,41 @@ + +namespace ImageSharp.Tests.Drawing.Text +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using SixLabors.Fonts; + + public class OutputText : FileTestBase + { + private readonly FontCollection FontCollection; + private readonly Font Font; + + public OutputText() + { + this.FontCollection = new FontCollection(); + this.Font = FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")); + } + + [Fact] + public void DrawAB() + { + //draws 2 overlapping triangle glyphs twice 1 set on each line + using (var img = new Image(100, 200)) + { + img.Fill(Color.DarkBlue) + .DrawText("AB\nAB", new Font(this.Font, 50), Color.Red, new Vector2(0, 0)); + img.Save($"{this.CreateOutputDirectory("Drawing", "Text")}/AB.png"); + } + } + } +} diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index f7f4386aa..765ff3a42 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Tests { using System.Collections.Generic; - using ImageSharp.Formats; /// /// The test base class for reading and writing to files. @@ -18,8 +17,8 @@ namespace ImageSharp.Tests /// protected static readonly List Files = new List { - TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), - TestFile.Create(TestImages.Jpeg.Baseline.Turtle), + TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), + // TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only @@ -28,10 +27,12 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only - TestFile.Create(TestImages.Bmp.Car), + TestFile.Create(TestImages.Bmp.Car), // TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only - TestFile.Create(TestImages.Png.Splash), - TestFile.Create(TestImages.Png.Powerpoint), + TestFile.Create(TestImages.Png.Splash), + // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.SplashInterlaced), // Perf: Enable for local testing only @@ -44,7 +45,7 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Png.FilterVar), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only - TestFile.Create(TestImages.Gif.Rings), // Perf: Enable for local testing only + TestFile.Create(TestImages.Gif.Rings), // TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only }; diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs similarity index 57% rename from tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs rename to tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index c91b0ad1b..497abb7d5 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -7,35 +7,29 @@ using ImageSharp.Formats; namespace ImageSharp.Tests { - using System.IO; - - using Formats; - using Xunit; - public class BitmapTests : FileTestBase + public class BmpEncoderTests : FileTestBase { public static readonly TheoryData BitsPerPixel = new TheoryData { - BmpBitsPerPixel.Pixel24 , + BmpBitsPerPixel.Pixel24, BmpBitsPerPixel.Pixel32 }; [Theory] - [MemberData("BitsPerPixel")] + [MemberData(nameof(BitsPerPixel))] public void BitmapCanEncodeDifferentBitRates(BmpBitsPerPixel bitsPerPixel) { - string path = CreateOutputDirectory("Bmp"); + string path = this.CreateOutputDirectory("Bmp"); foreach (TestFile file in Files) { string filename = file.GetFileNameWithoutExtension(bitsPerPixel); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}.bmp")) + using (Image image = file.CreateImage()) { - image.Save(output, new BmpEncoder { BitsPerPixel = bitsPerPixel }); + image.Save($"{path}/{filename}.bmp", new BmpEncoderOptions { BitsPerPixel = bitsPerPixel }); } } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 9ead4cf56..ae795c2ec 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -5,9 +5,7 @@ namespace ImageSharp.Tests { - using System; using System.IO; - using System.Numerics; using Xunit; @@ -16,17 +14,18 @@ namespace ImageSharp.Tests [Fact] public void ResolutionShouldChange() { - string path = CreateOutputDirectory("Resolution"); + string path = this.CreateOutputDirectory("Resolution"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (Image image = file.CreateImage()) { - image.VerticalResolution = 150; - image.HorizontalResolution = 150; - image.Save(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.MetaData.VerticalResolution = 150; + image.MetaData.HorizontalResolution = 150; + image.Save(output); + } } } } @@ -34,45 +33,31 @@ namespace ImageSharp.Tests [Fact] public void ImageCanEncodeToString() { - string path = CreateOutputDirectory("ToString"); + string path = this.CreateOutputDirectory("ToString"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; - File.WriteAllText(filename, image.ToBase64String()); + using (Image image = file.CreateImage()) + { + string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; + File.WriteAllText(filename, image.ToBase64String()); + } } } [Fact] public void DecodeThenEncodeImageFromStreamShouldSucceed() { - string path = CreateOutputDirectory("Encode"); + string path = this.CreateOutputDirectory("Encode"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (Image image = file.CreateImage()) { - image.Save(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Save(output); + } } } } @@ -80,35 +65,39 @@ namespace ImageSharp.Tests [Fact] public void QuantizeImageShouldPreserveMaximumColorPrecision() { - string path = CreateOutputDirectory("Quantize"); + string path = this.CreateOutputDirectory("Quantize"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - // Copy the original pixels to save decoding time. - Color[] pixels = new Color[image.Width * image.Height]; - Array.Copy(image.Pixels, pixels, image.Pixels.Length); - - using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) + using (Image srcImage = file.CreateImage()) { - image.Quantize(Quantization.Octree) - .Save(output, image.CurrentImageFormat); + using (Image image = new Image(srcImage)) + { + using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) + { + image.Quantize(Quantization.Octree) + .Save(output, image.CurrentImageFormat); - } + } + } - image.SetPixels(image.Width, image.Height, pixels); - using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) - { - image.Quantize(Quantization.Wu) - .Save(output, image.CurrentImageFormat); - } + using (Image image = new Image(srcImage)) + { + using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) + { + image.Quantize(Quantization.Wu) + .Save(output, image.CurrentImageFormat); + } + } - image.SetPixels(image.Width, image.Height, pixels); - using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) - { - image.Quantize(Quantization.Palette) - .Save(output, image.CurrentImageFormat); + using (Image image = new Image(srcImage)) + { + using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) + { + image.Quantize(Quantization.Palette) + .Save(output, image.CurrentImageFormat); + } + } } } } @@ -116,30 +105,31 @@ namespace ImageSharp.Tests [Fact] public void ImageCanConvertFormat() { - string path = CreateOutputDirectory("Format"); + string path = this.CreateOutputDirectory("Format"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) + using (Image image = file.CreateImage()) { - image.SaveAsGif(output); - } + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) + { + image.SaveAsBmp(output); + } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) - { - image.SaveAsBmp(output); - } + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) + { + image.SaveAsJpeg(output); + } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) - { - image.SaveAsJpeg(output); - } + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.SaveAsPng(output); + } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.SaveAsPng(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) + { + image.SaveAsGif(output); + } } } } @@ -147,13 +137,12 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldPreservePixelByteOrderWhenSerialized() { - string path = CreateOutputDirectory("Serialized"); + string path = this.CreateOutputDirectory("Serialized"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - byte[] serialized; + using (Image image = file.CreateImage()) using (MemoryStream memoryStream = new MemoryStream()) { image.Save(memoryStream); @@ -161,13 +150,10 @@ namespace ImageSharp.Tests serialized = memoryStream.ToArray(); } - using (MemoryStream memoryStream = new MemoryStream(serialized)) + using (Image image2 = new Image(serialized)) + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - Image image2 = new Image(memoryStream); - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image2.Save(output); - } + image2.Save(output); } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs new file mode 100644 index 000000000..b874a1585 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Text; + using Xunit; + + using ImageSharp.Formats; + + public class GifDecoderTests + { + [Fact] + public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() + { + var options = new DecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("Comments", image.MetaData.Properties[0].Name); + Assert.Equal("ImageSharp", image.MetaData.Properties[0].Value); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() + { + var options = new DecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(0, image.MetaData.Properties.Count); + } + } + + [Fact] + public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + { + var options = new GifDecoderOptions() + { + TextEncoding = Encoding.Unicode + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs new file mode 100644 index 000000000..da1323627 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using Xunit; + + using ImageSharp.Formats; + + public class GifEncoderTests + { + [Fact] + public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() + { + var options = new EncoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.Save(memStream, new GifFormat(), options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Equal(1, output.MetaData.Properties.Count); + Assert.Equal("Comments", output.MetaData.Properties[0].Name); + Assert.Equal("ImageSharp", output.MetaData.Properties[0].Value); + } + } + } + } + + [Fact] + public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() + { + var options = new GifEncoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.SaveAsGif(memStream, options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Equal(0, output.MetaData.Properties.Count); + } + } + } + } + + [Fact] + public void Encode_CommentIsToLong_CommentIsTrimmed() + { + using (Image input = new Image(1, 1)) + { + string comments = new string('c', 256); + input.MetaData.Properties.Add(new ImageProperty("Comments", comments)); + + using (MemoryStream memStream = new MemoryStream()) + { + input.Save(memStream, new GifFormat()); + + memStream.Position = 0; + using (Image output = new Image(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); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs index 628bc4ea9..8dbdb998c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs @@ -29,21 +29,25 @@ namespace ImageSharp.Tests [Theory] [WithFile(TestImages.Jpeg.Baseline.Bad.MissingEOF, PixelTypes.Color)] public void LoadBaselineImage(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - var image = provider.GetImage(); - Assert.NotNull(image); - provider.Utility.SaveTestOutputFile(image, "bmp"); + using (Image image = provider.GetImage()) + { + Assert.NotNull(image); + provider.Utility.SaveTestOutputFile(image, "bmp"); + } } [Theory] // TODO: #18 [WithFile(TestImages.Jpeg.Progressive.Bad.BadEOF, PixelTypes.Color)] public void LoadProgressiveImage(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - var image = provider.GetImage(); - Assert.NotNull(image); - provider.Utility.SaveTestOutputFile(image, "bmp"); + using (Image image = provider.GetImage()) + { + Assert.NotNull(image); + provider.Utility.SaveTestOutputFile(image, "bmp"); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 01ffa2eb0..7cb9a7cf2 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -27,21 +27,23 @@ namespace ImageSharp.Tests [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void OpenBaselineJpeg_SaveBmp(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - Image image = provider.GetImage(); - - provider.Utility.SaveTestOutputFile(image, "bmp"); + using (Image image = provider.GetImage()) + { + provider.Utility.SaveTestOutputFile(image, "bmp"); + } } - + [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void OpenProgressiveJpeg_SaveBmp(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - Image image = provider.GetImage(); - - provider.Utility.SaveTestOutputFile(image, "bmp"); + using (Image image = provider.GetImage()) + { + provider.Utility.SaveTestOutputFile(image, "bmp"); + } } [Theory] @@ -53,17 +55,20 @@ namespace ImageSharp.Tests public void DecodeGenerated_SaveBmp( TestImageProvider provider, JpegSubsample subsample, - int quality) - where TColor : struct, IPackedPixel, IEquatable + int quality) + where TColor : struct, IPixel { - Image image = provider.GetImage(); - - JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality }; - - byte[] data = new byte[65536]; - using (MemoryStream ms = new MemoryStream(data)) + byte[] data; + using (Image image = provider.GetImage()) { - image.Save(ms, encoder); + JpegEncoder encoder = new JpegEncoder(); + JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality }; + + data = new byte[65536]; + using (MemoryStream ms = new MemoryStream(data)) + { + image.Save(ms, encoder, options); + } } // TODO: Automatic image comparers could help here a lot :P @@ -75,25 +80,78 @@ namespace ImageSharp.Tests [Theory] [WithSolidFilledImages(42, 88, 255, 0, 0, PixelTypes.StandardImageClass)] public void DecodeGenerated_MetadataOnly( - TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + TestImageProvider provider) + where TColor : struct, IPixel { - Image image = provider.GetImage(); - - using (MemoryStream ms = new MemoryStream()) + using (Image image = provider.GetImage()) { - image.Save(ms, new JpegEncoder()); - ms.Seek(0, SeekOrigin.Begin); - - Image mirror = provider.Factory.CreateImage(1, 1); - using (JpegDecoderCore decoder = new JpegDecoderCore()) + using (MemoryStream ms = new MemoryStream()) { - decoder.Decode(mirror, ms, true); - - Assert.Equal(decoder.ImageWidth, image.Width); - Assert.Equal(decoder.ImageHeight, image.Height); + image.Save(ms, new JpegEncoder()); + ms.Seek(0, SeekOrigin.Begin); + + Image mirror = provider.Factory.CreateImage(1, 1); + using (JpegDecoderCore decoder = new JpegDecoderCore(null)) + { + decoder.Decode(mirror, ms, true); + + Assert.Equal(decoder.ImageWidth, image.Width); + Assert.Equal(decoder.ImageHeight, image.Height); + } } } } + + [Fact] + public void Decoder_Reads_Correct_Resolution_From_Jfif() + { + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) + { + Assert.Equal(300, image.MetaData.HorizontalResolution); + Assert.Equal(300, image.MetaData.VerticalResolution); + } + } + + [Fact] + public void Decoder_Reads_Correct_Resolution_From_Exif() + { + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Jpeg420).CreateImage()) + { + Assert.Equal(72, image.MetaData.HorizontalResolution); + Assert.Equal(72, image.MetaData.VerticalResolution); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() + { + var options = new DecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image image = testFile.CreateImage(options)) + { + Assert.NotNull(image.MetaData.ExifProfile); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored() + { + var options = new DecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Null(image.MetaData.ExifProfile); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index d1a5e185f..741e785c0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -32,43 +32,92 @@ namespace ImageSharp.Tests [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.StandardImageClass, 75, JpegSubsample.Ratio444)] [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.StandardImageClass, 75, JpegSubsample.Ratio444)] public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - Image image = provider.GetImage() - .Resize(new ResizeOptions - { - Size = new Size(150, 100), - Mode = ResizeMode.Max - }); - image.Quality = quality; - image.ExifProfile = null; // Reduce the size of the file - JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality }; - - provider.Utility.TestName += $"{subsample}_Q{quality}"; - provider.Utility.SaveTestOutputFile(image, "png"); - provider.Utility.SaveTestOutputFile(image, "jpg", encoder); + using (Image image = provider.GetImage().Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })) + { + image.MetaData.Quality = quality; + image.MetaData.ExifProfile = null; // Reduce the size of the file + JpegEncoder encoder = new JpegEncoder(); + JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality }; + + provider.Utility.TestName += $"{subsample}_Q{quality}"; + provider.Utility.SaveTestOutputFile(image, "png"); + provider.Utility.SaveTestOutputFile(image, "jpg", encoder, options); + } } [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio420, 75)] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio444, 75)] public void OpenBmp_SaveJpeg(TestImageProvider provider, JpegSubsample subSample, int quality) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel + { + using (Image image = provider.GetImage()) + { + ImagingTestCaseUtility utility = provider.Utility; + utility.TestName += "_" + subSample + "_Q" + quality; + + using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) + { + JpegEncoder encoder = new JpegEncoder(); + + image.Save(outputStream, encoder, new JpegEncoderOptions() + { + Subsample = subSample, + Quality = quality + }); + } + } + } + + [Fact] + public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten() { - Image image = provider.GetImage(); + var options = new EncoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.Save(memStream, new JpegFormat(), options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.NotNull(output.MetaData.ExifProfile); + } + } + } + } + + [Fact] + public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored() + { + var options = new JpegEncoderOptions() + { + IgnoreMetadata = true + }; - ImagingTestCaseUtility utility = provider.Utility; - utility.TestName += "_" + subSample + "_Q" + quality; + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - using (var outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) + using (Image input = testFile.CreateImage()) { - var encoder = new JpegEncoder() + using (MemoryStream memStream = new MemoryStream()) { - Subsample = subSample, - Quality = quality - }; + input.SaveAsJpeg(memStream, options); - image.Save(outputStream, encoder); + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Null(output.MetaData.ExifProfile); + } + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index bfe1f1e76..50e678bf0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -81,8 +81,9 @@ namespace ImageSharp.Tests { foreach (Image img in testImages) { - JpegEncoder encoder = new JpegEncoder() { Quality = quality, Subsample = subsample }; - img.Save(ms, encoder); + JpegEncoder encoder = new JpegEncoder(); + JpegEncoderOptions options = new JpegEncoderOptions { Quality = quality, Subsample = subsample }; + img.Save(ms, encoder, options); ms.Seek(0, SeekOrigin.Begin); } }, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs index cc7df178b..d0a7fae33 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs @@ -16,10 +16,9 @@ namespace ImageSharp.Tests public class JpegUtilsTests : TestBase { public static Image CreateTestImage(GenericFactory factory) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image image = factory.CreateImage(10, 10); - using (PixelAccessor pixels = image.Lock()) { for (int i = 0; i < 10; i++) @@ -35,78 +34,72 @@ namespace ImageSharp.Tests } } } + return image; } [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void CopyStretchedRGBTo_FromOrigo(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - Image src = provider.GetImage(); - - PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz); - Image dest = provider.Factory.CreateImage(8, 8); - - using (var s = src.Lock()) + using (Image src = provider.GetImage()) + using (Image dest = provider.Factory.CreateImage(8, 8)) + using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) + using (PixelAccessor s = src.Lock()) + using (PixelAccessor d = dest.Lock()) { - using (var d = dest.Lock()) - { - s.CopyRGBBytesStretchedTo(area, 0, 0); - d.CopyFrom(area, 0, 0); + s.CopyRGBBytesStretchedTo(area, 0, 0); + d.CopyFrom(area, 0, 0); - Assert.Equal(s[0, 0], d[0, 0]); - Assert.Equal(s[7, 0], d[7, 0]); - Assert.Equal(s[0, 7], d[0, 7]); - Assert.Equal(s[7, 7], d[7, 7]); - } + Assert.Equal(s[0, 0], d[0, 0]); + Assert.Equal(s[7, 0], d[7, 0]); + Assert.Equal(s[0, 7], d[0, 7]); + Assert.Equal(s[7, 7], d[7, 7]); } + } [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - Image src = provider.GetImage(); - - PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz); - Image dest = provider.Factory.CreateImage(8, 8); - + using (Image src = provider.GetImage()) + using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) + using (Image dest = provider.Factory.CreateImage(8, 8)) using (PixelAccessor s = src.Lock()) + using (PixelAccessor d = dest.Lock()) { - using (var d = dest.Lock()) - { - s.CopyRGBBytesStretchedTo(area, 7, 6); - d.CopyFrom(area, 0, 0); + s.CopyRGBBytesStretchedTo(area, 7, 6); + d.CopyFrom(area, 0, 0); - Assert.Equal(s[6, 7], d[0, 0]); - Assert.Equal(s[6, 8], d[0, 1]); - Assert.Equal(s[7, 8], d[1, 1]); + Assert.Equal(s[6, 7], d[0, 0]); + Assert.Equal(s[6, 8], d[0, 1]); + Assert.Equal(s[7, 8], d[1, 1]); - Assert.Equal(s[6, 9], d[0, 2]); - Assert.Equal(s[6, 9], d[0, 3]); - Assert.Equal(s[6, 9], d[0, 7]); + Assert.Equal(s[6, 9], d[0, 2]); + Assert.Equal(s[6, 9], d[0, 3]); + Assert.Equal(s[6, 9], d[0, 7]); - Assert.Equal(s[7, 9], d[1, 2]); - Assert.Equal(s[7, 9], d[1, 3]); - Assert.Equal(s[7, 9], d[1, 7]); + Assert.Equal(s[7, 9], d[1, 2]); + Assert.Equal(s[7, 9], d[1, 3]); + Assert.Equal(s[7, 9], d[1, 7]); - Assert.Equal(s[9, 9], d[3, 2]); - Assert.Equal(s[9, 9], d[3, 3]); - Assert.Equal(s[9, 9], d[3, 7]); + Assert.Equal(s[9, 9], d[3, 2]); + Assert.Equal(s[9, 9], d[3, 3]); + Assert.Equal(s[9, 9], d[3, 7]); - Assert.Equal(s[9, 7], d[3, 0]); - Assert.Equal(s[9, 7], d[4, 0]); - Assert.Equal(s[9, 7], d[7, 0]); + Assert.Equal(s[9, 7], d[3, 0]); + Assert.Equal(s[9, 7], d[4, 0]); + Assert.Equal(s[9, 7], d[7, 0]); - Assert.Equal(s[9, 9], d[3, 2]); - Assert.Equal(s[9, 9], d[4, 2]); - Assert.Equal(s[9, 9], d[7, 2]); + Assert.Equal(s[9, 9], d[3, 2]); + Assert.Equal(s[9, 9], d[4, 2]); + Assert.Equal(s[9, 9], d[7, 2]); - Assert.Equal(s[9, 9], d[4, 3]); - Assert.Equal(s[9, 9], d[7, 7]); - } + Assert.Equal(s[9, 9], d[4, 3]); + Assert.Equal(s[9, 9], d[7, 7]); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 10106ae6f..589317a36 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -6,8 +6,6 @@ // ReSharper disable InconsistentNaming namespace ImageSharp.Tests.Formats.Jpg { - using System.Numerics; - using ImageSharp.Formats; using ImageSharp.Formats.Jpg; using Xunit; @@ -97,7 +95,7 @@ namespace ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, new ApproximateFloatComparer(2f)); } } - + [Theory] [InlineData(42)] [InlineData(1)] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs new file mode 100644 index 000000000..921530806 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Text; + using Xunit; + + using ImageSharp.Formats; + + public class PngDecoderTests + { + [Fact] + public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() + { + var options = new PngDecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("Software", image.MetaData.Properties[0].Name); + Assert.Equal("paint.net 4.0.6", image.MetaData.Properties[0].Value); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() + { + var options = new PngDecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(0, image.MetaData.Properties.Count); + } + } + + [Fact] + public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + { + var options = new PngDecoderOptions() + { + TextEncoding = Encoding.Unicode + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs new file mode 100644 index 000000000..49be75139 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using ImageSharp.Formats; + +namespace ImageSharp.Tests +{ + using System.IO; + using System.Threading.Tasks; + + using Xunit; + + public class PngEncoderTests : FileTestBase + { + [Fact] + public void ImageCanSaveIndexedPng() + { + string path = CreateOutputDirectory("Png", "Indexed"); + + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) + { + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.MetaData.Quality = 256; + image.Save(output, new PngFormat()); + } + } + } + } + + [Fact] + public void ImageCanSavePngInParallel() + { + string path = this.CreateOutputDirectory("Png"); + + Parallel.ForEach( + Files, + file => + { + using (Image image = file.CreateImage()) + { + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.SaveAsPng(output); + } + } + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs deleted file mode 100644 index a76cbc615..000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using ImageSharp.Formats; - -namespace ImageSharp.Tests -{ - using System.IO; - using System.Threading.Tasks; - - using Formats; - - using Xunit; - - public class PngTests : FileTestBase - { - [Fact] - public void ImageCanSaveIndexedPng() - { - string path = CreateOutputDirectory("Png"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.Quality = 256; - image.Save(output, new PngFormat()); - } - } - } - - [Fact] - public void ImageCanSavePngInParallel() - { - string path = this.CreateOutputDirectory("Png"); - - Parallel.ForEach( - Files, - file => - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.SaveAsPng(output); - } - }); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 829dce748..0ed724fad 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -7,6 +7,8 @@ namespace ImageSharp.Tests { using System; + using ImageSharp.Formats; + using Xunit; /// @@ -23,10 +25,107 @@ namespace ImageSharp.Tests }); TestFile file = TestFile.Create(TestImages.Bmp.Car); - Image image = new Image(file.Bytes); + using (Image image = new Image(file.Bytes)) + { + Assert.Equal(600, image.Width); + Assert.Equal(450, image.Height); + } + } + + [Fact] + public void ConstructorFileSystem() + { + TestFile file = TestFile.Create(TestImages.Bmp.Car); + using (Image image = new Image(file.FilePath)) + { + Assert.Equal(600, image.Width); + Assert.Equal(450, image.Height); + } + } + + [Fact] + public void ConstructorFileSystem_FileNotFound() + { + System.IO.FileNotFoundException ex = Assert.Throws( + () => + { + new Image(Guid.NewGuid().ToString()); + }); + } + + [Fact] + public void ConstructorFileSystem_NullPath() + { + ArgumentNullException ex = Assert.Throws( + () => + { + new Image((string) null); + }); + } + + [Fact] + public void Save_DetecedEncoding() + { + string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.png"); + var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + using (Image image = new Image(10, 10)) + { + image.Save(file); + } + + var c = TestFile.Create("../../TestOutput/Save_DetecedEncoding.png"); + using (var img = c.CreateImage()) + { + Assert.IsType(img.CurrentImageFormat); + } + } + + [Fact] + public void Save_UnknownExtensionsEncoding() + { + string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.tmp"); + var ex = Assert.Throws( + () => + { + using (Image image = new Image(10, 10)) + { + image.Save(file); + } + }); + } + + [Fact] + public void Save_SetFormat() + { + string file = TestFile.GetPath("../../TestOutput/Save_SetFormat.dat"); + var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + using (Image image = new Image(10, 10)) + { + image.Save(file, new PngFormat()); + } + + var c = TestFile.Create("../../TestOutput/Save_SetFormat.dat"); + using (var img = c.CreateImage()) + { + Assert.IsType(img.CurrentImageFormat); + } + } - Assert.Equal(600, image.Width); - Assert.Equal(450, image.Height); + [Fact] + public void Save_SetEncoding() + { + string file = TestFile.GetPath("../../TestOutput/Save_SetEncoding.dat"); + var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + using (Image image = new Image(10, 10)) + { + image.Save(file, new PngEncoder()); + } + + var c = TestFile.Create("../../TestOutput/Save_SetEncoding.dat"); + using (var img = c.CreateImage()) + { + Assert.IsType(img.CurrentImageFormat); + } } } } diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs index ce28518d7..f3cd20f45 100644 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs @@ -16,11 +16,10 @@ namespace ImageSharp.Tests public class PixelAccessorTests { public static Image CreateTestImage(GenericFactory factory) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image image = factory.CreateImage(10, 10); - - using (var pixels = image.Lock()) + using (PixelAccessor pixels = image.Lock()) { for (int i = 0; i < 10; i++) { @@ -45,33 +44,35 @@ namespace ImageSharp.Tests [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Xyzw)] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Zyxw)] public void CopyTo_Then_CopyFrom_OnFullImageRect(TestImageProvider provider, ComponentOrder order) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - var src = provider.GetImage(); - - var dest = new Image(src.Width, src.Height); - - using (PixelArea area = new PixelArea(src.Width, src.Height, order)) + using (Image src = provider.GetImage()) { - using (var srcPixels = src.Lock()) + using (Image dest = new Image(src.Width, src.Height)) { - srcPixels.CopyTo(area, 0, 0); - } + using (PixelArea area = new PixelArea(src.Width, src.Height, order)) + { + using (PixelAccessor srcPixels = src.Lock()) + { + srcPixels.CopyTo(area, 0, 0); + } + + using (PixelAccessor destPixels = dest.Lock()) + { + destPixels.CopyFrom(area, 0, 0); + } + } - using (var destPixels = dest.Lock()) - { - destPixels.CopyFrom(area, 0, 0); + Assert.True(src.IsEquivalentTo(dest, false)); } } - - Assert.True(src.IsEquivalentTo(dest, false)); } // TODO: Need a processor in the library with this signature private static void Fill(Image image, Rectangle region, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { - using (var pixels = image.Lock()) + using (PixelAccessor pixels = image.Lock()) { for (int y = region.Top; y < region.Bottom; y++) { @@ -88,91 +89,113 @@ namespace ImageSharp.Tests [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyx)] [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Xyzw)] [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyxw)] - public void CopyTo_Then_CopyFrom_WithOffset(TestImageProvider provider, ComponentOrder order) - where TColor : struct, IPackedPixel, IEquatable - + public void CopyToThenCopyFromWithOffset(TestImageProvider provider, ComponentOrder order) + where TColor : struct, IPixel { - var srcImage = provider.GetImage(); - - var color = default(TColor); - color.PackFromBytes(255, 0, 0, 255); - - Fill(srcImage, new Rectangle(4, 4, 8, 8), color); - - var destImage = new Image(8, 8); - - using (var srcPixels = srcImage.Lock()) + using (Image destImage = new Image(8, 8)) { - using (var area = new PixelArea(8, 8, order)) + using (Image srcImage = provider.GetImage()) { - srcPixels.CopyTo(area, 4, 4); - - using (var destPixels = destImage.Lock()) + Fill(srcImage, new Rectangle(4, 4, 8, 8), NamedColors.Red); + using (PixelAccessor srcPixels = srcImage.Lock()) { - destPixels.CopyFrom(area, 0, 0); + using (PixelArea area = new PixelArea(8, 8, order)) + { + srcPixels.CopyTo(area, 4, 4); + + using (PixelAccessor destPixels = destImage.Lock()) + { + destPixels.CopyFrom(area, 0, 0); + } + } } } - } - provider.Utility.SourceFileOrDescription = order.ToString(); - provider.Utility.SaveTestOutputFile(destImage, "bmp"); + provider.Utility.SourceFileOrDescription = order.ToString(); + provider.Utility.SaveTestOutputFile(destImage, "bmp"); - var expectedImage = new Image(8, 8).Fill(color); - - Assert.True(destImage.IsEquivalentTo(expectedImage)); + using (Image expectedImage = new Image(8, 8).Fill(NamedColors.Red)) + { + Assert.True(destImage.IsEquivalentTo(expectedImage)); + } + } } [Fact] public void CopyFromZYX() { - CopyFromZYX(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyFromZYX(image); + } } [Fact] public void CopyFromZYXOptimized() { - CopyFromZYX(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyFromZYX(image); + } } [Fact] public void CopyFromZYXW() { - CopyFromZYXW(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyFromZYXW(image); + } } [Fact] public void CopyFromZYXWOptimized() { - CopyFromZYXW(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyFromZYXW(image); + } } [Fact] public void CopyToZYX() { - CopyToZYX(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyToZYX(image); + } } [Fact] public void CopyToZYXOptimized() { - CopyToZYX(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyToZYX(image); + } } [Fact] public void CopyToZYXW() { - CopyToZYXW(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyToZYXW(image); + } } [Fact] public void CopyToZYXWOptimized() { - CopyToZYXW(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyToZYXW(image); + } } private static void CopyFromZYX(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { @@ -199,7 +222,7 @@ namespace ImageSharp.Tests } private static void CopyFromZYXW(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { @@ -227,7 +250,7 @@ namespace ImageSharp.Tests } private static void CopyToZYX(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { @@ -249,7 +272,7 @@ namespace ImageSharp.Tests } private static void CopyToZYXW(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { diff --git a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs new file mode 100644 index 000000000..24dd2eac5 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the class. + /// + public class ImageFrameMetaDataTests + { + [Fact] + public void ConstructorImageFrameMetaData() + { + ImageFrameMetaData metaData = new ImageFrameMetaData(); + metaData.FrameDelay = 42; + + ImageFrameMetaData clone = new ImageFrameMetaData(metaData); + + Assert.Equal(42, clone.FrameDelay); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs new file mode 100644 index 000000000..3c0057b62 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the class. + /// + public class ImageMetaDataTests + { + [Fact] + public void ConstructorImageMetaData() + { + ImageMetaData metaData = new ImageMetaData(); + + ExifProfile exifProfile = new ExifProfile(); + ImageProperty imageProperty = new ImageProperty("name", "value"); + + metaData.ExifProfile = exifProfile; + metaData.FrameDelay = 42; + metaData.HorizontalResolution = 4; + metaData.VerticalResolution = 2; + metaData.Properties.Add(imageProperty); + metaData.Quality = 24; + metaData.RepeatCount = 1; + + ImageMetaData clone = new ImageMetaData(metaData); + + Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); + Assert.Equal(42, clone.FrameDelay); + Assert.Equal(4, clone.HorizontalResolution); + Assert.Equal(2, clone.VerticalResolution); + Assert.Equal(imageProperty, clone.Properties[0]); + Assert.Equal(24, clone.Quality); + Assert.Equal(1, clone.RepeatCount); + } + + [Fact] + public void HorizontalResolution() + { + ImageMetaData metaData = new ImageMetaData(); + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution=0; + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution=-1; + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution=1; + Assert.Equal(1, metaData.HorizontalResolution); + } + + [Fact] + public void VerticalResolution() + { + ImageMetaData metaData = new ImageMetaData(); + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = 0; + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = -1; + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = 1; + Assert.Equal(1, metaData.VerticalResolution); + } + + [Fact] + public void SyncProfiles() + { + ExifProfile exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + + Image image = new Image(1, 1); + image.MetaData.ExifProfile = exifProfile; + image.MetaData.HorizontalResolution = 400; + image.MetaData.VerticalResolution = 500; + + image.MetaData.SyncProfiles(); + + Assert.Equal(400, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(500, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImagePropertyTests.cs b/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Image/ImagePropertyTests.cs rename to tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs similarity index 58% rename from tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs rename to tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 13fe24f50..8ec57057f 100644 --- a/tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -19,17 +19,17 @@ namespace ImageSharp.Tests { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateImage(); - Assert.Null(image.ExifProfile); + Assert.Null(image.MetaData.ExifProfile); - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); + image.MetaData.ExifProfile = new ExifProfile(); + image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); image = WriteAndRead(image); - Assert.NotNull(image.ExifProfile); - Assert.Equal(1, image.ExifProfile.Values.Count()); + Assert.NotNull(image.MetaData.ExifProfile); + Assert.Equal(1, image.MetaData.ExifProfile.Values.Count()); - ExifValue value = image.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); + ExifValue value = image.MetaData.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); TestValue(value, "Dirk Lemstra"); } @@ -68,14 +68,14 @@ namespace ImageSharp.Tests profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); Image image = new Image(1, 1); - image.ExifProfile = profile; + image.MetaData.ExifProfile = profile; image.SaveAsJpeg(memStream); memStream.Position = 0; image = new Image(memStream); - profile = image.ExifProfile; + profile = image.MetaData.ExifProfile; Assert.NotNull(profile); ExifValue value = profile.GetValue(ExifTag.ExposureTime); @@ -86,14 +86,14 @@ namespace ImageSharp.Tests profile = GetExifProfile(); profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); - image.ExifProfile = profile; + image.MetaData.ExifProfile = profile; image.SaveAsJpeg(memStream); memStream.Position = 0; image = new Image(memStream); - profile = image.ExifProfile; + profile = image.MetaData.ExifProfile; Assert.NotNull(profile); value = profile.GetValue(ExifTag.ExposureTime); @@ -105,24 +105,24 @@ namespace ImageSharp.Tests public void ReadWriteInfinity() { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); - image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); + image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); image = WriteAndRead(image); - ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); Assert.NotNull(value); Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); - image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); + image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); image = WriteAndRead(image); - value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); Assert.NotNull(value); Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); - image.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); + image.MetaData.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); image = WriteAndRead(image); - value = image.ExifProfile.GetValue(ExifTag.FlashEnergy); + value = image.MetaData.ExifProfile.GetValue(ExifTag.FlashEnergy); Assert.NotNull(value); Assert.Equal(new Rational(double.PositiveInfinity), value.Value); } @@ -133,71 +133,107 @@ namespace ImageSharp.Tests Rational[] 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.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); + image.MetaData.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); - ExifValue value = image.ExifProfile.GetValue(ExifTag.Software); + ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.Software); TestValue(value, "ImageSharp"); Assert.Throws(() => { value.Value = 15; }); - image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); + image.MetaData.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); - value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); TestValue(value, new SignedRational(7555, 100)); Assert.Throws(() => { value.Value = 75; }); - image.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); + image.MetaData.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); - value = image.ExifProfile.GetValue(ExifTag.XResolution); + // We also need to change this value because this overrides XResolution when the image is written. + image.MetaData.HorizontalResolution = 150.0; + + value = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); TestValue(value, new Rational(150, 1)); Assert.Throws(() => { value.Value = "ImageSharp"; }); - image.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); + image.MetaData.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); - value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); TestValue(value, (string)null); - image.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); + image.MetaData.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); - value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); + value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); TestValue(value, latitude); image = WriteAndRead(image); - Assert.NotNull(image.ExifProfile); - Assert.Equal(17, image.ExifProfile.Values.Count()); + Assert.NotNull(image.MetaData.ExifProfile); + Assert.Equal(17, image.MetaData.ExifProfile.Values.Count()); - value = image.ExifProfile.GetValue(ExifTag.Software); + value = image.MetaData.ExifProfile.GetValue(ExifTag.Software); TestValue(value, "ImageSharp"); - value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); TestValue(value, new SignedRational(75.55)); - value = image.ExifProfile.GetValue(ExifTag.XResolution); + value = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); TestValue(value, new Rational(150.0)); - value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); Assert.Null(value); - value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); + value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); TestValue(value, latitude); - image.ExifProfile.Parts = ExifParts.ExifTags; + image.MetaData.ExifProfile.Parts = ExifParts.ExifTags; image = WriteAndRead(image); - Assert.NotNull(image.ExifProfile); - Assert.Equal(8, image.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.Equal(7, image.MetaData.ExifProfile.Values.Count()); + } + + [Fact] + public void Syncs() + { + ExifProfile exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + + ImageMetaData metaData = new ImageMetaData(); + metaData.ExifProfile = exifProfile; + metaData.HorizontalResolution = 200; + metaData.VerticalResolution = 300; + + metaData.HorizontalResolution = 100; + + Assert.Equal(200, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + exifProfile.Sync(metaData); + + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + metaData.VerticalResolution = 150; + + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); - Assert.NotNull(image.ExifProfile.GetValue(ExifTag.ColorSpace)); - Assert.True(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.False(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.Null(image.ExifProfile.GetValue(ExifTag.ColorSpace)); + exifProfile.Sync(metaData); - Assert.Equal(7, image.ExifProfile.Values.Count()); + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(150, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); } [Fact] @@ -223,8 +259,8 @@ namespace ImageSharp.Tests } Image image = new Image(100, 100); - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); + image.MetaData.ExifProfile = new ExifProfile(); + image.MetaData.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); using (MemoryStream memStream = new MemoryStream()) { @@ -236,7 +272,7 @@ namespace ImageSharp.Tests { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); - ExifProfile profile = image.ExifProfile; + ExifProfile profile = image.MetaData.ExifProfile; Assert.NotNull(profile); return profile; @@ -247,6 +283,7 @@ namespace ImageSharp.Tests using (MemoryStream memStream = new MemoryStream()) { image.SaveAsJpeg(memStream); + image.Dispose(); memStream.Position = 0; return new Image(memStream); diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs similarity index 97% rename from tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs rename to tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs index 4f77dc11a..daad49b2c 100644 --- a/tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Tests public class ExifDescriptionAttributeTests { [Fact] - public void Test_ExifTag() + public void TestExifTag() { var exifProfile = new ExifProfile(); diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs similarity index 84% rename from tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs rename to tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs index 5993d1720..2014d08dc 100644 --- a/tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs @@ -12,9 +12,12 @@ namespace ImageSharp.Tests { private static ExifValue GetExifValue() { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); + ExifProfile profile; + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) + { + profile = image.MetaData.ExifProfile; + } - ExifProfile profile = image.ExifProfile; Assert.NotNull(profile); return profile.Values.First(); diff --git a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs index efbfe75a8..a8aeb3341 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs @@ -19,39 +19,35 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("AlphaValues")] + [MemberData(nameof(AlphaValues))] public void ImageShouldApplyAlphaFilter(int value) { - string path = CreateOutputDirectory("Alpha"); + string path = this.CreateOutputDirectory("Alpha"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Alpha(value) - .Save(output); + image.Alpha(value).Save(output); } } } [Theory] - [MemberData("AlphaValues")] + [MemberData(nameof(AlphaValues))] public void ImageShouldApplyAlphaFilterInBox(int value) { - string path = CreateOutputDirectory("Alpha"); + string path = this.CreateOutputDirectory("Alpha"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Alpha(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); + image.Alpha(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs index 10d6253fd..ef183480c 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs @@ -26,25 +26,22 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("OrientationValues")] + [MemberData(nameof(OrientationValues))] public void ImageShouldFlip(RotateType rotateType, FlipType flipType, ushort orientation) { - string path = CreateOutputDirectory("AutoOrient"); + string path = this.CreateOutputDirectory("AutoOrient"); TestFile file = TestFile.Create(TestImages.Bmp.F); - Image image = file.CreateImage(); - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.Orientation, orientation); - - using (FileStream before = File.OpenWrite($"{path}/before-{file.FileName}")) + using (Image image = file.CreateImage()) { + image.MetaData.ExifProfile = new ExifProfile(); + image.MetaData.ExifProfile.SetValue(ExifTag.Orientation, orientation); + + using (FileStream before = File.OpenWrite($"{path}/before-{file.FileName}")) using (FileStream after = File.OpenWrite($"{path}/after-{file.FileName}")) { - image.RotateFlip(rotateType, flipType) - .Save(before) - .AutoOrient() - .Save(after); + image.RotateFlip(rotateType, flipType).Save(before).AutoOrient().Save(after); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs index a7ecf6c08..fd08b87a4 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyBackgroundColorFilter() { - string path = CreateOutputDirectory("BackgroundColor"); + string path = this.CreateOutputDirectory("BackgroundColor"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.BackgroundColor(Color.HotPink) - .Save(output); + image.BackgroundColor(Color.HotPink).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BinaryThreshold.cs b/tests/ImageSharp.Tests/Processors/Filters/BinaryThreshold.cs index 10b174cd5..d7d4eac05 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BinaryThreshold.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BinaryThreshold.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("BinaryThresholdValues")] + [MemberData(nameof(BinaryThresholdValues))] public void ImageShouldApplyBinaryThresholdFilter(float value) { - string path = CreateOutputDirectory("BinaryThreshold"); + string path = this.CreateOutputDirectory("BinaryThreshold"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.BinaryThreshold(value) - .Save(output); + image.BinaryThreshold(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs index d4af4ad38..6b9a48f72 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyBlackWhiteFilter() { - string path = CreateOutputDirectory("BlackWhite"); + string path = this.CreateOutputDirectory("BlackWhite"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.BlackWhite() - .Save(output); + image.BlackWhite().Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs index 4755acb1e..5d4f628ee 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("BoxBlurValues")] + [MemberData(nameof(BoxBlurValues))] public void ImageShouldApplyBoxBlurFilter(int value) { - string path = CreateOutputDirectory("BoxBlur"); + string path = this.CreateOutputDirectory("BoxBlur"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.BoxBlur(value) - .Save(output); + image.BoxBlur(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs index e32c7d35e..e274ef041 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("BrightnessValues")] + [MemberData(nameof(BrightnessValues))] public void ImageShouldApplyBrightnessFilter(int value) { - string path = CreateOutputDirectory("Brightness"); + string path = this.CreateOutputDirectory("Brightness"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Brightness(value) - .Save(output); + image.Brightness(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs index 63005733a..d18f32caf 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs @@ -26,20 +26,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("ColorBlindnessFilters")] + [MemberData(nameof(ColorBlindnessFilters))] public void ImageShouldApplyColorBlindnessFilter(ColorBlindness colorBlindness) { - string path = CreateOutputDirectory("ColorBlindness"); + string path = this.CreateOutputDirectory("ColorBlindness"); foreach (TestFile file in Files) { string filename = file.GetFileName(colorBlindness); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.ColorBlindness(colorBlindness) - .Save(output); + image.ColorBlindness(colorBlindness).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs index 3c83fd892..09376f2c0 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs @@ -19,19 +19,17 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("ContrastValues")] + [MemberData(nameof(ContrastValues))] public void ImageShouldApplyContrastFilter(int value) { - string path = CreateOutputDirectory("Contrast"); + string path = this.CreateOutputDirectory("Contrast"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Contrast(value) - .Save(output); + image.Contrast(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs b/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs index 9e9dd34db..69c9d9372 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyCropSampler() { - string path = CreateOutputDirectory("Crop"); + string path = this.CreateOutputDirectory("Crop"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Crop(image.Width / 2, image.Height / 2) - .Save(output); + image.Crop(image.Width / 2, image.Height / 2).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs index 1c3815b9b..e12440106 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs @@ -31,32 +31,29 @@ namespace ImageSharp.Tests [MemberData(nameof(DetectEdgesFilters))] public void ImageShouldApplyDetectEdgesFilter(EdgeDetection detector) { - string path = CreateOutputDirectory("DetectEdges"); + string path = this.CreateOutputDirectory("DetectEdges"); foreach (TestFile file in Files) { string filename = file.GetFileName(detector); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.DetectEdges(detector) - .Save(output); + image.DetectEdges(detector).Save(output); } } } [Theory] - [MemberData("DetectEdgesFilters")] + [MemberData(nameof(DetectEdgesFilters))] public void ImageShouldApplyDetectEdgesFilterInBox(EdgeDetection detector) { - string path = CreateOutputDirectory("DetectEdges"); + string path = this.CreateOutputDirectory("DetectEdges"); foreach (TestFile file in Files) { string filename = file.GetFileName(detector + "-InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.DetectEdges(detector, new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) diff --git a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs new file mode 100644 index 000000000..e89a1b1ec --- /dev/null +++ b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using ImageSharp.Dithering; + using ImageSharp.Dithering.Ordered; + + using Xunit; + + public class DitherTest : FileTestBase + { + public static readonly TheoryData Ditherers = new TheoryData + { + { "Ordered", new Ordered() }, + { "Bayer", new Bayer() } + }; + + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + { "Atkinson", new Atkinson() }, + { "Burks", new Burks() }, + { "FloydSteinberg", new FloydSteinberg() }, + { "JarvisJudiceNinke", new JarvisJudiceNinke() }, + { "Sierra2", new Sierra2() }, + { "Sierra3", new Sierra3() }, + { "SierraLite", new SierraLite() }, + { "Stucki", new Stucki() }, + }; + + [Theory] + [MemberData(nameof(Ditherers))] + public void ImageShouldApplyDitherFilter(string name, IOrderedDither ditherer) + { + string path = this.CreateOutputDirectory("Dither", "Dither"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Dither(ditherer).Save(output); + } + } + } + + [Theory] + [MemberData(nameof(Ditherers))] + public void ImageShouldApplyDitherFilterInBox(string name, IOrderedDither ditherer) + { + string path = this.CreateOutputDirectory("Dither", "Dither"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName($"{name}-InBox"); + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Dither(ditherer, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); + } + } + } + + [Theory] + [MemberData(nameof(ErrorDiffusers))] + public void ImageShouldApplyDiffusionFilter(string name, IErrorDiffuser diffuser) + { + string path = this.CreateOutputDirectory("Dither", "Diffusion"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Dither(diffuser, .5F).Save(output); + } + } + } + + [Theory] + [MemberData(nameof(ErrorDiffusers))] + public void ImageShouldApplyDiffusionFilterInBox(string name, IErrorDiffuser diffuser) + { + string path = this.CreateOutputDirectory("Dither", "Diffusion"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName($"{name}-InBox"); + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Dither(diffuser, .5F, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs index fdbbc5cde..1299d9814 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("EntropyCropValues")] + [MemberData(nameof(EntropyCropValues))] public void ImageShouldApplyEntropyCropSampler(float value) { - string path = CreateOutputDirectory("EntropyCrop"); + string path = this.CreateOutputDirectory("EntropyCrop"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.EntropyCrop(value) - .Save(output); + image.EntropyCrop(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs b/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs index 26f964676..26bc240d5 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs @@ -20,20 +20,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("FlipValues")] + [MemberData(nameof(FlipValues))] public void ImageShouldFlip(FlipType flipType) { - string path = CreateOutputDirectory("Flip"); + string path = this.CreateOutputDirectory("Flip"); foreach (TestFile file in Files) { string filename = file.GetFileName(flipType); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Flip(flipType) - .Save(output); + image.Flip(flipType).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs index 6a8279689..809ffa2f5 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("GaussianBlurValues")] + [MemberData(nameof(GaussianBlurValues))] public void ImageShouldApplyGaussianBlurFilter(int value) { - string path = CreateOutputDirectory("GaussianBlur"); + string path = this.CreateOutputDirectory("GaussianBlur"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.GaussianBlur(value) - .Save(output); + image.GaussianBlur(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs index 69c24ab3b..c1aa06941 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("GaussianSharpenValues")] + [MemberData(nameof(GaussianSharpenValues))] public void ImageShouldApplyGaussianSharpenFilter(int value) { - string path = CreateOutputDirectory("GaussianSharpen"); + string path = this.CreateOutputDirectory("GaussianSharpen"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.GaussianSharpen(value) - .Save(output); + image.GaussianSharpen(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs index 1d317795d..1afb1300a 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyGlowFilter() { - string path = CreateOutputDirectory("Glow"); + string path = this.CreateOutputDirectory("Glow"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Glow() - .Save(output); + image.Glow().Save(output); } } } @@ -31,17 +29,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyGlowFilterColor() { - string path = CreateOutputDirectory("Glow"); + string path = this.CreateOutputDirectory("Glow"); foreach (TestFile file in Files) { string filename = file.GetFileName("Color"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Glow(Color.HotPink) - .Save(output); + image.Glow(Color.HotPink).Save(output); } } } @@ -49,17 +45,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyGlowFilterRadius() { - string path = CreateOutputDirectory("Glow"); + string path = this.CreateOutputDirectory("Glow"); foreach (TestFile file in Files) { string filename = file.GetFileName("Radius"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Glow(image.Width / 4) - .Save(output); + image.Glow(image.Width / 4F).Save(output); } } } @@ -67,17 +61,16 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyGlowFilterInBox() { - string path = CreateOutputDirectory("Glow"); + string path = this.CreateOutputDirectory("Glow"); foreach (TestFile file in Files) { string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Glow(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); + .Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs index ee4d0b027..91f383dd2 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs @@ -20,20 +20,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("GrayscaleValues")] + [MemberData(nameof(GrayscaleValues))] public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value) { - string path = CreateOutputDirectory("Grayscale"); + string path = this.CreateOutputDirectory("Grayscale"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Grayscale(value) - .Save(output); + image.Grayscale(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs index a56aec9ec..4241dc833 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("HueValues")] + [MemberData(nameof(HueValues))] public void ImageShouldApplyHueFilter(int value) { - string path = CreateOutputDirectory("Hue"); + string path = this.CreateOutputDirectory("Hue"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Hue(value) - .Save(output); + image.Hue(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs index 55bfa852b..da672f830 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs @@ -14,15 +14,13 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyInvertFilter() { - string path = CreateOutputDirectory("Invert"); + string path = this.CreateOutputDirectory("Invert"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Invert() - .Save(output); + image.Invert().Save(output); } } } @@ -30,17 +28,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyInvertFilterInBox() { - string path = CreateOutputDirectory("Invert"); + string path = this.CreateOutputDirectory("Invert"); foreach (TestFile file in Files) { string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Invert(new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); + image.Invert(new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs index adb7cb36d..40734e02a 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyKodachromeFilter() { - string path = CreateOutputDirectory("Kodachrome"); + string path = this.CreateOutputDirectory("Kodachrome"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Kodachrome() - .Save(output); + image.Kodachrome().Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs index 79a7aa3ba..57ca72d39 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyLomographFilter() { - string path = CreateOutputDirectory("Lomograph"); + string path = this.CreateOutputDirectory("Lomograph"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Lomograph() - .Save(output); + image.Lomograph().Save(output); } } } @@ -31,17 +29,16 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyLomographFilterInBox() { - string path = CreateOutputDirectory("Lomograph"); + string path = this.CreateOutputDirectory("Lomograph"); foreach (TestFile file in Files) { string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Lomograph(new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)) - .Save(output); + .Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs b/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs index c177e9423..a9b552e21 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs @@ -23,19 +23,17 @@ namespace ImageSharp.Tests [MemberData(nameof(OilPaintValues))] public void ImageShouldApplyOilPaintFilter(Tuple value) { - string path = CreateOutputDirectory("OilPaint"); + string path = this.CreateOutputDirectory("OilPaint"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { if (image.Width > value.Item2 && image.Height > value.Item2) { - image.OilPaint(value.Item1, value.Item2) - .Save(output); + image.OilPaint(value.Item1, value.Item2).Save(output); } } } @@ -45,18 +43,19 @@ namespace ImageSharp.Tests [MemberData(nameof(OilPaintValues))] public void ImageShouldApplyOilPaintFilterInBox(Tuple value) { - string path = CreateOutputDirectory("OilPaint"); + string path = this.CreateOutputDirectory("OilPaint"); foreach (TestFile file in Files) { string filename = file.GetFileName(value + "-InBox"); - Image image = file.CreateImage(); - - if (image.Width > value.Item2 && image.Height > value.Item2) + using (Image image = file.CreateImage()) { - using (FileStream output = File.OpenWrite($"{path}/{filename}")) + if (image.Width > value.Item2 && image.Height > value.Item2) { - image.OilPaint(value.Item1, value.Item2, new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)).Save(output); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.OilPaint(value.Item1, value.Item2, new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)).Save(output); + } } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs index 5a33aac2b..f00cdd4f3 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyPadSampler() { - string path = CreateOutputDirectory("Pad"); + string path = this.CreateOutputDirectory("Pad"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Pad(image.Width + 50, image.Height + 50) - .Save(output); + image.Pad(image.Width + 50, image.Height + 50).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs index 38ec406ac..3a5fbc556 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs @@ -41,17 +41,15 @@ namespace ImageSharp.Tests [MemberData(nameof(PixelateValues))] public void ImageShouldApplyPixelateFilterInBox(int value) { - string path = CreateOutputDirectory("Pixelate"); + string path = this.CreateOutputDirectory("Pixelate"); foreach (TestFile file in Files) { string filename = file.GetFileName(value + "-InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Pixelate(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); + image.Pixelate(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs index dc9d3a150..040f8b4a2 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyPolaroidFilter() { - string path = CreateOutputDirectory("Polaroid"); + string path = this.CreateOutputDirectory("Polaroid"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Polaroid() - .Save(output); + image.Polaroid().Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index a57d7f6c4..06ab245c9 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -42,12 +42,31 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Resize(image.Width / 2, image.Height / 2, sampler, true).Save(output); + } + } + } + + [Theory] + [MemberData(nameof(ReSamplers))] + public void ImageShouldResizeFromSourceRectangle(string name, IResampler sampler) + { + name = $"{name}-SourceRect"; + string path = this.CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Resize(image.Width / 2, image.Height / 2, sampler, true) - .Save(output); + 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.Resize(image.Width, image.Height, sampler, sourceRectangle, destRectangle, false).Save(output); } } } @@ -63,12 +82,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Resize(image.Width / 3, 0, sampler, false) - .Save(output); + image.Resize(image.Width / 3, 0, sampler, false).Save(output); } } } @@ -84,12 +101,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Resize(0, image.Height / 3, sampler, false) - .Save(output); + image.Resize(0, image.Height / 3, sampler, false).Save(output); } } } @@ -105,18 +120,16 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(image.Width / 2, image.Height) }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -132,18 +145,16 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(image.Width, image.Height / 2) }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -159,18 +170,16 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Size = new Size(image.Width + 200, image.Height), Mode = ResizeMode.Pad }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -186,19 +195,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(image.Width + 200, image.Height + 200), Mode = ResizeMode.BoxPad }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -214,19 +221,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(300, 300), Mode = ResizeMode.Max }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -242,19 +247,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, - Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * 95F)), + Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)), Mode = ResizeMode.Min }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -270,19 +273,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(image.Width / 2, image.Height), Mode = ResizeMode.Stretch }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs b/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs index 1cccef48a..e235ed229 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs @@ -22,20 +22,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("RotateFlipValues")] + [MemberData(nameof(RotateFlipValues))] public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) { - string path = CreateOutputDirectory("RotateFlip"); + string path = this.CreateOutputDirectory("RotateFlip"); foreach (TestFile file in Files) { string filename = file.GetFileName(rotateType + "-" + flipType); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.RotateFlip(rotateType, flipType) - .Save(output); + image.RotateFlip(rotateType, flipType).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs b/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs index 406edda37..a504fd989 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs @@ -28,39 +28,35 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("RotateFloatValues")] + [MemberData(nameof(RotateFloatValues))] public void ImageShouldApplyRotateSampler(float value) { - string path = CreateOutputDirectory("Rotate"); + string path = this.CreateOutputDirectory("Rotate"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Rotate(value) - .Save(output); + image.Rotate(value).Save(output); } } } [Theory] - [MemberData("RotateEnumValues")] + [MemberData(nameof(RotateEnumValues))] public void ImageShouldApplyRotateSampler(RotateType value) { - string path = CreateOutputDirectory("Rotate"); + string path = this.CreateOutputDirectory("Rotate"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Rotate(value) - .Save(output); + image.Rotate(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs index 5fe4c3e00..abd596d70 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("SaturationValues")] + [MemberData(nameof(SaturationValues))] public void ImageShouldApplySaturationFilter(int value) { string path = CreateOutputDirectory("Saturation"); @@ -27,12 +27,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Saturation(value) - .Save(output); + image.Saturation(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs index b5e4d3105..fbae10fa5 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs @@ -18,12 +18,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Sepia() - .Save(output); + image.Sepia().Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs index 11db7e691..231f5dae8 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs @@ -19,22 +19,20 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("SkewValues")] + [MemberData(nameof(SkewValues))] public void ImageShouldApplySkewSampler(float x, float y) { - string path = CreateOutputDirectory("Skew"); + string path = this.CreateOutputDirectory("Skew"); - // Matches live example + // Matches live example // http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew foreach (TestFile file in Files) { string filename = file.GetFileName(x + "-" + y); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Skew(x, y) - .Save(output); + image.Skew(x, y).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs b/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs index 3fddad1da..7f40ef1d2 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyVignetteFilter() { - string path = CreateOutputDirectory("Vignette"); + string path = this.CreateOutputDirectory("Vignette"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Vignette() - .Save(output); + image.Vignette().Save(output); } } } @@ -31,17 +29,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyVignetteFilterColor() { - string path = CreateOutputDirectory("Vignette"); + string path = this.CreateOutputDirectory("Vignette"); foreach (TestFile file in Files) { string filename = file.GetFileName("Color"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Vignette(Color.HotPink) - .Save(output); + image.Vignette(Color.HotPink).Save(output); } } } @@ -49,17 +45,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyVignetteFilterRadius() { - string path = CreateOutputDirectory("Vignette"); + string path = this.CreateOutputDirectory("Vignette"); foreach (TestFile file in Files) { string filename = file.GetFileName("Radius"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Vignette(image.Width / 4, image.Height / 4) - .Save(output); + image.Vignette(image.Width / 4F, image.Height / 4F).Save(output); } } } @@ -67,17 +61,16 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyVignetteFilterInBox() { - string path = CreateOutputDirectory("Vignette"); + string path = this.CreateOutputDirectory("Vignette"); foreach (TestFile file in Files) { string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Vignette(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); + .Save(output); } } } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 96bf5cbb0..42340dc44 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -54,6 +54,11 @@ namespace ImageSharp.Tests /// public byte[] Bytes { get; } + /// + /// The file name. + /// + public string FilePath => this.file; + /// /// The file name. /// @@ -125,6 +130,18 @@ namespace ImageSharp.Tests return new Image(this.image); } + /// + /// Creates a new image. + /// + /// The options for the decoder. + /// + /// The . + /// + public Image CreateImage(IDecoderOptions options) + { + return new Image(this.Bytes, options); + } + /// /// Gets the correct path to the formats directory. /// diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs new file mode 100644 index 000000000..3a5bb2b2c --- /dev/null +++ b/tests/ImageSharp.Tests/TestFont.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + + /// + /// A test image file. + /// + public static class TestFontUtilities + { + /// + /// The formats directory. + /// + private static readonly string FormatsDirectory = GetFontsDirectory(); + + /// + /// Gets the full qualified path to the file. + /// + /// + /// The file path. + /// + /// + /// The . + /// + public static string GetPath(string file) + { + return Path.Combine(FormatsDirectory, file); + } + + /// + /// Gets the correct path to the formats directory. + /// + /// + /// The . + /// + private static string GetFontsDirectory() + { + List directories = new List< string > { + "TestFonts/", // Here for code coverage tests. + "tests/ImageSharp.Tests/TestFonts/", // from travis/build script + "../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 + "../../../../TestFonts/" + }; + + directories = directories.SelectMany(x => new[] + { + Path.GetFullPath(x) + }).ToList(); + + AddFormatsDirectoryFromTestAssebmlyPath(directories); + + var directory = directories.FirstOrDefault(x => Directory.Exists(x)); + + if(directory != null) + { + return directory; + } + + throw new System.Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]"); + } + + /// + /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory + /// in certain scenarios like dotTrace test profiling. + /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location. + /// + /// The directories list + private static void AddFormatsDirectoryFromTestAssebmlyPath(List directories) + { + string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location; + assemblyLocation = Path.GetDirectoryName(assemblyLocation); + + if (assemblyLocation != null) + { + string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/"); + dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation); + directories.Add(dirFromAssemblyLocation); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff b/tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff new file mode 100644 index 000000000..277749dfb Binary files /dev/null and b/tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff differ diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index cd365156a..f0a0e8dd8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -25,15 +25,19 @@ namespace ImageSharp.Tests public const string SplashInterlaced = "Png/splash-interlaced.png"; public const string Interlaced = "Png/interlaced.png"; - // filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html + // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; public const string Filter1 = "Png/filter1.png"; public const string Filter2 = "Png/filter2.png"; public const string Filter3 = "Png/filter3.png"; public const string Filter4 = "Png/filter4.png"; - // filter changing per scanline + // Filter changing per scanline public const string FilterVar = "Png/filterVar.png"; + + // Odd chunk lengths + public const string ChunkLength1 = "Png/chunklength1.png"; + public const string ChunkLength2 = "Png/chunklength2.png"; } public static class Jpeg diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif b/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif index acd5d6339..a714dbfbb 100644 --- a/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif +++ b/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:716448da88152225767c024aac498f5b7562b6e8391907cefc9d03dba40050fd -size 53435 +oid sha256:a7b9b5f5056a8d90134d72b462bf37675ab37aa2551ffeae0072037a0a27ad74 +size 53457 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength1.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength1.png new file mode 100644 index 000000000..c6cf867b2 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b9957a86738acc0b682618a6677c8d659614cd6be5728e85185aef314c21981 +size 149591 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png new file mode 100644 index 000000000..85929cb1e --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b508f05da9f50edd87ed387abc8222620682ef2f59f036fd66ac7cbaf95ffe44 +size 12181 diff --git a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs index c2f0aed84..87b7ace6a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Tests /// Used as parameter for -based factory methods /// public class GenericFactory - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { public virtual Image CreateImage(int width, int height) { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index c14f56588..ad4d2cc98 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Tests using System; public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private class BlankProvider : TestImageProvider { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 1b8c1498e..cd403caed 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Tests using System.Collections.Concurrent; public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private class FileProvider : TestImageProvider { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs index c8bc705f1..9addc8ca6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Tests /// /// The pixel format of the image public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private class LambdaProvider : TestImageProvider { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index f7d3a0bf8..855374f55 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Tests /// /// The pixel format of the image public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private class SolidProvider : BlankProvider { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 911719afa..cdb31ab69 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Tests /// /// The pixel format of the image public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { public PixelTypes PixelType { get; private set; } = typeof(TColor).GetPixelType(); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 8eb658073..38429b278 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -75,8 +75,9 @@ namespace ImageSharp.Tests /// The image instance /// The requested extension /// Optional encoder - public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null) - where TColor : struct, IPackedPixel, IEquatable + /// Optional encoder options + public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null, IEncoderOptions options = null) + where TColor : struct, IPixel { string path = this.GetTestOutputFileName(extension); @@ -86,7 +87,7 @@ namespace ImageSharp.Tests using (var stream = File.OpenWrite(path)) { - image.Save(stream, encoder); + image.Save(stream, encoder, options); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs index d2726c16a..ca3340238 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs @@ -45,19 +45,10 @@ namespace ImageSharp.Tests PixelTypes2ClrTypes[PixelTypes.StandardImageClass] = typeof(Color); } - public static Type GetPackedType(Type pixelType) - { - var intrfcType = - pixelType.GetInterfaces() - .Single(i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == typeof(IPackedPixel<>)); - - return intrfcType.GetGenericArguments().Single(); - } - public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (a.Width != b.Width || a.Height != b.Height) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 2f5ec6c28..09c81b761 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Tests [Theory] [WithBlankImages(42, 666, PixelTypes.Color | PixelTypes.Argb | PixelTypes.HalfSingle, "hello")] public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); @@ -36,7 +36,7 @@ namespace ImageSharp.Tests public void Use_WithBlankImagesAttribute_WithAllPixelTypes( TestImageProvider provider, string message) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); @@ -50,7 +50,7 @@ namespace ImageSharp.Tests [WithBlankImages(1, 1, PixelTypes.Alpha8, PixelTypes.Alpha8)] [WithBlankImages(1, 1, PixelTypes.StandardImageClass, PixelTypes.StandardImageClass)] public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Assert.Equal(expected, provider.PixelType); } @@ -60,7 +60,7 @@ namespace ImageSharp.Tests [WithFile(TestImages.Bmp.F, PixelTypes.StandardImageClass)] public void PixelTypes_ColorWithDefaultImageClass_TriggersCreatingTheNonGenericDerivedImageClass( TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); @@ -71,7 +71,7 @@ namespace ImageSharp.Tests [WithFile(TestImages.Bmp.Car, PixelTypes.All, 88)] [WithFile(TestImages.Bmp.F, PixelTypes.All, 88)] public void Use_WithFileAttribute(TestImageProvider provider, int yo) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); var img = provider.GetImage(); @@ -88,7 +88,7 @@ namespace ImageSharp.Tests [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.Argb)] public void Use_WithFileCollection(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); var image = provider.GetImage(); @@ -98,7 +98,7 @@ namespace ImageSharp.Tests [Theory] [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Color | PixelTypes.Argb)] public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); Assert.Equal(img.Width, 10); @@ -130,7 +130,7 @@ namespace ImageSharp.Tests /// /// public static Image CreateTestImage(GenericFactory factory) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return factory.CreateImage(3, 3); } @@ -138,7 +138,7 @@ namespace ImageSharp.Tests [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] public void Use_WithMemberFactoryAttribute(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); Assert.Equal(img.Width, 3); @@ -160,7 +160,7 @@ namespace ImageSharp.Tests [Theory] [MemberData(nameof(BasicData))] public void Blank_MemberData(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); @@ -178,7 +178,7 @@ namespace ImageSharp.Tests [Theory] [MemberData(nameof(FileData))] public void File_MemberData(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 23a42c1a6..1db209dcd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -24,7 +24,7 @@ namespace ImageSharp.Tests private ITestOutputHelper Output { get; } public static Image CreateTestImage(GenericFactory factory) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image image = factory.CreateImage(10, 10); @@ -57,20 +57,12 @@ namespace ImageSharp.Tests var fake = typeof(Color).GetTypeInfo().Assembly.GetType("ImageSharp.dsaada_DASqewrr"); Assert.Null(fake); } - - [Fact] - public void GetPackedType() - { - Type shouldBeUIint32 = TestUtilityExtensions.GetPackedType(typeof(Color)); - - Assert.Equal(shouldBeUIint32, typeof(uint)); - } - + [Theory] [WithFile(TestImages.Bmp.Car, PixelTypes.Color, true)] [WithFile(TestImages.Bmp.Car, PixelTypes.Color, false)] public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var a = provider.GetImage(); var b = provider.GetImage(); @@ -83,7 +75,7 @@ namespace ImageSharp.Tests [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.Bgr565, true)] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.Bgr565, false)] public void IsEquivalentTo_WhenTrue(TestImageProvider provider, bool compareAlpha) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var a = provider.GetImage(); var b = provider.GetImage(); diff --git a/tests/ImageSharp.Tests/project.json b/tests/ImageSharp.Tests/project.json new file mode 100644 index 000000000..7c67a5c70 --- /dev/null +++ b/tests/ImageSharp.Tests/project.json @@ -0,0 +1,71 @@ +{ + "version": "1.0.0-*", + "description": "ImageSharp.Tests Class Library", + "authors": [ "James Jackson-South and contributors" ], + "packOptions": { + "projectUrl": "https://github.com/JimBobSquarePants/ImageSharp", + "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", + "tags": [ + "Image Resize Crop Gif Jpg Jpeg Bitmap Png Core" + ] + }, + "buildOptions": { + "allowUnsafe": true + }, + "configurations": { + "Release": { + "buildOptions": { + "warningsAsErrors": true + } + } + }, + "dependencies": { + "ImageSharp": { + "target": "project" + }, + "xunit": "2.2.0-*", + "dotnet-test-xunit": "2.2.0-*", + "ImageSharp.Drawing": { + "target": "project" + }, + "ImageSharp.Drawing.Paths": { + "target": "project" + }, + "ImageSharp.Drawing.Text": { + "target": "project" + }, + "ImageSharp.Formats.Png": { + "target": "project" + }, + "ImageSharp.Formats.Jpeg": { + "target": "project" + }, + "ImageSharp.Formats.Bmp": { + "target": "project" + }, + "ImageSharp.Formats.Gif": { + "target": "project" + }, + "ImageSharp.Processing": { + "target": "project" + }, + //alpha supports netstandard + "Moq": "4.6.38-alpha" + }, + "frameworks": { + "netcoreapp1.1": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-*" + }, + "Microsoft.CodeCoverage": "1.0.2" + } + }, + "net451": { + "dependencies": { + } + } + }, + "testRunner": "xunit" +} \ No newline at end of file