├── .editorconfig ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── funding.yml └── stale.yml ├── .gitignore ├── ImageProcessor.ruleset ├── ImageProcessor.sln ├── LICENSE ├── README.md ├── src ├── ImageProcessor.Plugins.WebP │ ├── Formats │ │ ├── NativeMethods.cs │ │ └── WebPFormat.cs │ ├── ImageProcessor.Plugins.WebP.csproj │ ├── README.md │ └── Resources │ │ └── Unmanaged │ │ ├── README.txt │ │ ├── x64 │ │ └── libwebp.dll │ │ └── x86 │ │ └── libwebp.dll └── ImageProcessor │ ├── Bgra32.cs │ ├── Common │ ├── Exceptions │ │ ├── ImageFormatException.cs │ │ ├── ImageProcessingException.cs │ │ ├── Logging │ │ │ ├── DefaultLogger.cs │ │ │ └── ILogger.cs │ │ └── QuantizationException.cs │ ├── Extensions │ │ ├── AssemblyExtensions.cs │ │ ├── DoubleExtensions.cs │ │ ├── ImageExtensions.cs │ │ ├── IntegerExtensions.cs │ │ └── StreamExtensions.cs │ └── Helpers │ │ ├── BigEndianBitConverter.cs │ │ ├── ComputerArchitectureInfo.cs │ │ ├── EndianBitConverter.cs │ │ ├── Endianness.cs │ │ ├── EnumerableUtilities.cs │ │ ├── FormatUtilities.cs │ │ ├── GeometryUtilities.cs │ │ ├── IComputerArchitectureInfo.cs │ │ ├── LittleEndianBitConverter.cs │ │ └── NumberUtilities.cs │ ├── Configuration │ ├── ImageProcessorBootstrapper.cs │ ├── NativeBinaryFactory.cs │ └── NativeMethods.cs │ ├── FastBitmap.cs │ ├── Formats │ ├── BitDepth.cs │ ├── BitmapFormat.cs │ ├── FormatBase.cs │ ├── GifDecoder.cs │ ├── GifEncoder.cs │ ├── GifFormat.cs │ ├── GifFrame.cs │ ├── IImageFormat.cs │ ├── JpegFormat.cs │ ├── PngFormat.cs │ └── TiffFormat.cs │ ├── FrameProcessingMode.cs │ ├── ImageFactory.Processing.cs │ ├── ImageFactory.cs │ ├── ImageProcessor.csproj │ ├── Metadata │ ├── ExifBitConverter.cs │ ├── ExifPropertyTag.cs │ ├── ExifPropertyTagConstants.cs │ ├── ExifPropertyTagType.cs │ ├── ImageFactoryMetaExtensions.cs │ ├── Int32Converter.cs │ ├── PropertyTagResolutionUnit.cs │ └── Rational.cs │ ├── MetadataMode.cs │ ├── Processing │ ├── Adjustments.cs │ ├── Alpha.cs │ ├── AutoRotate.cs │ ├── BackgroundColor.cs │ ├── Brightness.cs │ ├── ColorMatrixProcessor.cs │ ├── ColorMatrixRangedProcessor.cs │ ├── Contrast.cs │ ├── Convolution │ │ ├── Convolution2DProcessor.cs │ │ ├── ConvolutionProcessor.cs │ │ ├── EdgeDetection2DProcessor.cs │ │ ├── EdgeDetectionOperators.cs │ │ ├── EdgeDetectionProcessor.cs │ │ ├── Kayyali.cs │ │ ├── Laplacian3x3.cs │ │ ├── Laplacian5x5.cs │ │ ├── LaplacianOfGaussian.cs │ │ ├── Prewitt.cs │ │ ├── RobertsCross.cs │ │ ├── Scharr.cs │ │ └── Sobel.cs │ ├── Crop.cs │ ├── Grayscale.cs │ ├── Hue.cs │ ├── IGraphicsProcessor.cs │ ├── KnownColorMatrices.cs │ ├── Pixelate.cs │ ├── Resize.cs │ ├── ResizeHelper.cs │ └── Saturation.cs │ └── Quantizers │ ├── IQuantizer.cs │ ├── OctreeQuantizer.cs │ ├── Quantizer.cs │ └── WuQuantizer │ ├── Box.cs │ ├── ColorMoment.cs │ ├── CubeCut.cs │ ├── Histogram.cs │ ├── IWuQuantizer.cs │ ├── ImageBuffer.cs │ ├── PaletteColorHistory.cs │ ├── PaletteLookup.cs │ ├── WuQuantizer.cs │ └── WuQuantizerBase.cs ├── stylecop.json └── tests ├── ImageProcessor.Tests ├── FastBitmapTests.cs ├── ImageDeepCopyTests.cs ├── ImageExtensionTests.cs ├── ImageFactoryEncodingTests.cs ├── ImageFactoryExtensions.cs ├── ImageProcessor.Tests.csproj ├── ImagesSimilarityException.cs ├── Processing │ ├── AlphaTests.cs │ ├── AutoRotateTests.cs │ ├── BackgroundColorTests.cs │ ├── BrightnessTests.cs │ ├── ContrastTests.cs │ ├── CropTests.cs │ ├── DetectEdgesTests.cs │ ├── HueTests.cs │ ├── PixelateTests.cs │ ├── ResizeTests.cs │ └── SaturationTests.cs ├── TestFiles.cs ├── TestUtils.cs └── xunit.runner.json └── Images └── Input ├── 4.sm.webp ├── Teeth.png ├── animated-bird.gif ├── animated-meter.gif ├── animated-pattern.gif ├── animated-startrek.gif ├── animated-zivan.gif ├── autorotate-landscape-2.jpg ├── b.jpg ├── color-tests ├── hi-color.png ├── hi-contrast.jpg └── hi-saturation.jpg ├── exif-crop-issue-559.jfif ├── exif ├── autorotate.jpg ├── exif-Tulips.jpg └── exif-rocks.jpg ├── format-Penguins-8bit.png ├── format-Penguins.bmp ├── format-Penguins.gif ├── format-Penguins.jpg ├── format-Penguins.png ├── format-Penguins.tif ├── format-Penguins.webp ├── gamma ├── gamma-1.0-or-2.2.png ├── gamma-dalai-lama-gray-tft.jpg ├── gamma-dalai-lama-gray.jpg ├── gamma-fly.jpg └── gamma-saturn.jpg ├── icc-profiles ├── cmyk-profile-euroscale.jpg ├── profile-adobe-rgb.jpg └── profile-srgb.jpg ├── imageprocessor ├── mask │ └── mask.png └── overlay │ ├── monster.png │ └── monster24bit.png ├── stretched.jpg ├── text.png └── trans.gif /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to: 3 | # treat as text and 4 | # normalize to Unix-style line endings 5 | ############################################################################### 6 | * text eol=lf 7 | 8 | ############################################################################### 9 | # Set explicit file behavior to: 10 | # treat as text and 11 | # normalize to Unix-style line endings 12 | ############################################################################### 13 | *.asm text eol=lf 14 | *.c text eol=lf 15 | *.clj text eol=lf 16 | *.cmd text eol=lf 17 | *.cpp text eol=lf 18 | *.css text eol=lf 19 | *.cxx text eol=lf 20 | *.config text eol=lf 21 | *.DotSettings text eol=lf 22 | *.erl text eol=lf 23 | *.fs text eol=lf 24 | *.fsx text eol=lf 25 | *.h text eol=lf 26 | *.htm text eol=lf 27 | *.html text eol=lf 28 | *.hs text eol=lf 29 | *.hxx text eol=lf 30 | *.java text eol=lf 31 | *.js text eol=lf 32 | *.json text eol=lf 33 | *.less text eol=lf 34 | *.lisp text eol=lf 35 | *.lua text eol=lf 36 | *.m text eol=lf 37 | *.md text eol=lf 38 | *.php text eol=lf 39 | *.props text eol=lf 40 | *.ps1 text eol=lf 41 | *.py text eol=lf 42 | *.rb text eol=lf 43 | *.resx text eol=lf 44 | *.runsettings text eol=lf 45 | *.ruleset text eol=lf 46 | *.sass text eol=lf 47 | *.scss text eol=lf 48 | *.sh text eol=lf 49 | *.sql text eol=lf 50 | *.svg text eol=lf 51 | *.targets text eol=lf 52 | *.tt text eol=crlf 53 | *.ttinclude text eol=crlf 54 | *.txt text eol=lf 55 | *.vb text eol=lf 56 | *.yml text eol=lf 57 | 58 | ############################################################################### 59 | # Set explicit file behavior to: 60 | # treat as text 61 | # normalize to Unix-style line endings and 62 | # diff as csharp 63 | ############################################################################### 64 | *.cs text eol=lf diff=csharp 65 | 66 | ############################################################################### 67 | # Set explicit file behavior to: 68 | # treat as text 69 | # normalize to Unix-style line endings and 70 | # use a union merge when resoling conflicts 71 | ############################################################################### 72 | *.csproj text eol=lf merge=union 73 | *.dbproj text eol=lf merge=union 74 | *.fsproj text eol=lf merge=union 75 | *.ncrunchproject text eol=lf merge=union 76 | *.vbproj text eol=lf merge=union 77 | 78 | ############################################################################### 79 | # Set explicit file behavior to: 80 | # treat as text 81 | # normalize to Windows-style line endings and 82 | # use a union merge when resoling conflicts 83 | ############################################################################### 84 | *.sln text eol=crlf merge=union 85 | 86 | ############################################################################### 87 | # Set explicit file behavior to: 88 | # treat as binary 89 | ############################################################################### 90 | *.bmp binary 91 | *.dll binary 92 | *.exe binary 93 | *.gif binary 94 | *.jpg binary 95 | *.jpeg binary 96 | *.png binary 97 | *.ttf binary 98 | *.tif binary 99 | *.tiff binary 100 | *.snk binary 101 | 102 | ############################################################################### 103 | # Set explicit file behavior to: 104 | # diff as plain text 105 | ############################################################################### 106 | *.doc diff=astextplain 107 | *.docx diff=astextplain 108 | *.dot diff=astextplain 109 | *.pdf diff=astextplain 110 | *.pptx diff=astextplain 111 | *.rtf diff=astextplain 112 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to ImageProcessor 2 | 3 | #### **Did you find a bug?** 4 | 5 | - Please **ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/JimBobSquarePants/ImageProcessor/issues). 6 | 7 | - If you're unable to find an open issue addressing the problem, please [open a new one](https://github.com/JimBobSquarePants/ImageProcessor/issues/new). Be sure to include a **title, the applicable version, a clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. Please do not hijack existing issues. 8 | 9 | #### **Did you write a patch that fixes a bug?** 10 | 11 | * Open a new GitHub pull request with the patch. 12 | 13 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 14 | 15 | * Before submitting, please ensure that your code matches the existing coding patterns and practise as demonstrated in the repository. These follow strict Stylecop rules :cop:. 16 | 17 | #### **Do you intend to add a new feature or change an existing one?** 18 | 19 | * Suggest your change in the [ImageProcessor Gitter Chat Room](https://gitter.im/JimBobSquarePants/ImageProcessor) and start writing code. 20 | 21 | * Do not open an issue on GitHub until you have collected positive feedback about the change. GitHub issues are primarily intended for bug reports and fixes. 22 | 23 | #### **Do you have questions about consuming the library or the source code?** 24 | 25 | * Ask any question about how to use ImageSharp in the [ImageProcessor Gitter Chat Room](https://gitter.im/JimBobSquarePants/ImageProcessor). 26 | 27 | And please remember. ImageProcessor is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible imageprocessing available to all. Open Source can only exist with your help. 28 | 29 | Thanks for reading! 30 | 31 | James Jackson-South :heart: 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | - [ ] I have written a descriptive issue title 4 | - [ ] I have verified that I am running the latest version of ImageProcessor 5 | - [ ] I have verified if the problem exist in both `DEBUG` and `RELEASE` mode 6 | - [ ] I have searched [open](https://github.com/JimBobSquarePants/ImageProcessor/issues) and [closed](https://github.com/JimBobSquarePants/ImageProcessor/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported 7 | 8 | ### Description 9 | 10 | 11 | ### Steps to Reproduce 12 | 13 | 14 | ### System Configuration 15 | 16 | 17 | - ImageProcessor version: 18 | - ImageProcessor.Web version (if applicable) 19 | - Other ImageProcessor packages and versions: 20 | - Environment (Operating system, version and so on): 21 | - .NET Framework version: 22 | - Additional information: 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | - [ ] I have written a descriptive pull-request title 4 | - [ ] I have verified that there are no overlapping [pull-requests](https://github.com/JimBobSquarePants/ImageProcessor/pulls) open 5 | - [ ] I have verified that I am following matches the existing coding patterns and practise as demonstrated in the repository. These follow strict Stylecop rules :cop:. 6 | - [ ] I have provided test coverage for my change (where applicable) 7 | 8 | ### Description 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: JimBobSquarePants 4 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - bug 10 | # Label to use when marking an issue as stale 11 | staleLabel: no activity 12 | # Comment to post when marking an issue as stale. Set to `false` to disable 13 | markComment: > 14 | This issue has been automatically marked as stale because it has not had 15 | recent activity. It will be closed if no further activity occurs. Thank you 16 | for your contributions. 17 | # Comment to post when closing a stale issue. Set to `false` to disable 18 | closeComment: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | src/**/build/ 21 | tests/**/build/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | 26 | # Visual Studo 2015 cache/options directory 27 | .vs/ 28 | 29 | # Jetbrains Rider cache/options directory 30 | .idea/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # ASP.NET 5 46 | project.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding addin-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | 154 | # Windows Azure Build Output 155 | csx/ 156 | *.build.csdef 157 | 158 | # Windows Store app package directory 159 | AppPackages/ 160 | 161 | # Others 162 | *.[Cc]ache 163 | ClientBin/ 164 | ~$* 165 | *~ 166 | *.dbmdl 167 | *.dbproj.schemaview 168 | *.pfx 169 | *.publishsettings 170 | node_modules/ 171 | bower_components/ 172 | 173 | # RIA/Silverlight projects 174 | Generated_Code/ 175 | 176 | # Backup & report files from converting an old project file 177 | # to a newer Visual Studio version. Backup files are not needed, 178 | # because we have git ;-) 179 | _UpgradeReport_Files/ 180 | Backup*/ 181 | UpgradeLog*.XML 182 | UpgradeLog*.htm 183 | 184 | # SQL Server files 185 | *.mdf 186 | *.ldf 187 | 188 | # Business Intelligence projects 189 | *.rdl.data 190 | *.bim.layout 191 | *.bim_*.settings 192 | 193 | # Microsoft Fakes 194 | FakesAssemblies/ 195 | 196 | # Node.js Tools for Visual Studio 197 | .ntvs_analysis.dat 198 | 199 | # Visual Studio 6 build log 200 | *.plg 201 | 202 | # Visual Studio 6 workspace options file 203 | *.opt 204 | 205 | **/node_modules 206 | **/node_modules/* 207 | 208 | # ASP.NET 5 209 | project.lock.json 210 | artifacts/ 211 | 212 | #BenchmarkDotNet 213 | **/BenchmarkDotNet.Artifacts/ 214 | 215 | # Build process 216 | *.csproj.bak 217 | 218 | # Tests TODO: Remove Expected when tests are complete 219 | **/Images/Actual 220 | **/Images/Expected 221 | 222 | !**/Resources/Unmanaged/x64/ 223 | !**/Resources/Unmanaged/x86/ 224 | -------------------------------------------------------------------------------- /ImageProcessor.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ImageProcessor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29025.244 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageProcessor", "src\ImageProcessor\ImageProcessor.csproj", "{37B5AB0B-F2D3-418B-BC2B-956AB934CC35}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E12DAFB7-1E0E-4B9F-A997-22F0896ADD0F}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F7EF30FE-9BFD-4EA1-838C-F3D505AB3CD8}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageProcessor.Tests", "tests\ImageProcessor.Tests\ImageProcessor.Tests.csproj", "{47C75531-C0F3-469B-88CF-4F0B00BDD841}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageProcessor.Plugins.WebP", "src\ImageProcessor.Plugins.WebP\ImageProcessor.Plugins.WebP.csproj", "{D8285938-B99E-4B77-BE60-9FBF8A4BD173}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0170E9EB-0F33-45E0-AF6A-7D964F25E73F}" 17 | ProjectSection(SolutionItems) = preProject 18 | .editorconfig = .editorconfig 19 | .gitattributes = .gitattributes 20 | .gitignore = .gitignore 21 | ImageProcessor.ruleset = ImageProcessor.ruleset 22 | README.md = README.md 23 | stylecop.json = stylecop.json 24 | EndProjectSection 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {37B5AB0B-F2D3-418B-BC2B-956AB934CC35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {37B5AB0B-F2D3-418B-BC2B-956AB934CC35}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {37B5AB0B-F2D3-418B-BC2B-956AB934CC35}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {37B5AB0B-F2D3-418B-BC2B-956AB934CC35}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {47C75531-C0F3-469B-88CF-4F0B00BDD841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {47C75531-C0F3-469B-88CF-4F0B00BDD841}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {47C75531-C0F3-469B-88CF-4F0B00BDD841}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {47C75531-C0F3-469B-88CF-4F0B00BDD841}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {D8285938-B99E-4B77-BE60-9FBF8A4BD173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {D8285938-B99E-4B77-BE60-9FBF8A4BD173}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {D8285938-B99E-4B77-BE60-9FBF8A4BD173}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {D8285938-B99E-4B77-BE60-9FBF8A4BD173}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(NestedProjects) = preSolution 49 | {37B5AB0B-F2D3-418B-BC2B-956AB934CC35} = {E12DAFB7-1E0E-4B9F-A997-22F0896ADD0F} 50 | {47C75531-C0F3-469B-88CF-4F0B00BDD841} = {F7EF30FE-9BFD-4EA1-838C-F3D505AB3CD8} 51 | {D8285938-B99E-4B77-BE60-9FBF8A4BD173} = {E12DAFB7-1E0E-4B9F-A997-22F0896ADD0F} 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {5D838DCC-759C-4EB1-9DD5-FF87A299A6D5} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | ImageProcessor 3 |
4 | ImageProcessor 5 |
6 |
7 | Build status 8 | Issues open 9 | Source Browser 10 | 11 |

12 | 13 | ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️ 14 | 15 | **ImageProcessor is, and will only ever be supported on the .NET Framework running on a Windows OS. Please do not attempt to use with .NET Core or NET 5+** 16 | 17 | ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️ 18 | 19 | **Imageprocessor** is a lightweight, fluent wrapper around System.Drawing. 20 | 21 | It's fast, extensible, easy to use, comes bundled with some great features and is fully open source. 22 | 23 | For full documentation please see [https://jimbobsquarepants.github.io/ImageProcessor/](https://jimbobsquarepants.github.io/ImageProcessor/) 24 | 25 | ## Roadmap 26 | Focus for the ImageProcessor libraries has switched to desktop only due to the [lack of support for System.Drawing on Windows Services and ASP.NET](https://docs.microsoft.com/en-us/dotnet/api/system.drawing?view=netframework-4.8#remarks). As such, the `ImageProcessor.Web`and accompanying libraries will not be further developed. For an alternative please use [`ImageSharp.Web`](https://github.com/SixLabors/ImageSharp.Web). 27 | 28 | ImageProcessor has been retired. For modern platforms use [`ImageSharp`](https://github.com/SixLabors/ImageSharp) 29 | 30 | ### Latest Releases 31 | | Library | Version | 32 | | :-------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | 33 | | **ImageProcessor** | [![NuGet](https://buildstats.info/nuget/ImageProcessor)](https://www.nuget.org/packages/ImageProcessor) | 34 | | **ImageProcessor.Plugins.WebP** | [![NuGet](https://buildstats.info/nuget/ImageProcessor.Plugins.WebP)](https://www.nuget.org/packages/ImageProcessor.Plugins.WebP) | 35 | 36 | 37 | ## Documentation 38 | 39 | ImageProcessor's documentation, included in this repo in the gh-pages branch, is built with [Jekyll](http://jekyllrb.com) and publicly hosted on GitHub Pages at For full documentation please see [https://jimbobsquarepants.github.io/ImageProcessor/](https://jimbobsquarepants.github.io/ImageProcessor/). The docs may also be run locally. 40 | 41 | ### Running documentation locally 42 | 1. If necessary, [install Jekyll](http://jekyllrb.com/docs/installation) (requires v2.5.3x). 43 | - **Windows users:** Read [this unofficial guide](https://github.com/juthilo/run-jekyll-on-windows/) to get Jekyll up and running without problems. 44 | 2. From the root `/ImageProcessor` directory, run `jekyll serve` in the command line. 45 | 3. Open in your browser to navigate to your site. 46 | Learn more about using Jekyll by reading its [documentation](http://jekyllrb.com/docs/home/). 47 | 48 | ### The ImageProcessor Team 49 | 50 | Grand High Eternal Dictator 51 | - [James Jackson-South](https://github.com/jimbobsquarepants) 52 | -------------------------------------------------------------------------------- /src/ImageProcessor.Plugins.WebP/ImageProcessor.Plugins.WebP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net452 5 | 7.3 6 | 7 | 8 | 9 | bin\Release\net452\ImageProcessor.Plugins.WebP.xml 10 | true 11 | 12 | 13 | 14 | bin\Debug\net452\ImageProcessor.Plugins.WebP.xml 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | 34 | 35 | ..\..\ImageProcessor.ruleset 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/ImageProcessor.Plugins.WebP/README.md: -------------------------------------------------------------------------------- 1 | # Build instructions for libwebp.dll 2 | 3 | Download libwebp source via git or through http download. 4 | 5 | In Start Menu, run Visual Studio Tools > Command Prompt. 6 | 7 | Change to the libwebp directory and run 8 | 9 | nmake /f Makefile.vc CFG=release-dynamic RTLIBCFG=dynamic OBJDIR=output 10 | 11 | Repeat with the 12 | Visual Studio x64 Cross Tools Command Prompt 13 | 14 | 15 | Copy to x86 and x64 directories from /output/ 16 | 17 | To verify p/invokes have not changed: 18 | 19 | Review the following history logs for changes since the last release: 20 | 21 | http://git.chromium.org/gitweb/?p=webm/libwebp.git;a=history;f=src/webp/types.h;hb=HEAD 22 | http://git.chromium.org/gitweb/?p=webm/libwebp.git;a=history;f=src/webp/encode.h;hb=HEAD 23 | http://git.chromium.org/gitweb/?p=webm/libwebp.git;a=history;f=src/webp/decode.h;hb=HEAD 24 | -------------------------------------------------------------------------------- /src/ImageProcessor.Plugins.WebP/Resources/Unmanaged/README.txt: -------------------------------------------------------------------------------- 1 | Build instructions for libwebp.dll 2 | ================================== 3 | 4 | Current version : 1.0.3 5 | 6 | Download libwebp-{version}.tar.gz from the downloads list at http://downloads.webmproject.org/releases/webp 7 | and extract its contents. 8 | 9 | In Start Menu, run Visual Studio Tools>Command Prompt. 10 | C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts\Developer Command Prompt for VS2013 11 | 12 | Change to the libwebp-{version} directory, run: 13 | 14 | nmake /f Makefile.vc CFG=release-dynamic RTLIBCFG=dynamic OBJDIR=output 15 | 16 | Repeat with the x64 Cross Tools Command Prompt. 17 | C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts\VS2013 x64 Cross Tools Command Prompt 18 | 19 | Copy to x86 and x64 directories from /output/bin/ 20 | 21 | To verify p/invokes have not changed: 22 | 23 | Review the following history logs for changes since the last release: 24 | 25 | http://git.chromium.org/gitweb/?p=webm/libwebp.git;a=history;f=src/webp/types.h;hb=HEAD 26 | http://git.chromium.org/gitweb/?p=webm/libwebp.git;a=history;f=src/webp/encode.h;hb=HEAD 27 | http://git.chromium.org/gitweb/?p=webm/libwebp.git;a=history;f=src/webp/decode.h;hb=HEAD 28 | -------------------------------------------------------------------------------- /src/ImageProcessor.Plugins.WebP/Resources/Unmanaged/x64/libwebp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/src/ImageProcessor.Plugins.WebP/Resources/Unmanaged/x64/libwebp.dll -------------------------------------------------------------------------------- /src/ImageProcessor.Plugins.WebP/Resources/Unmanaged/x86/libwebp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/src/ImageProcessor.Plugins.WebP/Resources/Unmanaged/x86/libwebp.dll -------------------------------------------------------------------------------- /src/ImageProcessor/Bgra32.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace ImageProcessor 9 | { 10 | /// 11 | /// Structure that defines a 32 bits per pixel Bgra color. Used for pixel manipulation not for color conversion. 12 | /// 13 | [StructLayout(LayoutKind.Explicit)] 14 | public struct Bgra32 : IEquatable 15 | { 16 | /// 17 | /// Holds the blue component of the color. 18 | /// 19 | [FieldOffset(0)] 20 | public byte B; 21 | 22 | /// 23 | /// Holds the green component of the color. 24 | /// 25 | [FieldOffset(1)] 26 | public byte G; 27 | 28 | /// 29 | /// Holds the red component of the color. 30 | /// 31 | [FieldOffset(2)] 32 | public byte R; 33 | 34 | /// 35 | /// Holds the alpha component of the color. 36 | /// 37 | [FieldOffset(3)] 38 | public byte A; 39 | 40 | /// 41 | /// Permits the color32 to be treated as a 32 bit integer. 42 | /// 43 | [FieldOffset(0)] 44 | public int Argb; 45 | 46 | /// 47 | /// Initializes a new instance of the struct. 48 | /// 49 | /// The alpha component. 50 | /// The red component. 51 | /// The green component. 52 | /// The blue component. 53 | public Bgra32(byte alpha, byte red, byte green, byte blue) 54 | : this() 55 | { 56 | this.A = alpha; 57 | this.R = red; 58 | this.G = green; 59 | this.B = blue; 60 | } 61 | 62 | /// 63 | /// Initializes a new instance of the struct. 64 | /// 65 | /// The combined color components. 66 | public Bgra32(int argb) 67 | : this() => this.Argb = argb; 68 | 69 | /// 70 | /// Gets the color for this Color32 object. 71 | /// 72 | public Color Color => Color.FromArgb(this.A, this.R, this.G, this.B); 73 | 74 | /// 75 | /// Indicates whether this instance and a specified are equal. 76 | /// 77 | /// The instance on the left hand of the operator. 78 | /// The instance on the right hand of the operator. 79 | /// The . 80 | public static bool operator ==(Bgra32 left, Bgra32 right) => left.Equals(right); 81 | 82 | /// 83 | /// Indicates whether this instance and a specified are not equal. 84 | /// 85 | /// The instance on the left hand of the operator. 86 | /// The instance on the right hand of the operator. 87 | /// The . 88 | public static bool operator !=(Bgra32 left, Bgra32 right) => !(left == right); 89 | 90 | /// 91 | public override bool Equals(object obj) => obj is Bgra32 color && this.Equals(color); 92 | 93 | /// 94 | public bool Equals(Bgra32 other) => this.Argb.Equals(other.Argb); 95 | 96 | /// 97 | public override int GetHashCode() => this.GetHashCode(this); 98 | 99 | private int GetHashCode(Bgra32 color) 100 | { 101 | unchecked 102 | { 103 | int hashCode = color.B.GetHashCode(); 104 | hashCode = (hashCode * 397) ^ color.G.GetHashCode(); 105 | hashCode = (hashCode * 397) ^ color.R.GetHashCode(); 106 | return (hashCode * 397) ^ color.A.GetHashCode(); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Exceptions/ImageFormatException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.Serialization; 6 | 7 | namespace ImageProcessor 8 | { 9 | /// 10 | /// The exception that is thrown when loading the supported image format types has failed. 11 | /// 12 | [Serializable] 13 | public sealed class ImageFormatException : Exception 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The message that describes the error. 19 | public ImageFormatException(string message) 20 | : base(message) 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The error message that explains the reason for the exception. 28 | /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. 29 | public ImageFormatException(string message, Exception innerException) 30 | : base(message, innerException) 31 | { 32 | } 33 | 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// 37 | public ImageFormatException() 38 | { 39 | } 40 | 41 | /// 42 | /// Initializes a new instance of the class with serialized data. 43 | /// 44 | /// The that holds the serialized object data about the exception being thrown. 45 | /// The that contains contextual information about the source or destination. 46 | /// The parameter is null. 47 | /// The class name is null or is zero (0). 48 | private ImageFormatException(SerializationInfo info, StreamingContext context) 49 | : base(info, context) 50 | { 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Exceptions/ImageProcessingException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.Serialization; 6 | 7 | namespace ImageProcessor 8 | { 9 | /// 10 | /// The exception that is thrown when processing an image has failed. 11 | /// 12 | [Serializable] 13 | public sealed class ImageProcessingException : Exception 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The message that describes the error. 19 | public ImageProcessingException(string message) 20 | : base(message) 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The error message that explains the reason for the exception. 28 | /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. 29 | public ImageProcessingException(string message, Exception innerException) 30 | : base(message, innerException) 31 | { 32 | } 33 | 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// 37 | public ImageProcessingException() 38 | { 39 | } 40 | 41 | /// 42 | /// Initializes a new instance of the class with serialized data. 43 | /// 44 | /// The that holds the serialized object data about the exception being thrown. 45 | /// The that contains contextual information about the source or destination. 46 | /// The parameter is null. 47 | /// The class name is null or is zero (0). 48 | private ImageProcessingException(SerializationInfo info, StreamingContext context) 49 | : base(info, context) 50 | { 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Exceptions/Logging/DefaultLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace ImageProcessor 9 | { 10 | /// 11 | /// The default logger which logs messages to the trace listeners. 12 | /// 13 | /// 14 | public class DefaultLogger : ILogger 15 | { 16 | /// 17 | /// Logs the specified message. 18 | /// 19 | /// The type calling the logger. 20 | /// The message to log. 21 | /// The property or method name calling the log. 22 | /// The line number where the method is called. 23 | public void Log(string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) => this.LogInternal(typeof(T), text, callerName, lineNumber); 24 | 25 | /// 26 | /// Logs the specified message. 27 | /// 28 | /// The type calling the logger. 29 | /// The message to log. 30 | /// The property or method name calling the log. 31 | /// The line number where the method is called. 32 | public void Log(Type type, string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) => this.LogInternal(type, text, callerName, lineNumber); 33 | 34 | /// 35 | /// Logs the specified message. 36 | /// 37 | /// The type calling the logger. 38 | /// The message to log. 39 | /// The property or method name calling the log. 40 | /// The line number where the method is called. 41 | [Conditional("TRACE")] 42 | private void LogInternal(Type type, string text, string callerName = null, int lineNumber = 0) 43 | { 44 | string message = string.Format("{0} - {1}: {2} {3}:{4}", DateTime.UtcNow.ToString("s"), type.Name, callerName, lineNumber, text); 45 | 46 | Trace.WriteLine(message); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Exceptions/Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace ImageProcessor 8 | { 9 | /// 10 | /// Encapsulates properties and methods for logging messages. 11 | /// 12 | public interface ILogger 13 | { 14 | /// 15 | /// Logs the specified message. 16 | /// 17 | /// The type calling the logger. 18 | /// The message to log. 19 | /// The property or method name calling the log. 20 | /// The line number where the method is called. 21 | void Log(string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0); 22 | 23 | /// 24 | /// Logs the specified message. 25 | /// 26 | /// The type calling the logger. 27 | /// The message to log. 28 | /// The property or method name calling the log. 29 | /// The line number where the method is called. 30 | void Log(Type type, string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0); 31 | } 32 | } -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Exceptions/QuantizationException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.Serialization; 6 | 7 | namespace ImageProcessor 8 | { 9 | /// 10 | /// The exception that is thrown when quantizing an image has failed. 11 | /// 12 | [Serializable] 13 | public class QuantizationException : Exception 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// 19 | /// The message. 20 | /// 21 | public QuantizationException(string message) 22 | : base(message) 23 | { 24 | } 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The error message that explains the reason for the exception. 30 | /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. 31 | public QuantizationException(string message, Exception innerException) 32 | : base(message, innerException) 33 | { 34 | } 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | public QuantizationException() 40 | { 41 | } 42 | 43 | /// 44 | /// Initializes a new instance of the class with serialized data. 45 | /// 46 | /// The that holds the serialized object data about the exception being thrown. 47 | /// The that contains contextual information about the source or destination. 48 | /// The parameter is null. 49 | /// The class name is null or is zero (0). 50 | private QuantizationException(SerializationInfo info, StreamingContext context) 51 | : base(info, context) 52 | { 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Extensions/AssemblyExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | 11 | namespace ImageProcessor 12 | { 13 | /// 14 | /// Encapsulates a series of time saving extension methods to the class. 15 | /// 16 | public static class AssemblyExtensions 17 | { 18 | /// 19 | /// Gets a collection of loadable types from the given assembly. 20 | /// Adapted from . 21 | /// 22 | /// The to load the types from. 23 | /// 24 | /// The loadable . 25 | /// 26 | public static IEnumerable GetLoadableTypes(this Assembly assembly) 27 | { 28 | if (assembly is null) 29 | { 30 | throw new ArgumentNullException(nameof(assembly)); 31 | } 32 | 33 | try 34 | { 35 | return assembly.GetTypes(); 36 | } 37 | catch (ReflectionTypeLoadException ex) 38 | { 39 | return ex.Types.Where(t => t != null); 40 | } 41 | } 42 | 43 | /// 44 | /// Converts an assembly resource into a string. 45 | /// 46 | /// The to load the strings from. 47 | /// The resource. 48 | /// The character encoding to return the resource in. 49 | /// 50 | /// The . 51 | /// 52 | public static string GetResourceAsString(this Assembly assembly, string resource, Encoding encoding = null) 53 | { 54 | encoding = encoding ?? Encoding.UTF8; 55 | 56 | using (var ms = new MemoryStream()) 57 | { 58 | using (Stream manifestResourceStream = assembly.GetManifestResourceStream(resource)) 59 | { 60 | manifestResourceStream?.CopyTo(ms); 61 | } 62 | 63 | return encoding.GetString(ms.GetBuffer()).Replace('\0', ' ').Trim(); 64 | } 65 | } 66 | 67 | /// 68 | /// Returns the identifying the file used to load the assembly. 69 | /// 70 | /// The to get the name from. 71 | /// The . 72 | public static FileInfo GetAssemblyFile(this Assembly assembly) 73 | { 74 | string codeBase = assembly.CodeBase; 75 | var uri = new Uri(codeBase); 76 | string path = uri.LocalPath; 77 | return new FileInfo(path); 78 | } 79 | 80 | /// 81 | /// Returns the identifying the file used to load the assembly. 82 | /// 83 | /// The to get the name from. 84 | /// The . 85 | public static FileInfo GetAssemblyFile(this AssemblyName assemblyName) 86 | { 87 | string codeBase = assemblyName.CodeBase; 88 | var uri = new Uri(codeBase); 89 | string path = uri.LocalPath; 90 | return new FileInfo(path); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Extensions/DoubleExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | 6 | namespace ImageProcessor 7 | { 8 | /// 9 | /// Encapsulates a series of time saving extension methods to the class. 10 | /// 11 | public static class DoubleExtensions 12 | { 13 | /// 14 | /// Converts an value into a valid . 15 | /// 16 | /// If the value given is less than 0 or greater than 255, the value will be constrained into 17 | /// those restricted ranges. 18 | /// 19 | /// 20 | /// The to convert. 21 | /// The . 22 | public static byte ToByte(this double value) => Convert.ToByte(NumberUtilities.Clamp(value, 0, 255)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Extensions/ImageExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | using System.Linq; 7 | using ImageProcessor.Configuration; 8 | using ImageProcessor.Formats; 9 | 10 | namespace ImageProcessor 11 | { 12 | /// 13 | /// Extension methods for the type. 14 | /// 15 | public static class ImageExtensions 16 | { 17 | /// 18 | /// Creates a deep copy of the source image. 19 | /// 20 | /// The source image. 21 | /// The . 22 | public static Bitmap DeepClone(this Image source) => DeepClone(source, source.PixelFormat); 23 | 24 | /// 25 | /// Creates a deep copy of the source image. 26 | /// 27 | /// The source image. 28 | /// The target pixel format. 29 | /// The . 30 | public static Bitmap DeepClone(this Image source, PixelFormat targetFormat) 31 | => DeepClone(source, targetFormat, FrameProcessingMode.All); 32 | 33 | /// 34 | /// Creates a deep copy of the source image. 35 | /// 36 | /// The source image. 37 | /// The target pixel format. 38 | /// The frame processing mode. 39 | /// The . 40 | public static Bitmap DeepClone( 41 | this Image source, 42 | PixelFormat targetFormat, 43 | FrameProcessingMode frameProcessingMode) 44 | => DeepClone(source, targetFormat, frameProcessingMode, true); 45 | 46 | /// 47 | /// Creates a deep copy of the source image. 48 | /// 49 | /// The source image. 50 | /// The target pixel format. 51 | /// The frame processing mode. 52 | /// Whether to preserve metadata. 53 | /// The . 54 | public static Bitmap DeepClone( 55 | this Image source, 56 | PixelFormat targetFormat, 57 | FrameProcessingMode frameProcessingMode, 58 | bool preserveMetaData) 59 | { 60 | IImageFormat format = ImageProcessorBootstrapper.Instance.ImageFormats 61 | .FirstOrDefault(x => x.ImageFormat.Equals(source.RawFormat)); 62 | 63 | if (format is null) 64 | { 65 | format = ImageProcessorBootstrapper.Instance.ImageFormats 66 | .First(x => x is BitmapFormat); 67 | } 68 | 69 | return format.DeepClone(source, targetFormat, frameProcessingMode, preserveMetaData); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Extensions/IntegerExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor 5 | { 6 | /// 7 | /// Encapsulates a series of time saving extension methods to the class. 8 | /// 9 | public static class IntegerExtensions 10 | { 11 | /// 12 | /// Converts an value into a valid . 13 | /// 14 | /// If the value given is less than 0 or greater than 255, the value will be constrained into 15 | /// those restricted ranges. 16 | /// 17 | /// 18 | /// The to convert. 19 | /// The . 20 | public static byte ToByte(this int value) => (byte)NumberUtilities.Clamp(value, 0, 255); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Extensions/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Buffers; 6 | using System.IO; 7 | 8 | namespace ImageProcessor 9 | { 10 | internal static class StreamExtensions 11 | { 12 | #if !SUPPORTS_SPAN_STREAM 13 | // This is a port of the CoreFX implementation and is MIT Licensed: 14 | // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 15 | public static int Read(this Stream stream, Span buffer) 16 | { 17 | // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, 18 | // in order to match the signature of the framework method that exists in 19 | // .NET Core. 20 | byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); 21 | try 22 | { 23 | int numRead = stream.Read(sharedBuffer, 0, buffer.Length); 24 | if ((uint)numRead > (uint)buffer.Length) 25 | { 26 | throw new IOException("Stream was too long."); 27 | } 28 | 29 | new Span(sharedBuffer, 0, numRead).CopyTo(buffer); 30 | return numRead; 31 | } 32 | finally 33 | { 34 | ArrayPool.Shared.Return(sharedBuffer); 35 | } 36 | } 37 | 38 | // This is a port of the CoreFX implementation and is MIT Licensed: 39 | // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L775 40 | public static void Write(this Stream stream, ReadOnlySpan buffer) 41 | { 42 | // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, 43 | // in order to match the signature of the framework method that exists in 44 | // .NET Core. 45 | byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); 46 | try 47 | { 48 | buffer.CopyTo(sharedBuffer); 49 | stream.Write(sharedBuffer, 0, buffer.Length); 50 | } 51 | finally 52 | { 53 | ArrayPool.Shared.Return(sharedBuffer); 54 | } 55 | } 56 | #endif 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Helpers/BigEndianBitConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor 5 | { 6 | /// 7 | /// Implementation of EndianBitConverter which converts to/from big-endian byte arrays. 8 | /// 9 | /// Adapted from Miscellaneous Utility Library . 10 | /// 11 | /// 12 | internal sealed class BigEndianBitConverter : EndianBitConverter 13 | { 14 | /// 15 | public override Endianness Endianness => Endianness.BigEndian; 16 | 17 | /// 18 | public override bool IsLittleEndian() => false; 19 | 20 | /// 21 | protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) 22 | { 23 | int endOffset = index + bytes - 1; 24 | for (int i = 0; i < bytes; i++) 25 | { 26 | buffer[endOffset - i] = unchecked((byte)(value & 0xff)); 27 | value >>= 8; 28 | } 29 | } 30 | 31 | /// 32 | protected internal override long FromBytes(byte[] value, int startIndex, int bytesToConvert) 33 | { 34 | long ret = 0; 35 | for (int i = 0; i < bytesToConvert; i++) 36 | { 37 | ret = unchecked((ret << 8) | value[startIndex + i]); 38 | } 39 | 40 | return ret; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Helpers/ComputerArchitectureInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | 6 | namespace ImageProcessor 7 | { 8 | /// 9 | /// Encapsulates methods that provide information about the current computer architecture. 10 | /// 11 | public class ComputerArchitectureInfo : IComputerArchitectureInfo 12 | { 13 | /// 14 | /// Returns a value indicating whether the current computer architecture is little endian. 15 | /// 16 | /// The . 17 | public bool IsLittleEndian() => BitConverter.IsLittleEndian; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Helpers/Endianness.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor 5 | { 6 | /// 7 | /// Enumerates the Endianness of a converter. 8 | /// 9 | internal enum Endianness 10 | { 11 | /// 12 | /// Little endian - least significant byte first. 13 | /// 14 | LittleEndian, 15 | 16 | /// 17 | /// Big endian - most significant byte first. 18 | /// 19 | BigEndian 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Helpers/EnumerableUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace ImageProcessor 8 | { 9 | /// 10 | /// Encapsulates a series of time saving extension methods to the interface. 11 | /// 12 | public static class EnumerableUtilities 13 | { 14 | /// 15 | /// Generates a sequence of integral numbers within a specified range. 16 | /// 17 | /// The start index, inclusive. 18 | /// The end index, exclusive. 19 | /// The incremental step. 20 | /// 21 | /// The that contains a range of sequential integral numbers. 22 | /// 23 | public static IEnumerable SteppedRange(int fromInclusive, int toExclusive, int step) 24 | { 25 | // Borrowed from Enumerable.Range 26 | long num = (fromInclusive + toExclusive) - 1L; 27 | 28 | if ((toExclusive < 0) || (num > 0x7fffffffL)) 29 | { 30 | throw new ArgumentOutOfRangeException(nameof(toExclusive)); 31 | } 32 | 33 | return SteppedRange(fromInclusive, i => i < toExclusive, step); 34 | } 35 | 36 | /// 37 | /// Generates a sequence of integral numbers within a specified range. 38 | /// 39 | /// The start index, inclusive. 40 | /// 41 | /// A method that has one parameter and returns a calculating the end index. 42 | /// 43 | /// The incremental step. 44 | /// 45 | /// The that contains a range of sequential integral numbers. 46 | /// 47 | public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) 48 | => RangeIterator(fromInclusive, toDelegate, step); 49 | 50 | /// 51 | /// Generates a sequence of integral numbers within a specified range. 52 | /// 53 | /// The start index, inclusive. 54 | /// 55 | /// A method that has one parameter and returns a calculating the end index. 56 | /// 57 | /// The incremental step. 58 | /// 59 | /// The that contains a range of sequential integral numbers. 60 | /// 61 | private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) 62 | { 63 | int i = fromInclusive; 64 | while (toDelegate(i)) 65 | { 66 | yield return i; 67 | i += step; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Helpers/GeometryUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace ImageProcessor 8 | { 9 | /// 10 | /// Utility class for common geometric functions. 11 | /// 12 | public static class GeometryUtilities 13 | { 14 | /// 15 | /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle. 16 | /// 17 | /// The angle in degrees. 18 | /// 19 | /// The representing the degree as radians. 20 | /// 21 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 22 | public static float DegreeToRadian(float degree) => degree * (float)(Math.PI / 180F); 23 | 24 | /// 25 | /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle. 26 | /// 27 | /// The angle in radians. 28 | /// 29 | /// The representing the degree as radians. 30 | /// 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public static float RadianToDegree(float radian) => radian / (float)(Math.PI / 180F); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Helpers/IComputerArchitectureInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor 5 | { 6 | /// 7 | /// Encapsulates methods that provide information about the current computer architecture. 8 | /// 9 | public interface IComputerArchitectureInfo 10 | { 11 | /// 12 | /// Returns a value indicating whether the current computer architecture is little endian. 13 | /// 14 | /// The . 15 | bool IsLittleEndian(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ImageProcessor/Common/Helpers/LittleEndianBitConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor 5 | { 6 | /// 7 | /// Implementation of EndianBitConverter which converts to/from little-endian byte arrays. 8 | /// 9 | /// Adapted from Miscellaneous Utility Library . 10 | /// 11 | /// 12 | internal sealed class LittleEndianBitConverter : EndianBitConverter 13 | { 14 | /// 15 | public override Endianness Endianness => Endianness.LittleEndian; 16 | 17 | /// 18 | public override bool IsLittleEndian() => true; 19 | 20 | /// 21 | protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) 22 | { 23 | for (int i = 0; i < bytes; i++) 24 | { 25 | buffer[i + index] = unchecked((byte)(value & 0xff)); 26 | value >>= 8; 27 | } 28 | } 29 | 30 | /// 31 | protected internal override long FromBytes(byte[] value, int startIndex, int bytesToConvert) 32 | { 33 | long ret = 0; 34 | for (int i = 0; i < bytesToConvert; i++) 35 | { 36 | ret = unchecked((ret << 8) | value[startIndex + bytesToConvert - 1 - i]); 37 | } 38 | 39 | return ret; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using ImageProcessor.Formats; 8 | 9 | namespace ImageProcessor.Configuration 10 | { 11 | /// 12 | /// The bootstrapper containing initialization code for extending ImageProcessor. 13 | /// 14 | public sealed class ImageProcessorBootstrapper 15 | { 16 | /// 17 | /// A new instance Initializes a new instance of the class. 18 | /// with lazy initialization. 19 | /// 20 | private static readonly Lazy Lazy = 21 | new Lazy(() => new ImageProcessorBootstrapper()); 22 | 23 | /// 24 | /// Prevents a default instance of the class from being created. 25 | /// 26 | private ImageProcessorBootstrapper() => this.LoadSupportedImageFormats(); 27 | 28 | /// 29 | /// Gets the current instance of the class. 30 | /// 31 | public static ImageProcessorBootstrapper Instance => Lazy.Value; 32 | 33 | /// 34 | /// Gets the supported image formats. 35 | /// 36 | public IReadOnlyCollection ImageFormats { get; private set; } 37 | 38 | /// 39 | /// Gets the currently installed logger. 40 | /// 41 | public ILogger Logger { get; private set; } = new DefaultLogger(); 42 | 43 | /// 44 | /// Gets the native binary factory for registering embedded (unmanaged) binaries. 45 | /// 46 | public NativeBinaryFactory NativeBinaryFactory { get; } = new NativeBinaryFactory(); 47 | 48 | /// 49 | /// Adds the given image formats to the supported format collection. 50 | /// 51 | /// The instances to add. 52 | public void AddImageFormats(params IImageFormat[] formats) 53 | { 54 | var currentFormats = (List)this.ImageFormats; 55 | 56 | foreach (IImageFormat format in formats) 57 | { 58 | if (currentFormats.Any(x => x.Equals(format))) 59 | { 60 | continue; 61 | } 62 | 63 | currentFormats.Add(format); 64 | } 65 | } 66 | 67 | /// 68 | /// Allows the setting of the default logger. Useful for when 69 | /// the type finder fails to dynamically add the custom logger implementation. 70 | /// 71 | /// The logger to use. 72 | public void SetLogger(ILogger logger) => this.Logger = logger; 73 | 74 | /// 75 | /// Creates a collection of supported image formats that ImageProcessor can run. 76 | /// 77 | private void LoadSupportedImageFormats() 78 | { 79 | this.ImageFormats = new List 80 | { 81 | new BitmapFormat(), 82 | new GifFormat(), 83 | new JpegFormat(), 84 | new PngFormat(), 85 | new TiffFormat() 86 | }; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/ImageProcessor/Configuration/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace ImageProcessor.Configuration 8 | { 9 | /// 10 | /// Provides access to unmanaged native methods. 11 | /// 12 | internal static class NativeMethods 13 | { 14 | /// 15 | /// Loads the specified module into the address space of the calling process. 16 | /// The specified module may cause other modules to be loaded. 17 | /// 18 | /// 19 | /// The name of the module. This can be either a library module or 20 | /// an executable module. 21 | /// 22 | /// If the function succeeds, the return value is a handle to the module; otherwise null. 23 | [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)] 24 | public static extern IntPtr LoadLibrary(string libname); 25 | 26 | /// 27 | /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. 28 | /// When the reference count reaches zero, the module is unloaded from the address space of the calling 29 | /// process and the handle is no longer valid. 30 | /// 31 | /// A handle to the loaded library module. 32 | /// The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function returns this handle. 33 | /// If the function succeeds, the return value is nonzero; otherwise zero. 34 | [DllImport("kernel32", SetLastError = true)] 35 | public static extern bool FreeLibrary(IntPtr hModule); 36 | 37 | /// 38 | /// Loads the specified module into the address space of the calling process. 39 | /// The specified module may cause other modules to be loaded. 40 | /// 41 | /// 42 | /// The name of the module. This can be either a library module or 43 | /// an executable module. 44 | /// 45 | /// 46 | /// The flag indicating whether to load the library immediately or lazily. 47 | /// 48 | /// 49 | /// If the function succeeds, the return value is a handle to the module; otherwise null. 50 | /// 51 | [DllImport("libdl")] 52 | #pragma warning disable IDE1006 // Naming Styles 53 | public static extern IntPtr dlopen(string libname, int flags); 54 | #pragma warning restore IDE1006 // Naming Styles 55 | 56 | /// 57 | /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. 58 | /// When the reference count reaches zero, the module is unloaded from the address space of the calling 59 | /// process and the handle is no longer valid. 60 | /// 61 | /// A handle to the loaded library module. 62 | /// The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function returns this handle. 63 | /// If the function succeeds, the return value is nonzero; otherwise zero. 64 | [DllImport("libdl")] 65 | #pragma warning disable IDE1006 // Naming Styles 66 | public static extern int dlclose(IntPtr hModule); 67 | #pragma warning restore IDE1006 // Naming Styles 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/BitDepth.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Formats 5 | { 6 | /// 7 | /// Provides enumeration for the available bit depths. 8 | /// 9 | public enum BitDepth : long 10 | { 11 | /// 12 | /// 1 bit per pixel 13 | /// 14 | Bit1 = 1L, 15 | 16 | /// 17 | /// 4 bits per pixel 18 | /// 19 | Bit4 = 4L, 20 | 21 | /// 22 | /// 8 bits per pixel 23 | /// 24 | Bit8 = 8L, 25 | 26 | /// 27 | /// 16 bits per pixel 28 | /// 29 | Bit16 = 16L, 30 | 31 | /// 32 | /// 24 bits per pixel 33 | /// 34 | Bit24 = 24L, 35 | 36 | /// 37 | /// 32 bits per pixel 38 | /// 39 | Bit32 = 32L 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/BitmapFormat.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | using System.IO; 7 | using System.Text; 8 | 9 | namespace ImageProcessor.Formats 10 | { 11 | /// 12 | /// Provides the necessary information to support bitmap images. 13 | /// 14 | public sealed class BitmapFormat : FormatBase 15 | { 16 | /// 17 | public override byte[][] FileHeaders { get; } = new[] 18 | { 19 | Encoding.ASCII.GetBytes("BM") 20 | }; 21 | 22 | /// 23 | public override string[] FileExtensions { get; } = new[] 24 | { 25 | "bmp" 26 | }; 27 | 28 | /// 29 | public override string MimeType { get; } = "image/bmp"; 30 | 31 | /// 32 | public override ImageFormat ImageFormat { get; } = ImageFormat.Bmp; 33 | 34 | /// 35 | public override void Save(Stream stream, Image image, BitDepth bitDepth, long quality) 36 | { 37 | switch (bitDepth) 38 | { 39 | case BitDepth.Bit1: 40 | case BitDepth.Bit4: 41 | case BitDepth.Bit8: 42 | 43 | // Save as 8 bit quantized image. 44 | // TODO: Consider allowing 1 and 4 bit quantization. 45 | using (Bitmap quantized = this.Quantizer.Quantize(image)) 46 | { 47 | CopyMetadata(image, quantized); 48 | base.Save(stream, quantized, bitDepth, quality); 49 | } 50 | 51 | break; 52 | 53 | default: 54 | 55 | PixelFormat pixelFormat = FormatUtilities.GetPixelFormatForBitDepth(bitDepth); 56 | 57 | if (pixelFormat != image.PixelFormat) 58 | { 59 | using (Image copy = this.DeepClone(image, pixelFormat, FrameProcessingMode.All, true)) 60 | { 61 | base.Save(stream, copy, bitDepth, quality); 62 | } 63 | } 64 | else 65 | { 66 | base.Save(stream, image, bitDepth, quality); 67 | } 68 | 69 | break; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/GifDecoder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | using ImageProcessor.Metadata; 8 | 9 | namespace ImageProcessor.Formats 10 | { 11 | /// 12 | /// Allows the decoding of gifs into individual frames. 13 | /// 14 | public class GifDecoder 15 | { 16 | private readonly Image image; 17 | private readonly byte[] times = new byte[4]; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// 23 | /// The to decode. 24 | /// 25 | public GifDecoder(Image image) 26 | : this(image, FrameProcessingMode.All) 27 | { 28 | } 29 | 30 | /// 31 | /// Initializes a new instance of the class. 32 | /// 33 | /// The image to decode. 34 | /// The frame processing mode. 35 | public GifDecoder(Image image, FrameProcessingMode frameProcessingMode) 36 | { 37 | this.image = image; 38 | 39 | if (FormatUtilities.IsAnimated(image) && frameProcessingMode == FrameProcessingMode.All) 40 | { 41 | this.IsAnimated = true; 42 | this.FrameCount = image.GetFrameCount(FrameDimension.Time); 43 | const int LoopCount = (int)ExifPropertyTag.LoopCount; 44 | const int FrameDelay = (int)ExifPropertyTag.FrameDelay; 45 | 46 | // Loop info is stored at byte 20737. Default to infinite loop if not found. 47 | this.LoopCount = Array.IndexOf(image.PropertyIdList, LoopCount) != -1 48 | ? BitConverter.ToInt16(image.GetPropertyItem(LoopCount).Value, 0) 49 | : 0; 50 | 51 | // Get the times stored in the gif. Default to 0 if not found. 52 | if (Array.IndexOf(this.image.PropertyIdList, FrameDelay) != -1) 53 | { 54 | this.times = this.image.GetPropertyItem(FrameDelay).Value; 55 | } 56 | } 57 | else 58 | { 59 | this.FrameCount = 1; 60 | } 61 | } 62 | 63 | /// 64 | /// Gets the input image. 65 | /// 66 | public Image Image { get; } 67 | 68 | /// 69 | /// Gets a value indicating whether the image is animated. 70 | /// 71 | public bool IsAnimated { get; } 72 | 73 | /// 74 | /// Gets the loop count. 75 | /// 76 | public int LoopCount { get; } 77 | 78 | /// 79 | /// Gets the frame count. 80 | /// 81 | public int FrameCount { get; } 82 | 83 | /// 84 | /// Gets the frame at the specified index. 85 | /// 86 | /// Image frames are returned in format to allow processing 87 | /// using the canvas. 88 | /// 89 | /// 90 | /// The index. 91 | /// 92 | /// The . 93 | /// 94 | public GifFrame GetFrame(int index) 95 | { 96 | // Convert each 4-byte chunk into an integer. 97 | // GDI returns a single array with all delays, while Mono returns a different array for each frame. 98 | var delay = TimeSpan.FromMilliseconds(BitConverter.ToInt32(this.times, (4 * index) % this.times.Length) * 10); 99 | 100 | // Find the frame 101 | this.image.SelectActiveFrame(FrameDimension.Time, index); 102 | 103 | var frame = new GifFrame(this.image, delay); 104 | 105 | // Reset the image 106 | this.image.SelectActiveFrame(FrameDimension.Time, 0); 107 | 108 | return frame; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/GifFormat.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | using System.IO; 8 | using System.Text; 9 | 10 | namespace ImageProcessor.Formats 11 | { 12 | /// 13 | /// Provides the necessary information to support gif images. 14 | /// 15 | public sealed class GifFormat : FormatBase 16 | { 17 | /// 18 | public override byte[][] FileHeaders { get; } = new[] { Encoding.ASCII.GetBytes("GIF") }; 19 | 20 | /// 21 | public override string[] FileExtensions { get; } = new[] { "gif" }; 22 | 23 | /// 24 | public override string MimeType { get; } = "image/gif"; 25 | 26 | /// 27 | public override ImageFormat ImageFormat { get; } = ImageFormat.Gif; 28 | 29 | /// 30 | public override Image ApplyProcessor(T processor, ImageFactory factory) 31 | { 32 | var decoder = new GifDecoder(factory.Image, factory.FrameProcessingMode); 33 | var encoder = new GifEncoder(this.Quantizer, decoder.LoopCount); 34 | 35 | for (int i = 0; i < decoder.FrameCount; i++) 36 | { 37 | GifFrame frame = null; 38 | try 39 | { 40 | // Frame images are already a copy so we don't have to copy again for processing. 41 | frame = decoder.GetFrame(i); 42 | processor.ProcessImageFrame(factory, frame.Image); 43 | encoder.EncodeFrame(frame); 44 | } 45 | catch (Exception ex) 46 | { 47 | throw new ImageProcessingException("Error processing image with " + typeof(T).Name, ex); 48 | } 49 | finally 50 | { 51 | frame?.Dispose(); 52 | } 53 | } 54 | 55 | return encoder.Encode(); 56 | } 57 | 58 | /// 59 | public override Bitmap DeepClone(Image source, PixelFormat targetFormat, FrameProcessingMode frameProcessingMode, bool preserveMetaData) 60 | { 61 | var decoder = new GifDecoder(source, frameProcessingMode); 62 | var encoder = new GifEncoder(this.Quantizer, decoder.LoopCount); 63 | 64 | for (int i = 0; i < decoder.FrameCount; i++) 65 | { 66 | using (GifFrame frame = decoder.GetFrame(i)) 67 | { 68 | encoder.EncodeFrame(frame); 69 | } 70 | } 71 | 72 | Image copy = encoder.Encode(); 73 | 74 | if (preserveMetaData) 75 | { 76 | CopyMetadata(source, copy); 77 | } 78 | 79 | return (Bitmap)copy; 80 | } 81 | 82 | /// 83 | public override void Save(Stream stream, Image image, BitDepth bitDepth, long quality) 84 | { 85 | // Never use default save for gifs. It's terrible. 86 | // BitDepth is ignored here since we always produce 8 bit images. 87 | var decoder = new GifDecoder(image, FrameProcessingMode.All); 88 | var encoder = new GifEncoder(this.Quantizer, decoder.LoopCount); 89 | 90 | for (int i = 0; i < decoder.FrameCount; i++) 91 | { 92 | using (GifFrame frame = decoder.GetFrame(i)) 93 | { 94 | encoder.EncodeFrame(frame); 95 | } 96 | } 97 | 98 | encoder.EncodeToStream(stream); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/GifFrame.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | 8 | namespace ImageProcessor.Formats 9 | { 10 | /// 11 | /// A single gif frame. 12 | /// 13 | public sealed class GifFrame : IDisposable 14 | { 15 | private bool isDisposed; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The source image to copy into the new frame. 21 | /// The time, in milliseconds, to wait before animating to the next frame. 22 | public GifFrame(Image source, TimeSpan delay) 23 | : this(source, delay, 0, 0) 24 | { 25 | } 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// The source image to copy into the new frame. 31 | /// The time, in milliseconds, to wait before animating to the next frame. 32 | /// The frame left position. 33 | /// The frame top position. 34 | public GifFrame(Image source, TimeSpan delay, int x, int y) 35 | { 36 | this.Image = FormatUtilities.DeepCloneImageFrame(source, PixelFormat.Format32bppArgb); 37 | this.Delay = delay; 38 | this.X = x; 39 | this.Y = y; 40 | } 41 | 42 | /// 43 | /// Gets the image, stored in format to allow processing 44 | /// using the canvas. 45 | /// 46 | public Image Image { get; private set; } 47 | 48 | /// 49 | /// Gets the delay in milliseconds. 50 | /// 51 | public TimeSpan Delay { get; } 52 | 53 | /// 54 | /// Gets the left position of the frame. 55 | /// 56 | public int X { get; } 57 | 58 | /// 59 | /// Gets the top position of the frame. 60 | /// 61 | public int Y { get; } 62 | 63 | /// 64 | public void Dispose() 65 | { 66 | if (this.isDisposed) 67 | { 68 | return; 69 | } 70 | 71 | this.Image?.Dispose(); 72 | this.Image = null; 73 | this.isDisposed = true; 74 | GC.SuppressFinalize(this); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/IImageFormat.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | using System.IO; 8 | using ImageProcessor.Processing; 9 | using ImageProcessor.Quantizers; 10 | 11 | namespace ImageProcessor.Formats 12 | { 13 | /// 14 | /// Defines the contract for a supported image format. 15 | /// 16 | public interface IImageFormat : IEquatable 17 | { 18 | /// 19 | /// Gets the file headers. 20 | /// 21 | byte[][] FileHeaders { get; } 22 | 23 | /// 24 | /// Gets the list of file extensions. 25 | /// 26 | string[] FileExtensions { get; } 27 | 28 | /// 29 | /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. 30 | /// 31 | string MimeType { get; } 32 | 33 | /// 34 | /// Gets the default file extension. 35 | /// 36 | string DefaultExtension { get; } 37 | 38 | /// 39 | /// Gets the file format of the image. 40 | /// 41 | ImageFormat ImageFormat { get; } 42 | 43 | /// 44 | /// Gets the quantizer for reducing the image palette. 45 | /// 46 | IQuantizer Quantizer { get; } 47 | 48 | /// 49 | /// Loads the image to process. 50 | /// 51 | /// The containing the image information. 52 | /// 53 | /// The . 54 | /// 55 | Image Load(Stream stream); 56 | 57 | /// 58 | /// Applies the given processor the current image. 59 | /// 60 | /// The type of . 61 | /// The processor. 62 | /// The factory. 63 | /// The . 64 | /// Thrown if an error occurs during processing. 65 | Image ApplyProcessor(T processor, ImageFactory factory) 66 | where T : IGraphicsProcessor; 67 | 68 | /// 69 | /// Creates a deep copy of the source image. 70 | /// 71 | /// The source image. 72 | /// The target pixel format. 73 | /// The frame processing mode. 74 | /// Whether to preserve metadata. 75 | /// The . 76 | Bitmap DeepClone(Image source, PixelFormat targetFormat, FrameProcessingMode frameProcessingMode, bool preserveMetaData); 77 | 78 | /// 79 | /// Saves the current image to the specified output stream. 80 | /// 81 | /// The to save the image information to. 82 | /// The to save. 83 | /// The color depth in number of bits per pixel to save the image with. 84 | /// The quality, if applicable, to save the image at. 85 | void Save(Stream stream, Image image, BitDepth bitDepth, long quality); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/JpegFormat.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | using System.IO; 8 | using System.Text; 9 | using Encoder = System.Drawing.Imaging.Encoder; 10 | 11 | namespace ImageProcessor.Formats 12 | { 13 | /// 14 | /// Provides the necessary information to support jpeg images. 15 | /// 16 | public sealed class JpegFormat : FormatBase 17 | { 18 | private ImageCodecInfo imageCodecInfo; 19 | 20 | /// 21 | public override byte[][] FileHeaders { get; } = new[] 22 | { 23 | new byte[] { 0xFF, 0xD8, 0xFF }, 24 | Encoding.ASCII.GetBytes("ÿØÿà..JFIF"), 25 | Encoding.ASCII.GetBytes("ÿØÿà..EXIF") 26 | }; 27 | 28 | /// 29 | public override string[] FileExtensions { get; } = new[] 30 | { 31 | "jpg", "jpeg", "jfif" 32 | }; 33 | 34 | /// 35 | public override string MimeType { get; } = "image/jpeg"; 36 | 37 | /// 38 | public override ImageFormat ImageFormat { get; } = ImageFormat.Jpeg; 39 | 40 | /// 41 | public override void Save(Stream stream, Image image, BitDepth bitDepth, long quality) 42 | { 43 | // Jpegs can be saved with different settings to include a quality setting for the JPEG compression. 44 | // This improves output compression and quality. 45 | using (EncoderParameters encoderParameters = GetEncoderParameters(quality)) 46 | { 47 | image.Save(stream, this.GetCodecInfo(), encoderParameters); 48 | } 49 | } 50 | 51 | private static EncoderParameters GetEncoderParameters(long quality) 52 | { 53 | return new EncoderParameters(1) 54 | { 55 | // Set the quality. 56 | Param = { [0] = new EncoderParameter(Encoder.Quality, quality) } 57 | }; 58 | } 59 | 60 | private ImageCodecInfo GetCodecInfo() 61 | { 62 | return this.imageCodecInfo ?? (this.imageCodecInfo = Array.Find( 63 | ImageCodecInfo.GetImageEncoders(), 64 | ici => ici.MimeType.Equals(this.MimeType, StringComparison.OrdinalIgnoreCase))); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/PngFormat.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | using System.IO; 7 | using ImageProcessor.Quantizers; 8 | 9 | namespace ImageProcessor.Formats 10 | { 11 | /// 12 | /// Provides the necessary information to support png images. 13 | /// 14 | public sealed class PngFormat : FormatBase 15 | { 16 | private static readonly byte[][] Identifier = new[] 17 | { 18 | new byte[] { 0x89, 0x50, 0x4E, 0x47 } 19 | }; 20 | 21 | private static readonly string[] Extensions = new[] 22 | { 23 | "png" 24 | }; 25 | 26 | /// 27 | public override byte[][] FileHeaders => Identifier; 28 | 29 | /// 30 | public override string[] FileExtensions => Extensions; 31 | 32 | /// 33 | public override string MimeType => "image/png"; 34 | 35 | /// 36 | public override ImageFormat ImageFormat => ImageFormat.Png; 37 | 38 | /// 39 | public override IQuantizer Quantizer => new WuQuantizer(); 40 | 41 | /// 42 | public override void Save(Stream stream, Image image, BitDepth bitDepth, long quality) 43 | { 44 | switch (bitDepth) 45 | { 46 | case BitDepth.Bit1: 47 | case BitDepth.Bit4: 48 | case BitDepth.Bit8: 49 | 50 | // Save as 8 bit quantized image. 51 | // TODO: Consider allowing 1 and 4 bit quantization. 52 | using (Bitmap quantized = this.Quantizer.Quantize(image)) 53 | { 54 | CopyMetadata(image, quantized); 55 | base.Save(stream, quantized, bitDepth, quality); 56 | } 57 | 58 | break; 59 | 60 | default: 61 | 62 | PixelFormat pixelFormat = FormatUtilities.GetPixelFormatForBitDepth(bitDepth); 63 | 64 | if (pixelFormat != image.PixelFormat) 65 | { 66 | using (Image copy = this.DeepClone(image, pixelFormat, FrameProcessingMode.All, true)) 67 | { 68 | base.Save(stream, copy, bitDepth, quality); 69 | } 70 | } 71 | else 72 | { 73 | base.Save(stream, image, bitDepth, quality); 74 | } 75 | 76 | break; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ImageProcessor/Formats/TiffFormat.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | using System.IO; 8 | using ImageProcessor.Quantizers; 9 | 10 | namespace ImageProcessor.Formats 11 | { 12 | /// 13 | /// Provides the necessary information to support tiff images. 14 | /// 15 | public sealed class TiffFormat : FormatBase 16 | { 17 | private ImageCodecInfo imageCodecInfo; 18 | 19 | /// 20 | public override byte[][] FileHeaders { get; } = new[] 21 | { 22 | new byte[] { 0x49, 0x49, 0x2A, 0x0 }, 23 | new byte[] { 0x4D, 0x4D, 0x0, 0x2A } 24 | }; 25 | 26 | /// 27 | public override string[] FileExtensions { get; } = new[] 28 | { 29 | "tif", "tiff" 30 | }; 31 | 32 | /// 33 | public override string MimeType => "image/tiff"; 34 | 35 | /// 36 | public override ImageFormat ImageFormat => ImageFormat.Tiff; 37 | 38 | /// 39 | public override IQuantizer Quantizer { get; } = new OctreeQuantizer(); 40 | 41 | /// 42 | public override void Save(Stream stream, Image image, BitDepth bitDepth, long quality) 43 | { 44 | // Tiffs can be saved with different bit depths but throws if we use 16 bits. 45 | if (bitDepth == BitDepth.Bit16) 46 | { 47 | bitDepth = BitDepth.Bit24; 48 | } 49 | 50 | using (EncoderParameters encoderParameters = GetEncoderParameters(bitDepth)) 51 | { 52 | switch (bitDepth) 53 | { 54 | case BitDepth.Bit4: 55 | case BitDepth.Bit8: 56 | // Save as 8 bit quantized image. 57 | using (Bitmap quantized = this.Quantizer.Quantize(image)) 58 | { 59 | CopyMetadata(image, quantized); 60 | quantized.Save(stream, this.GetCodecInfo(), encoderParameters); 61 | } 62 | 63 | return; 64 | 65 | case BitDepth.Bit24: 66 | case BitDepth.Bit32: 67 | 68 | PixelFormat pixelFormat = FormatUtilities.GetPixelFormatForBitDepth(bitDepth); 69 | 70 | if (pixelFormat != image.PixelFormat) 71 | { 72 | using (Image copy = this.DeepClone(image, pixelFormat, FrameProcessingMode.All, true)) 73 | { 74 | copy.Save(stream, this.GetCodecInfo(), encoderParameters); 75 | } 76 | } 77 | else 78 | { 79 | image.Save(stream, this.GetCodecInfo(), encoderParameters); 80 | } 81 | 82 | break; 83 | default: 84 | 85 | // Encoding is handled by the encoding parameters. 86 | image.Save(stream, this.GetCodecInfo(), encoderParameters); 87 | break; 88 | } 89 | } 90 | } 91 | 92 | private static EncoderParameters GetEncoderParameters(BitDepth bitDepth) 93 | { 94 | long colorDepth = (long)bitDepth; 95 | 96 | // CompressionCCITT4 provides 1 bit diffusion. 97 | long compression = (long)(bitDepth == BitDepth.Bit1 98 | ? EncoderValue.CompressionCCITT4 99 | : EncoderValue.CompressionLZW); 100 | 101 | var encoderParameters = new EncoderParameters(2); 102 | encoderParameters.Param[0] = new EncoderParameter(Encoder.Compression, compression); 103 | encoderParameters.Param[1] = new EncoderParameter(Encoder.ColorDepth, colorDepth); 104 | 105 | return encoderParameters; 106 | } 107 | 108 | private ImageCodecInfo GetCodecInfo() 109 | { 110 | return this.imageCodecInfo ?? (this.imageCodecInfo = Array.Find( 111 | ImageCodecInfo.GetImageEncoders(), 112 | ici => ici.MimeType.Equals(this.MimeType, StringComparison.OrdinalIgnoreCase))); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/ImageProcessor/FrameProcessingMode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor 5 | { 6 | /// 7 | /// Enumerated frame process modes to apply to multiframe images. 8 | /// 9 | public enum FrameProcessingMode 10 | { 11 | /// 12 | /// Processes and keeps all the frames of a multiframe image. 13 | /// 14 | All, 15 | 16 | /// 17 | /// Processes and keeps only the first frame of a multiframe image. 18 | /// 19 | First 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ImageProcessor/ImageProcessor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net452 5 | 7.3 6 | 7 | 8 | 9 | bin\Release\net452\ImageProcessor.xml 10 | true 11 | 12 | 13 | 14 | bin\Debug\net452\ImageProcessor.xml 15 | true 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ..\..\ImageProcessor.ruleset 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/ImageProcessor/Metadata/ExifBitConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Text; 6 | 7 | namespace ImageProcessor.Metadata 8 | { 9 | /// 10 | /// The exif bit converter. Converts based on the endianness of the current machine. 11 | /// 12 | internal sealed class ExifBitConverter : EndianBitConverter 13 | { 14 | /// 15 | /// The computer architecture info. 16 | /// 17 | private readonly IComputerArchitectureInfo computerArchitectureInfo; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// 23 | /// The computer architecture info. 24 | /// 25 | public ExifBitConverter(IComputerArchitectureInfo computerArchitectureInfo) => this.computerArchitectureInfo = computerArchitectureInfo; 26 | 27 | /// 28 | public override Endianness Endianness => this.IsLittleEndian() ? Endianness.LittleEndian : Endianness.BigEndian; 29 | 30 | /// 31 | public override bool IsLittleEndian() => this.computerArchitectureInfo.IsLittleEndian(); 32 | 33 | /// 34 | /// Converts the given ascii string to an array of bytes optionally adding a null terminator. 35 | /// 36 | /// The string to convert. 37 | /// Whether to add a null terminator to the end of the string. 38 | /// The . 39 | public byte[] GetBytes(string value, bool addTerminator) 40 | { 41 | if (addTerminator) 42 | { 43 | value += '\0'; 44 | } 45 | 46 | byte[] bytes = Encoding.ASCII.GetBytes(value); 47 | 48 | if (!this.IsLittleEndian()) 49 | { 50 | Array.Reverse(bytes); 51 | } 52 | 53 | return bytes; 54 | } 55 | 56 | /// 57 | /// Converts the given ascii string to an array of bytes without adding a null terminator. 58 | /// 59 | /// The string to convert. 60 | /// The . 61 | public byte[] GetBytes(string value) => this.GetBytes(value, false); 62 | 63 | /// 64 | /// Converts the given unsigned rational number to an array of bytes. 65 | /// 66 | /// 67 | /// The containing the numerator and denominator. 68 | /// 69 | /// The . 70 | public byte[] GetBytes(Rational value) 71 | { 72 | byte[] num = this.GetBytes(value.Numerator); 73 | byte[] den = this.GetBytes(value.Denominator); 74 | byte[] data = new byte[8]; 75 | Array.Copy(num, 0, data, 0, 4); 76 | Array.Copy(den, 0, data, 4, 4); 77 | return data; 78 | } 79 | 80 | /// 81 | /// Converts the given signed rational number to an array of bytes. 82 | /// 83 | /// 84 | /// The containing the numerator and denominator. 85 | /// 86 | /// The . 87 | public byte[] GetBytes(Rational value) 88 | { 89 | byte[] num = this.GetBytes(value.Numerator); 90 | byte[] den = this.GetBytes(value.Denominator); 91 | byte[] data = new byte[8]; 92 | Array.Copy(num, 0, data, 0, 4); 93 | Array.Copy(den, 0, data, 4, 4); 94 | return data; 95 | } 96 | 97 | /// 98 | protected internal override long FromBytes(byte[] value, int startIndex, int bytesToConvert) 99 | { 100 | if (this.IsLittleEndian()) 101 | { 102 | return Little.FromBytes(value, startIndex, bytesToConvert); 103 | } 104 | 105 | return Big.FromBytes(value, startIndex, bytesToConvert); 106 | } 107 | 108 | /// 109 | protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) 110 | { 111 | if (this.IsLittleEndian()) 112 | { 113 | Little.CopyBytesImpl(value, bytes, buffer, index); 114 | } 115 | else 116 | { 117 | Big.CopyBytesImpl(value, bytes, buffer, index); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/ImageProcessor/Metadata/ExifPropertyTagConstants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Linq; 6 | 7 | namespace ImageProcessor.Metadata 8 | { 9 | /// 10 | /// Contains constants grouping together common property items. 11 | /// 12 | public static class ExifPropertyTagConstants 13 | { 14 | /// 15 | /// Gets all required property items. The Gif format specifically requires these properties. 16 | /// 17 | public static readonly ExifPropertyTag[] RequiredPropertyItems = 18 | { 19 | ExifPropertyTag.LoopCount, ExifPropertyTag.FrameDelay 20 | }; 21 | 22 | /// 23 | /// Gets all required property items plus geolocation specific property items. 24 | /// 25 | public static readonly ExifPropertyTag[] GeolocationPropertyItems = RequiredPropertyItems.Union( 26 | new[] 27 | { 28 | ExifPropertyTag.GpsAltitude, 29 | ExifPropertyTag.GpsAltitudeRef, 30 | ExifPropertyTag.GpsDestBear, 31 | ExifPropertyTag.GpsDestBearRef, 32 | ExifPropertyTag.GpsDestDist, 33 | ExifPropertyTag.GpsDestDistRef, 34 | ExifPropertyTag.GpsDestLat, 35 | ExifPropertyTag.GpsDestLatRef, 36 | ExifPropertyTag.GpsDestLong, 37 | ExifPropertyTag.GpsDestLongRef, 38 | ExifPropertyTag.GpsGpsDop, 39 | ExifPropertyTag.GpsGpsMeasureMode, 40 | ExifPropertyTag.GpsGpsSatellites, 41 | ExifPropertyTag.GpsGpsStatus, 42 | ExifPropertyTag.GpsGpsTime, 43 | ExifPropertyTag.GpsIFD, 44 | ExifPropertyTag.GpsImgDir, 45 | ExifPropertyTag.GpsImgDirRef, 46 | ExifPropertyTag.GpsLatitude, 47 | ExifPropertyTag.GpsLatitudeRef, 48 | ExifPropertyTag.GpsLongitude, 49 | ExifPropertyTag.GpsLongitudeRef, 50 | ExifPropertyTag.GpsMapDatum, 51 | ExifPropertyTag.GpsSpeed, 52 | ExifPropertyTag.GpsSpeedRef, 53 | ExifPropertyTag.GpsTrack, 54 | ExifPropertyTag.GpsTrackRef, 55 | ExifPropertyTag.GpsVer 56 | }).ToArray(); 57 | 58 | /// 59 | /// Gets all required property items plus copyright specific property items. 60 | /// 61 | public static readonly ExifPropertyTag[] CopyrightPropertyItems = RequiredPropertyItems.Union( 62 | new[] 63 | { 64 | ExifPropertyTag.Copyright, 65 | ExifPropertyTag.Artist, 66 | ExifPropertyTag.ImageTitle, 67 | ExifPropertyTag.ImageDescription, 68 | ExifPropertyTag.ExifUserComment, 69 | ExifPropertyTag.EquipMake, 70 | ExifPropertyTag.EquipModel, 71 | ExifPropertyTag.ThumbnailArtist, 72 | ExifPropertyTag.ThumbnailCopyRight, 73 | ExifPropertyTag.ThumbnailImageDescription, 74 | ExifPropertyTag.ThumbnailEquipMake, 75 | ExifPropertyTag.ThumbnailEquipModel, 76 | }).ToArray(); 77 | 78 | /// 79 | /// Gets all required property items plus copyright and geolocation specific property items. 80 | /// 81 | public static readonly ExifPropertyTag[] CopyrightAndGeolocationPropertyItems = 82 | GeolocationPropertyItems.Union(CopyrightPropertyItems).ToArray(); 83 | 84 | /// 85 | /// Gets all known property items. 86 | /// 87 | public static readonly ExifPropertyTag[] All = (ExifPropertyTag[])Enum.GetValues(typeof(ExifPropertyTag)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/ImageProcessor/Metadata/ExifPropertyTagType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Metadata 5 | { 6 | /// 7 | /// Specifies the data type of the values stored in the value data member of that same PropertyItem object. 8 | /// . 9 | /// 10 | public enum ExifPropertyTagType : short 11 | { 12 | /// 13 | /// Specifies that the value data member is an array of bytes. 14 | /// 15 | Byte = 1, 16 | 17 | /// 18 | /// Specifies that the value data member is a null-terminated ASCII string. If you set the type data member of a 19 | /// PropertyItem object to ExifPropertyTagTypeASCII, you should set the length data member to the length of the string 20 | /// including the NULL terminator. For example, the string HELLO would have a length of 6. 21 | /// 22 | ASCII = 2, 23 | 24 | /// 25 | /// Specifies that the value data member is an array of unsigned short (16-bit) integers. 26 | /// 27 | UShort = 3, 28 | 29 | /// 30 | /// Specifies that the value data member is an array of unsigned long (32-bit) integers. 31 | /// 32 | ULong = 4, 33 | 34 | /// 35 | /// Specifies that the value data member is an array of pairs of unsigned long integers. Each pair represents a 36 | /// fraction; the first integer is the numerator and the second integer is the denominator. 37 | /// 38 | Rational = 5, 39 | 40 | /// 41 | /// Specifies that the value data member is an array of bytes that can hold values of any data type. 42 | /// 43 | Undefined = 6, 44 | 45 | /// 46 | /// Specifies that the value data member is an array of signed long (32-bit) integers. 47 | /// 48 | SLong = 7, 49 | 50 | /// 51 | /// Specifies that the value data member is an array of pairs of signed long integers. Each pair represents a 52 | /// fraction; the first integer is the numerator and the second integer is the denominator. 53 | /// 54 | SRational = 10 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ImageProcessor/Metadata/Int32Converter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Runtime.InteropServices; 5 | 6 | namespace ImageProcessor.Metadata 7 | { 8 | /// 9 | /// Provides a way to convert integers to an array of bytes without creating multiple 10 | /// short term arrays. 11 | /// 12 | [StructLayout(LayoutKind.Explicit)] 13 | internal readonly struct Int32Converter 14 | { 15 | /// 16 | /// The value of the byte array as an integer. 17 | /// 18 | [FieldOffset(0)] 19 | public readonly int Value; 20 | 21 | /// 22 | /// The first byte. 23 | /// 24 | [FieldOffset(0)] 25 | public readonly byte Byte1; 26 | 27 | /// 28 | /// The second byte. 29 | /// 30 | [FieldOffset(1)] 31 | public readonly byte Byte2; 32 | 33 | /// 34 | /// The third byte. 35 | /// 36 | [FieldOffset(2)] 37 | public readonly byte Byte3; 38 | 39 | /// 40 | /// The fourth byte. 41 | /// 42 | [FieldOffset(3)] 43 | public readonly byte Byte4; 44 | 45 | /// 46 | /// Initializes a new instance of the struct. 47 | /// 48 | /// 49 | /// The value to convert from. 50 | /// 51 | public Int32Converter(int value) 52 | : this() => this.Value = value; 53 | 54 | /// 55 | /// Allows the implicit conversion of an instance of to a . 56 | /// 57 | /// 58 | /// The instance of to convert. 59 | /// 60 | /// An instance of . 61 | public static implicit operator int(Int32Converter value) => value.Value; 62 | 63 | /// 64 | /// Allows the implicit conversion of an instance of to a . 65 | /// 66 | /// The instance of to convert. 67 | /// An instance of . 68 | public static implicit operator Int32Converter(int value) => new Int32Converter(value); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/ImageProcessor/Metadata/PropertyTagResolutionUnit.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Metadata 5 | { 6 | /// 7 | /// The following enum gives the unit of measure for the horizontal resolution and the vertical resolution 8 | /// supported by Windows GDI+. 9 | /// . 10 | /// 11 | public enum PropertyTagResolutionUnit : ushort 12 | { 13 | /// 14 | /// The resolution is measured in pixels per inch. 15 | /// 16 | Inch = 2, 17 | 18 | /// 19 | /// The resolution is measured in pixels per centimeter. 20 | /// 21 | Cm = 3 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ImageProcessor/MetadataMode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor 5 | { 6 | /// 7 | /// Enumerates the various metadata modes that control how much metadata information is stored on processing. 8 | /// 9 | public enum MetadataMode 10 | { 11 | /// 12 | /// Store no metadata on processing 13 | /// 14 | All, 15 | 16 | /// 17 | /// Store copyright specific metadata on processing 18 | /// 19 | Copyright, 20 | 21 | /// 22 | /// Store geolocation specific metadata on processing 23 | /// 24 | Geolocation, 25 | 26 | /// 27 | /// Store copyright and geolocation specific metadata on processing 28 | /// 29 | CopyrightAndGeolocation, 30 | 31 | /// 32 | /// Store all metadata on processing 33 | /// 34 | None 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Adjustments.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Threading.Tasks; 7 | 8 | namespace ImageProcessor.Processing 9 | { 10 | /// 11 | /// Provides reusable adjustment methods to apply to images. 12 | /// 13 | public static class Adjustments 14 | { 15 | /// 16 | /// Adjust the gamma (intensity of the light) component of the given image. 17 | /// 18 | /// The source to adjust. 19 | /// The value to adjust the gamma by (typically between .2 and 5). 20 | /// 21 | /// The with the gamma adjusted. 22 | /// 23 | /// 24 | /// Thrown if the value falls outside the acceptable range. 25 | /// 26 | public static Bitmap Gamma(Image source, float value) 27 | { 28 | if (value > 5 || value < .1) 29 | { 30 | throw new ArgumentOutOfRangeException(nameof(value), "Value should be between .1 and 5."); 31 | } 32 | 33 | byte[] ramp = new byte[256]; 34 | for (int x = 0; x < 256; ++x) 35 | { 36 | ramp[x] = ((255 * Math.Pow(x / 255D, value)) + 0.5).ToByte(); 37 | } 38 | 39 | int width = source.Width; 40 | int height = source.Height; 41 | 42 | using (var bitmap = new FastBitmap(source)) 43 | { 44 | Parallel.For( 45 | 0, 46 | height, 47 | y => 48 | { 49 | for (int x = 0; x < width; x++) 50 | { 51 | Color composite = bitmap.GetPixel(x, y); 52 | var linear = Color.FromArgb(composite.A, ramp[composite.R], ramp[composite.G], ramp[composite.B]); 53 | bitmap.SetPixel(x, y, linear); 54 | } 55 | }); 56 | } 57 | 58 | return (Bitmap)source; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Alpha.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | 7 | namespace ImageProcessor.Processing 8 | { 9 | /// 10 | /// Changes the alpha component of the image. 11 | /// 12 | public class Alpha : ColorMatrixRangedProcessor 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The percentage by which to alter the images opacity. Range 0..100. 19 | /// 20 | public Alpha(float percentage) 21 | : base(percentage) 22 | { 23 | } 24 | 25 | /// 26 | public override Image ProcessImageFrame(ImageFactory factory, Image frame) 27 | { 28 | float amount = this.Options / 100; 29 | ColorMatrix colorMatrix = KnownColorMatrices.CreateOpacityFilter(amount); 30 | ApplyMatrix(frame, colorMatrix); 31 | 32 | return frame; 33 | } 34 | 35 | /// 36 | protected override void GuardRange(float amount) 37 | { 38 | if (amount < 0 || amount > 100) 39 | { 40 | throw new ImageProcessingException($"{nameof(amount)} must be in range 0..100"); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/AutoRotate.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | using ImageProcessor.Metadata; 7 | 8 | namespace ImageProcessor.Processing 9 | { 10 | /// 11 | /// Performs auto-rotation to ensure that EXIF defined rotation is reflected in 12 | /// the final image. . 13 | /// 14 | public class AutoRotate : IGraphicsProcessor 15 | { 16 | /// 17 | public Image ProcessImageFrame(ImageFactory factory, Image frame) 18 | { 19 | const int Orientation = (int)ExifPropertyTag.Orientation; 20 | 21 | // Images are always rotated before and after processing if there is an 22 | // orientation key present. By removing the property item we prevent the reverse 23 | // rotation. 24 | if (factory.MetadataMode != MetadataMode.All 25 | && factory.PropertyItems.ContainsKey(Orientation)) 26 | { 27 | factory.PropertyItems.TryRemove(Orientation, out PropertyItem _); 28 | } 29 | 30 | return frame; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/BackgroundColor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | 6 | namespace ImageProcessor.Processing 7 | { 8 | /// 9 | /// Changes the background color of an image. 10 | /// 11 | public class BackgroundColor : IGraphicsProcessor 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The color to set. 17 | public BackgroundColor(Color color) => this.Options = color; 18 | 19 | /// 20 | public Color Options { get; } 21 | 22 | /// 23 | public Image ProcessImageFrame(ImageFactory factory, Image frame) 24 | { 25 | Bitmap result = FormatUtilities.CreateEmptyFrameFrom(frame); 26 | 27 | using (var graphics = Graphics.FromImage(result)) 28 | { 29 | graphics.PageUnit = GraphicsUnit.Pixel; 30 | graphics.Clear(this.Options); 31 | 32 | graphics.DrawImageUnscaled(frame, 0, 0); 33 | } 34 | 35 | frame.Dispose(); 36 | return result; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Brightness.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | 7 | namespace ImageProcessor.Processing 8 | { 9 | /// 10 | /// Changes the brightness component of the image. 11 | /// 12 | public class Brightness : ColorMatrixRangedProcessor 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The percentage by which to alter the images brightness. Range -100..100. 19 | /// 20 | public Brightness(float amount) 21 | : base(amount) 22 | { 23 | } 24 | 25 | /// 26 | public override Image ProcessImageFrame(ImageFactory factory, Image frame) 27 | { 28 | float amount = (this.Options + 100) / 100; 29 | ColorMatrix colorMatrix = KnownColorMatrices.CreateBrightnessFilter(amount); 30 | ApplyMatrix(frame, colorMatrix); 31 | 32 | return frame; 33 | } 34 | 35 | /// 36 | protected override void GuardRange(float amount) 37 | { 38 | if (amount < -100 || amount > 100) 39 | { 40 | throw new ImageProcessingException($"{nameof(amount)} must be in Range -100..100"); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/ColorMatrixProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Drawing2D; 6 | using System.Drawing.Imaging; 7 | 8 | namespace ImageProcessor.Processing 9 | { 10 | /// 11 | /// A base class for processing images via color matrices. 12 | /// 13 | public abstract class ColorMatrixProcessor : IGraphicsProcessor 14 | { 15 | /// 16 | public abstract Image ProcessImageFrame(ImageFactory factory, Image frame); 17 | 18 | /// 19 | /// Performs the application of the color matrix upon the image. 20 | /// 21 | /// The image frame. 22 | /// The color matrix. 23 | protected static void ApplyMatrix(Image frame, ColorMatrix colorMatrix) 24 | { 25 | using (var graphics = Graphics.FromImage(frame)) 26 | { 27 | using (var imageAttributes = new ImageAttributes()) 28 | { 29 | imageAttributes.SetColorMatrix( 30 | colorMatrix, 31 | ColorMatrixFlag.Default, 32 | ColorAdjustType.Bitmap); 33 | 34 | graphics.CompositingMode = CompositingMode.SourceCopy; 35 | graphics.DrawImage( 36 | frame, 37 | new Rectangle(0, 0, frame.Width, frame.Height), 38 | 0, 39 | 0, 40 | frame.Width, 41 | frame.Height, 42 | GraphicsUnit.Pixel, 43 | imageAttributes); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/ColorMatrixRangedProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// A base class for processing images via color matrices. 8 | /// 9 | public abstract class ColorMatrixRangedProcessor : ColorMatrixProcessor, IGraphicsProcessor 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The amount by which to adjust the matrix. 15 | protected ColorMatrixRangedProcessor(float amount) 16 | { 17 | this.GuardRange(amount); 18 | this.Options = amount; 19 | } 20 | 21 | /// 22 | public float Options { get; } 23 | 24 | /// 25 | /// A method for protecting input thresholds. 26 | /// 27 | /// The amount to check. 28 | /// 29 | /// Thrown if the range is outith the acceptable threshold. 30 | /// 31 | protected virtual void GuardRange(float amount) 32 | { 33 | // Default to no-op. 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Contrast.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | 7 | namespace ImageProcessor.Processing 8 | { 9 | /// 10 | /// Changes the contrast component of the image. 11 | /// 12 | public class Contrast : ColorMatrixRangedProcessor 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The percentage by which to alter the images contrast. Range -100..100. 19 | /// 20 | public Contrast(float amount) 21 | : base(amount) 22 | { 23 | } 24 | 25 | /// 26 | public override Image ProcessImageFrame(ImageFactory factory, Image frame) 27 | { 28 | float amount = (this.Options + 100) / 100; 29 | ColorMatrix colorMatrix = KnownColorMatrices.CreateContrastFilter(amount); 30 | ApplyMatrix(frame, colorMatrix); 31 | 32 | return frame; 33 | } 34 | 35 | /// 36 | protected override void GuardRange(float amount) 37 | { 38 | if (amount < -100 || amount > 100) 39 | { 40 | throw new ImageProcessingException($"{nameof(amount)} must be in Range -100..100"); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/EdgeDetection2DProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | 6 | namespace ImageProcessor.Processing 7 | { 8 | /// 9 | /// A base class for processing images using edge detection. 10 | /// 11 | public abstract class EdgeDetection2DProcessor : IGraphicsProcessor, IGraphicsProcessor 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The horizontal and vertical kernel operators. 17 | /// Whether to convert the image to grascale before processing. 18 | protected EdgeDetection2DProcessor(KernelPair kernels, bool grayscale) 19 | { 20 | this.Kernels = kernels; 21 | this.Grayscale = grayscale; 22 | } 23 | 24 | /// 25 | /// Gets the kernel operator pair. 26 | /// 27 | public KernelPair Kernels { get; } 28 | 29 | /// 30 | /// Gets a value indicating whether to convert the image to grascale before processing. 31 | /// 32 | public bool Grayscale { get; } 33 | 34 | /// 35 | KernelPair IGraphicsProcessor.Options 36 | { 37 | get { return this.Kernels; } 38 | } 39 | 40 | /// 41 | bool IGraphicsProcessor.Options 42 | { 43 | get { return this.Grayscale; } 44 | } 45 | 46 | /// 47 | public Image ProcessImageFrame(ImageFactory factory, Image frame) 48 | { 49 | if (this.Grayscale) 50 | { 51 | // Grayscale is not destructive and will return the same image. 52 | frame = new Grayscale(100F).ProcessImageFrame(factory, frame); 53 | } 54 | 55 | // Convolution is destructive and will create a new image. 56 | Image result = new Convolution2DProcessor(this.Kernels).ProcessImageFrame(factory, frame); 57 | 58 | frame.Dispose(); 59 | return result; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/EdgeDetectionOperators.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Enumerates the various types of defined edge detection filters. 8 | /// 9 | public enum EdgeDetectionOperators 10 | { 11 | /// 12 | /// The Kayyali operator filter. 13 | /// 14 | Kayyali, 15 | 16 | /// 17 | /// The Laplacian3X3 operator filter. 18 | /// 19 | Laplacian3x3, 20 | 21 | /// 22 | /// The Laplacian5X5 operator filter. 23 | /// 24 | Laplacian5x5, 25 | 26 | /// 27 | /// The LaplacianOfGaussian operator filter. 28 | /// 29 | LaplacianOfGaussian, 30 | 31 | /// 32 | /// The Prewitt operator filter. 33 | /// 34 | Prewitt, 35 | 36 | /// 37 | /// The RobertsCross operator filter. 38 | /// 39 | RobertsCross, 40 | 41 | /// 42 | /// The Scharr operator filter. 43 | /// 44 | Scharr, 45 | 46 | /// 47 | /// The Sobel operator filter. 48 | /// 49 | Sobel 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/EdgeDetectionProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | 6 | namespace ImageProcessor.Processing 7 | { 8 | /// 9 | /// A base class for processing images using edge detection. 10 | /// 11 | public abstract class EdgeDetectionProcessor : IGraphicsProcessor, IGraphicsProcessor 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The kernel operator. 17 | /// Whether to convert the image to grascale before processing. 18 | protected EdgeDetectionProcessor(double[,] kernel, bool grayscale) 19 | { 20 | this.Kernel = kernel; 21 | this.Grayscale = grayscale; 22 | } 23 | 24 | /// 25 | /// Gets the kernel operator. 26 | /// 27 | public double[,] Kernel { get; } 28 | 29 | /// 30 | /// Gets a value indicating whether to convert the image to grascale before processing. 31 | /// 32 | public bool Grayscale { get; } 33 | 34 | /// 35 | double[,] IGraphicsProcessor.Options 36 | { 37 | get { return this.Kernel; } 38 | } 39 | 40 | /// 41 | bool IGraphicsProcessor.Options 42 | { 43 | get { return this.Grayscale; } 44 | } 45 | 46 | /// 47 | public Image ProcessImageFrame(ImageFactory factory, Image frame) 48 | { 49 | if (this.Grayscale) 50 | { 51 | // Grayscale is not destructive and will return the same image. 52 | frame = new Grayscale(100F).ProcessImageFrame(factory, frame); 53 | } 54 | 55 | // Convolution is destructive and will create a new image. 56 | Image result = new ConvolutionProcessor(this.Kernel).ProcessImageFrame(factory, frame); 57 | 58 | frame.Dispose(); 59 | return result; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/Kayyali.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Detects edges within an image using Kayyali operators. 8 | /// . 9 | /// 10 | public class Kayyali : EdgeDetection2DProcessor 11 | { 12 | private static readonly double[,] KernelX = new double[,] 13 | { 14 | { 6, 0, -6 }, 15 | { 0, 0, 0 }, 16 | { -6, 0, 6 } 17 | }; 18 | 19 | private static readonly double[,] KernelY = new double[,] 20 | { 21 | { -6, 0, 6 }, 22 | { 0, 0, 0 }, 23 | { 6, 0, -6 } 24 | }; 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// Whether to convert the image to grascale before processing. 30 | public Kayyali(bool grayscale) 31 | : base(new KernelPair(KernelX, KernelY), grayscale) 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/Laplacian3x3.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Detects edges within an image using Laplacian 3x3 operators. 8 | /// . 9 | /// 10 | public class Laplacian3x3 : EdgeDetectionProcessor 11 | { 12 | private static readonly double[,] KernelXY = new double[,] 13 | { 14 | { -1, -1, -1 }, 15 | { -1, 8, -1 }, 16 | { -1, -1, -1 } 17 | }; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// Whether to convert the image to grascale before processing. 23 | public Laplacian3x3(bool grayscale) 24 | : base(KernelXY, grayscale) 25 | { 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/Laplacian5x5.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Detects edges within an image using Laplacian 5x5 operators. 8 | /// . 9 | /// 10 | public class Laplacian5x5 : EdgeDetectionProcessor 11 | { 12 | private static readonly double[,] KernelXY = new double[,] 13 | { 14 | { -1, -1, -1, -1, -1 }, 15 | { -1, -1, -1, -1, -1 }, 16 | { -1, -1, 24, -1, -1 }, 17 | { -1, -1, -1, -1, -1 }, 18 | { -1, -1, -1, -1, -1 } 19 | }; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// Whether to convert the image to grascale before processing. 25 | public Laplacian5x5(bool grayscale) 26 | : base(KernelXY, grayscale) 27 | { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/LaplacianOfGaussian.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Detects edges within an image using Laplacian of Gaussian operators. 8 | /// . 9 | /// 10 | public class LaplacianOfGaussian : EdgeDetectionProcessor 11 | { 12 | private static readonly double[,] KernelXY = new double[,] 13 | { 14 | { 0, 0, -1, 0, 0 }, 15 | { 0, -1, -2, -1, 0 }, 16 | { -1, -2, 16, -2, -1 }, 17 | { 0, -1, -2, -1, 0 }, 18 | { 0, 0, -1, 0, 0 } 19 | }; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// Whether to convert the image to grascale before processing. 25 | public LaplacianOfGaussian(bool grayscale) 26 | : base(KernelXY, grayscale) 27 | { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/Prewitt.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Detects edges within an image using Prewitt operators. 8 | /// . 9 | /// 10 | public class Prewitt : EdgeDetection2DProcessor 11 | { 12 | private static readonly double[,] KernelX = new double[,] 13 | { 14 | { -1, 0, 1 }, 15 | { -1, 0, 1 }, 16 | { -1, 0, 1 } 17 | }; 18 | 19 | private static readonly double[,] KernelY = new double[,] 20 | { 21 | { 1, 1, 1 }, 22 | { 0, 0, 0 }, 23 | { -1, -1, -1 } 24 | }; 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// Whether to convert the image to grascale before processing. 30 | public Prewitt(bool grayscale) 31 | : base(new KernelPair(KernelX, KernelY), grayscale) 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/RobertsCross.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Detects edges within an image using RobertsCross operators. 8 | /// . 9 | /// 10 | public class RobertsCross : EdgeDetection2DProcessor 11 | { 12 | private static readonly double[,] KernelX = new double[,] 13 | { 14 | { 1, 0 }, 15 | { 0, -1 } 16 | }; 17 | 18 | private static readonly double[,] KernelY = new double[,] 19 | { 20 | { 0, 1 }, 21 | { -1, 0 } 22 | }; 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// Whether to convert the image to grascale before processing. 28 | public RobertsCross(bool grayscale) 29 | : base(new KernelPair(KernelX, KernelY), grayscale) 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/Scharr.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Detects edges within an image using Scharr operators. 8 | /// . 9 | /// 10 | public class Scharr : EdgeDetection2DProcessor 11 | { 12 | private static readonly double[,] KernelX = new double[,] 13 | { 14 | { -3, 0, 3 }, 15 | { -10, 0, 10 }, 16 | { -3, 0, 3 } 17 | }; 18 | 19 | private static readonly double[,] KernelY = new double[,] 20 | { 21 | { 3, 10, 3 }, 22 | { 0, 0, 0 }, 23 | { -3, -10, -3 } 24 | }; 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// Whether to convert the image to grascale before processing. 30 | public Scharr(bool grayscale) 31 | : base(new KernelPair(KernelX, KernelY), grayscale) 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Convolution/Sobel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Processing 5 | { 6 | /// 7 | /// Detects edges within an image using Sobel operators. 8 | /// . 9 | /// 10 | public class Sobel : EdgeDetection2DProcessor 11 | { 12 | private static readonly double[,] KernelX = new double[,] 13 | { 14 | { -1, 0, 1 }, 15 | { -2, 0, 2 }, 16 | { -1, 0, 1 } 17 | }; 18 | 19 | private static readonly double[,] KernelY = new double[,] 20 | { 21 | { 1, 2, 1 }, 22 | { 0, 0, 0 }, 23 | { -1, -2, -1 } 24 | }; 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// Whether to convert the image to grascale before processing. 30 | public Sobel(bool grayscale) 31 | : base(new KernelPair(KernelX, KernelY), grayscale) 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Grayscale.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | 7 | namespace ImageProcessor.Processing 8 | { 9 | /// 10 | /// Changes the grayscale component of the image using the formula as specified by ITU-R Recommendation BT.601. 11 | /// . 12 | /// 13 | public class Grayscale : ColorMatrixRangedProcessor 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// 19 | /// The percentage by which to alter the images opacity. Range 0..100. 20 | /// 21 | public Grayscale(float percentage) 22 | : base(percentage) 23 | { 24 | } 25 | 26 | /// 27 | public override Image ProcessImageFrame(ImageFactory factory, Image frame) 28 | { 29 | float amount = this.Options / 100; 30 | ColorMatrix colorMatrix = KnownColorMatrices.CreateGrayscaleFilter(amount); 31 | ApplyMatrix(frame, colorMatrix); 32 | 33 | return frame; 34 | } 35 | 36 | /// 37 | protected override void GuardRange(float amount) 38 | { 39 | if (amount < 0 || amount > 100) 40 | { 41 | throw new ImageProcessingException($"{nameof(amount)} must be in range 0..100"); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Hue.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | 7 | namespace ImageProcessor.Processing 8 | { 9 | /// 10 | /// Changes the hue component of the image. 11 | /// 12 | public class Hue : ColorMatrixRangedProcessor 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The rotation angle in degrees to adjust the hue. 18 | public Hue(float degrees) 19 | : base(degrees) 20 | { 21 | } 22 | 23 | /// 24 | public override Image ProcessImageFrame(ImageFactory factory, Image frame) 25 | { 26 | ColorMatrix colorMatrix = KnownColorMatrices.CreateHueFilter(this.Options); 27 | ApplyMatrix(frame, colorMatrix); 28 | 29 | return frame; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/IGraphicsProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | 6 | namespace ImageProcessor.Processing 7 | { 8 | /// 9 | /// Defines the contract for graphics processors. 10 | /// 11 | public interface IGraphicsProcessor 12 | { 13 | /// 14 | /// Returns a new image frame from the source with the process applied. 15 | /// 16 | /// The class. 17 | /// The source image frame. 18 | /// The . 19 | Image ProcessImageFrame(ImageFactory factory, Image frame); 20 | } 21 | 22 | /// 23 | /// Defines the contract for graphics processors with options. 24 | /// 25 | /// The type of options. 26 | public interface IGraphicsProcessor : IGraphicsProcessor 27 | { 28 | /// 29 | /// Gets the options. 30 | /// 31 | T Options { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Pixelate.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Drawing; 7 | using System.Threading.Tasks; 8 | 9 | namespace ImageProcessor.Processing 10 | { 11 | /// 12 | /// Pixelates an image. 13 | /// 14 | public class Pixelate : IGraphicsProcessor 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The pixelate options. 20 | public Pixelate(PixelateOptions options) => this.Options = options; 21 | 22 | /// 23 | public PixelateOptions Options { get; } 24 | 25 | /// 26 | public Image ProcessImageFrame(ImageFactory factory, Image frame) 27 | { 28 | int size = this.Options.Size; 29 | var rectangle = Rectangle.Intersect(this.Options.Rectangle, new Rectangle(Point.Empty, frame.Size)); 30 | int startX = rectangle.X; 31 | int startY = rectangle.Y; 32 | int offset = size / 2; 33 | int maxX = rectangle.Right; 34 | int maxY = rectangle.Bottom; 35 | 36 | // Align start/end positions. 37 | int minX = Math.Max(0, startX); 38 | int minY = Math.Max(0, startY); 39 | 40 | // Reset offset if necessary. 41 | if (minX > 0) 42 | { 43 | startX = 0; 44 | } 45 | 46 | if (minY > 0) 47 | { 48 | startY = 0; 49 | } 50 | 51 | using (var fastBitmap = new FastBitmap(frame)) 52 | { 53 | // Get the range on the y-plane to choose from. 54 | IEnumerable range = EnumerableUtilities.SteppedRange(minY, maxY, size); 55 | 56 | Parallel.ForEach( 57 | range, 58 | y => 59 | { 60 | int offsetY = y - startY; 61 | int offsetPy = offset; 62 | 63 | // Make sure that the offset is within the boundary of the image. 64 | while (offsetY + offsetPy >= maxY) 65 | { 66 | offsetPy--; 67 | } 68 | 69 | for (int x = minX; x < maxX; x += size) 70 | { 71 | int offsetX = x - startX; 72 | int offsetPx = offset; 73 | 74 | while (x + offsetPx >= maxX) 75 | { 76 | offsetPx--; 77 | } 78 | 79 | // Get the pixel color in the centre of the soon to be pixelated area. 80 | Color pixel = fastBitmap.GetPixel(offsetX + offsetPx, offsetY + offsetPy); 81 | 82 | // For each pixel in the pixelate size, set it to the centre color. 83 | for (int l = offsetY; l < offsetY + size && l < maxY; l++) 84 | { 85 | for (int k = offsetX; k < offsetX + size && k < maxX; k++) 86 | { 87 | fastBitmap.SetPixel(k, l, pixel); 88 | } 89 | } 90 | } 91 | }); 92 | } 93 | 94 | return frame; 95 | } 96 | } 97 | 98 | /// 99 | /// The pixelate options for pixelating images. 100 | /// 101 | public class PixelateOptions 102 | { 103 | /// 104 | /// Initializes a new instance of the class. 105 | /// 106 | /// The pixel size. 107 | /// The bounds within which to pixelate. 108 | public PixelateOptions(int size, Rectangle rectangle) 109 | { 110 | if (size < 1) 111 | { 112 | throw new ImageProcessingException($"{nameof(size)} must be >= 1."); 113 | } 114 | 115 | if (rectangle == default) 116 | { 117 | throw new ImageProcessingException($"{nameof(rectangle)} must have value"); 118 | } 119 | 120 | this.Size = size; 121 | this.Rectangle = rectangle; 122 | } 123 | 124 | /// 125 | /// Gets the size of the pixels. 126 | /// 127 | public int Size { get; } 128 | 129 | /// 130 | /// Gets the rectangle bounds within which to pixelate. 131 | /// 132 | public Rectangle Rectangle { get; } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/ImageProcessor/Processing/Saturation.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | 7 | namespace ImageProcessor.Processing 8 | { 9 | /// 10 | /// Changes the saturation component of the image. 11 | /// 12 | public class Saturation : ColorMatrixRangedProcessor 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The percentage by which to alter the images saturation. Range -100..100. 19 | /// 20 | public Saturation(float amount) 21 | : base(amount) 22 | { 23 | } 24 | 25 | /// 26 | public override Image ProcessImageFrame(ImageFactory factory, Image frame) 27 | { 28 | float amount = (this.Options + 100) / 100; 29 | ColorMatrix colorMatrix = KnownColorMatrices.CreateSaturationFilter(amount); 30 | ApplyMatrix(frame, colorMatrix); 31 | 32 | return frame; 33 | } 34 | 35 | /// 36 | protected override void GuardRange(float amount) 37 | { 38 | if (amount < -100 || amount > 100) 39 | { 40 | throw new ImageProcessingException($"{nameof(amount)} {amount} must be in Range -100..100"); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ImageProcessor/Quantizers/IQuantizer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | 6 | namespace ImageProcessor.Quantizers 7 | { 8 | /// 9 | /// Defines the contract for allowing quantization of images. 10 | /// 11 | public interface IQuantizer 12 | { 13 | /// 14 | /// Quantize an image and returns the resulting output bitmap. 15 | /// TODO: This should copy metadata. 16 | /// 17 | /// The image to quantize. 18 | /// . 19 | Bitmap Quantize(Image source); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ImageProcessor/Quantizers/WuQuantizer/Box.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Quantizers 5 | { 6 | /// 7 | /// The box for storing color attributes. 8 | /// Adapted from . 9 | /// 10 | public struct Box 11 | { 12 | /// 13 | /// The alpha maximum. 14 | /// 15 | public byte AlphaMaximum; 16 | 17 | /// 18 | /// The alpha minimum. 19 | /// 20 | public byte AlphaMinimum; 21 | 22 | /// 23 | /// The blue maximum. 24 | /// 25 | public byte BlueMaximum; 26 | 27 | /// 28 | /// The blue minimum. 29 | /// 30 | public byte BlueMinimum; 31 | 32 | /// 33 | /// The green maximum. 34 | /// 35 | public byte GreenMaximum; 36 | 37 | /// 38 | /// The green minimum. 39 | /// 40 | public byte GreenMinimum; 41 | 42 | /// 43 | /// The red maximum. 44 | /// 45 | public byte RedMaximum; 46 | 47 | /// 48 | /// The red minimum. 49 | /// 50 | public byte RedMinimum; 51 | 52 | /// 53 | /// The size. 54 | /// 55 | public int Size; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ImageProcessor/Quantizers/WuQuantizer/CubeCut.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ImageProcessor.Quantizers 5 | { 6 | /// 7 | /// Represents a cube cut. 8 | /// Adapted from . 9 | /// 10 | internal readonly struct CubeCut 11 | { 12 | /// 13 | /// The position. 14 | /// 15 | public readonly byte? Position; 16 | 17 | /// 18 | /// The value. 19 | /// 20 | public readonly float Value; 21 | 22 | /// 23 | /// Initializes a new instance of the struct. 24 | /// 25 | /// The cut point. 26 | /// The result. 27 | public CubeCut(byte? cutPoint, float result) 28 | { 29 | this.Position = cutPoint; 30 | this.Value = result; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ImageProcessor/Quantizers/WuQuantizer/Histogram.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | 6 | namespace ImageProcessor.Quantizers 7 | { 8 | /// 9 | /// The histogram representing the distribution of color data. 10 | /// Adapted from . 11 | /// 12 | internal class Histogram 13 | { 14 | /// 15 | /// The side size. 16 | /// 17 | private const int SideSize = 33; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | public Histogram() 23 | { 24 | // 47,436,840 bytes 25 | this.Moments = new ColorMoment[SideSize, SideSize, SideSize, SideSize]; 26 | } 27 | 28 | /// 29 | /// Gets the collection of moments. 30 | /// 31 | public ColorMoment[,,,] Moments { get; } 32 | 33 | /// 34 | /// Clears the histogram. 35 | /// 36 | internal void Clear() => Array.Clear(this.Moments, 0, SideSize * SideSize * SideSize * SideSize); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ImageProcessor/Quantizers/WuQuantizer/IWuQuantizer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | 6 | namespace ImageProcessor.Quantizers 7 | { 8 | /// 9 | /// Encapsulates methods to calculate the color palette of an image using 10 | /// a Wu color quantizer . 11 | /// Adapted from . 12 | /// 13 | public interface IWuQuantizer : IQuantizer 14 | { 15 | /// 16 | /// Quantizes the given image. 17 | /// 18 | /// The 32 bit per pixel . 19 | /// 20 | /// The alpha threshold. All colors with an alpha value less than this will be 21 | /// considered fully transparent. 22 | /// 23 | /// 24 | /// The alpha fader. Alpha values will be normalized to the nearest multiple of this value. 25 | /// 26 | /// 27 | /// The quantized . 28 | /// 29 | Bitmap Quantize(Image image, int alphaThreshold, int alphaFader); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ImageProcessor/Quantizers/WuQuantizer/ImageBuffer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Drawing; 7 | using System.Drawing.Imaging; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace ImageProcessor.Quantizers 11 | { 12 | /// 13 | /// The image buffer for storing and manipulating pixel information. 14 | /// Adapted from . 15 | /// 16 | internal class ImageBuffer 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The image to store. 22 | public ImageBuffer(Bitmap image) => this.Image = image; 23 | 24 | /// 25 | /// Gets the image. 26 | /// 27 | public Bitmap Image { get; } 28 | 29 | /// 30 | /// Gets the enumerable pixel array representing each row of pixels. 31 | /// 32 | /// 33 | /// Thrown if the given image is not a 32 bit per pixel image. 34 | /// 35 | public IEnumerable PixelLines 36 | { 37 | get 38 | { 39 | int width = this.Image.Width; 40 | int height = this.Image.Height; 41 | var pixels = new Bgra32[width]; 42 | 43 | using (var bitmap = new FastBitmap(this.Image)) 44 | { 45 | for (int y = 0; y < height; y++) 46 | { 47 | for (int x = 0; x < width; x++) 48 | { 49 | Color color = bitmap.GetPixel(x, y); 50 | pixels[x] = new Bgra32(color.A, color.R, color.G, color.B); 51 | } 52 | 53 | yield return pixels; 54 | } 55 | } 56 | } 57 | } 58 | 59 | /// 60 | /// Updates the pixel indexes. 61 | /// 62 | /// 63 | /// The enumerable byte array representing each row of pixels. 64 | /// 65 | public void UpdatePixelIndexes(IEnumerable lineIndexes) 66 | { 67 | int width = this.Image.Width; 68 | int height = this.Image.Height; 69 | int rowIndex = 0; 70 | 71 | BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); 72 | try 73 | { 74 | IntPtr pixelBase = data.Scan0; 75 | int scanWidth = data.Stride; 76 | foreach (byte[] scanLine in lineIndexes) 77 | { 78 | Marshal.Copy(scanLine, 0, IntPtr.Add(pixelBase, scanWidth * rowIndex), width); 79 | 80 | if (++rowIndex >= height) 81 | { 82 | break; 83 | } 84 | } 85 | } 86 | finally 87 | { 88 | this.Image.UnlockBits(data); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/ImageProcessor/Quantizers/WuQuantizer/PaletteColorHistory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) James Jackson-South and contributors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Drawing; 5 | 6 | namespace ImageProcessor.Quantizers 7 | { 8 | /// 9 | /// The palette color history containing the sum of all pixel data. 10 | /// Adapted from . 11 | /// 12 | internal struct PaletteColorHistory 13 | { 14 | /// 15 | /// The alpha component. 16 | /// 17 | public ulong Alpha; 18 | 19 | /// 20 | /// The red component. 21 | /// 22 | public ulong Red; 23 | 24 | /// 25 | /// The green component. 26 | /// 27 | public ulong Green; 28 | 29 | /// 30 | /// The blue component. 31 | /// 32 | public ulong Blue; 33 | 34 | /// 35 | /// The sum of the color components. 36 | /// 37 | public ulong Sum; 38 | 39 | /// 40 | /// Normalizes the color. 41 | /// 42 | /// 43 | /// The normalized . 44 | /// 45 | public Color ToNormalizedColor() => (this.Sum != 0) ? Color.FromArgb((int)(this.Alpha /= this.Sum), (int)(this.Red /= this.Sum), (int)(this.Green /= this.Sum), (int)(this.Blue /= this.Sum)) : Color.Empty; 46 | 47 | /// 48 | /// Adds a pixel to the color history. 49 | /// 50 | /// 51 | /// The pixel to add. 52 | /// 53 | public void AddPixel(Bgra32 pixel) 54 | { 55 | this.Alpha += pixel.A; 56 | this.Red += pixel.R; 57 | this.Green += pixel.G; 58 | this.Blue += pixel.B; 59 | this.Sum++; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "orderingRules": { 5 | "usingDirectivesPlacement": "outsideNamespace", 6 | "elementOrder": [ 7 | "kind" 8 | ] 9 | }, 10 | "documentationRules": { 11 | "xmlHeader": false, 12 | "documentInternalElements": false, 13 | "copyrightText": "Copyright (c) James Jackson-South and contributors.\nLicensed under the Apache License, Version 2.0." 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/FastBitmapTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using Xunit; 5 | 6 | namespace ImageProcessor.Tests 7 | { 8 | public class FastBitmapTests 9 | { 10 | [Theory] 11 | [InlineData(PixelFormat.Format1bppIndexed)] 12 | [InlineData(PixelFormat.Format4bppIndexed)] 13 | [InlineData(PixelFormat.Format8bppIndexed)] 14 | public void FastBitmapConstructorChecksInput(PixelFormat format) 15 | { 16 | using (var image = new Bitmap(1, 1, format)) 17 | { 18 | Assert.Throws(() => 19 | { 20 | using (var fast = new FastBitmap(image)) 21 | { 22 | } 23 | }); 24 | } 25 | } 26 | 27 | [Theory] 28 | [InlineData(PixelFormat.Format16bppRgb565)] 29 | [InlineData(PixelFormat.Format24bppRgb)] 30 | [InlineData(PixelFormat.Format32bppRgb)] 31 | [InlineData(PixelFormat.Format32bppArgb)] 32 | [InlineData(PixelFormat.Format32bppPArgb)] 33 | [InlineData(PixelFormat.Format48bppRgb)] 34 | public void FastBitmapCanManipulatePixels(PixelFormat format) 35 | { 36 | using (var image = new Bitmap(5, 5, format)) 37 | { 38 | var expected = Color.FromArgb(16, 32, 64, 128); 39 | using (var fast = new FastBitmap(image)) 40 | { 41 | for (int y = 0; y < fast.Height; y++) 42 | { 43 | for (int x = 0; x < fast.Width; x++) 44 | { 45 | fast.SetPixel(x, y, expected); 46 | Color actual = fast.GetPixel(x, y); 47 | 48 | Assert.Equal(expected.ToArgb(), actual.ToArgb()); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/ImageDeepCopyTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ImageProcessor.Tests 8 | { 9 | public class ImageDeepCopyTests 10 | { 11 | public void CanDeepCopyImage() 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/ImageExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Xunit; 3 | 4 | namespace ImageProcessor.Tests 5 | { 6 | public class ImageExtensionTests 7 | { 8 | [Fact] 9 | public void CanDeepCloneRawImage() 10 | { 11 | using (var image = new Bitmap(10, 10)) 12 | { 13 | Color expected = Color.HotPink; 14 | image.SetPixel(0, 0, expected); 15 | 16 | using (Bitmap copy = image.DeepClone()) 17 | { 18 | Assert.True(image != copy); 19 | 20 | Color pixel = copy.GetPixel(0, 0); 21 | Assert.Equal(expected.ToArgb(), pixel.ToArgb()); 22 | } 23 | } 24 | } 25 | 26 | // TODO: Per format tests. 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/ImageFactoryEncodingTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ImageProcessor.Configuration; 3 | using ImageProcessor.Formats; 4 | using Xunit; 5 | 6 | namespace ImageProcessor.Tests 7 | { 8 | public class ImageFactoryEncodingTests 9 | { 10 | public ImageFactoryEncodingTests() 11 | { 12 | ImageProcessorBootstrapper.Instance.AddImageFormats(new WebPFormat()); 13 | } 14 | 15 | private const string Category = "Encoding"; 16 | 17 | public static IEnumerable BitDepths = new[] 18 | { 19 | new object[]{ BitDepth.Bit1 }, 20 | new object[]{ BitDepth.Bit4 }, 21 | new object[]{ BitDepth.Bit8 }, 22 | new object[]{ BitDepth.Bit16 }, 23 | new object[]{ BitDepth.Bit24 }, 24 | new object[]{ BitDepth.Bit32 } 25 | }; 26 | 27 | [Theory] 28 | [MemberData(nameof(BitDepths))] 29 | public void CanEncodeJpegAllBitDepths(BitDepth bitDepth) 30 | { 31 | using (var factory = new ImageFactory()) 32 | { 33 | TestFile file = TestFiles.Jpeg.Penguins; 34 | 35 | factory.Load(file.FullName).SaveAndCompare(file, Category, bitDepth); 36 | } 37 | } 38 | 39 | [Theory] 40 | [MemberData(nameof(BitDepths))] 41 | public void CanEncodePngAllBitDepths(BitDepth bitDepth) 42 | { 43 | using (var factory = new ImageFactory()) 44 | { 45 | TestFile file = TestFiles.Png.Penguins; 46 | 47 | factory.Load(file.FullName).SaveAndCompare(file, Category, bitDepth); 48 | } 49 | } 50 | 51 | [Theory] 52 | [MemberData(nameof(BitDepths))] 53 | public void CanEncodeBmpAllBitDepths(BitDepth bitDepth) 54 | { 55 | using (var factory = new ImageFactory()) 56 | { 57 | TestFile file = TestFiles.Bmp.Penguins; 58 | 59 | factory.Load(file.FullName).SaveAndCompare(file, Category, bitDepth); 60 | } 61 | } 62 | 63 | [Theory] 64 | [MemberData(nameof(BitDepths))] 65 | public void CanEncodeGifAllBitDepths(BitDepth bitDepth) 66 | { 67 | using (var factory = new ImageFactory()) 68 | { 69 | TestFile file = TestFiles.Gif.Penguins; 70 | 71 | factory.Load(file.FullName).SaveAndCompare(file, Category, bitDepth); 72 | } 73 | } 74 | 75 | [Theory] 76 | [MemberData(nameof(BitDepths))] 77 | public void CanEncodeTiffAllBitDepths(BitDepth bitDepth) 78 | { 79 | using (var factory = new ImageFactory()) 80 | { 81 | TestFile file = TestFiles.Tiff.Penguins; 82 | 83 | factory.Load(file.FullName).SaveAndCompare(file, Category, bitDepth); 84 | } 85 | } 86 | 87 | [Theory] 88 | [MemberData(nameof(BitDepths))] 89 | public void CanEncodeWebPAllBitDepths(BitDepth bitDepth) 90 | { 91 | using (var factory = new ImageFactory()) 92 | { 93 | TestFile file = TestFiles.WebP.Penguins; 94 | 95 | factory.Load(file.FullName).SaveAndCompare(file, Category, bitDepth); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/ImageFactoryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using System.IO; 4 | using ImageProcessor.Formats; 5 | 6 | namespace ImageProcessor.Tests 7 | { 8 | public static class ImageFactoryExtensions 9 | { 10 | public static ImageFactory SaveAndCompare( 11 | this ImageFactory factory, 12 | TestFile testFile, 13 | string category, 14 | params object[] additionalData) 15 | { 16 | string filename = Path.GetFileNameWithoutExtension(testFile.Name); 17 | 18 | BitDepth bitDepth = default; 19 | bool hasBitDepth = false; 20 | if (additionalData.Length > 0) 21 | { 22 | if (additionalData[0] is BitDepth depth) 23 | { 24 | hasBitDepth = true; 25 | bitDepth = depth; 26 | } 27 | 28 | filename += "_" + string.Join("_", additionalData); 29 | } 30 | 31 | filename += testFile.Extension; 32 | 33 | string expectedPath = Path.GetFullPath(Path.Combine(testFile.ExpectedRoot, category, filename)); 34 | string actualPath = Path.GetFullPath(Path.Combine(testFile.ActualRoot, category, filename)); 35 | 36 | // TODO: Remove expected saving once we have our images. 37 | if (hasBitDepth) 38 | { 39 | factory.Save(expectedPath, bitDepth); 40 | factory.Save(actualPath, bitDepth); 41 | } 42 | else 43 | { 44 | factory.Save(expectedPath); 45 | factory.Save(actualPath); 46 | } 47 | 48 | Bitmap expectedClone = null; 49 | Bitmap actualClone = null; 50 | FastBitmap expectedFast = null; 51 | FastBitmap actualFast = null; 52 | try 53 | { 54 | using (var expectedFactory = new ImageFactory()) 55 | using (var actualFactory = new ImageFactory()) 56 | { 57 | expectedFactory.Load(expectedPath); 58 | actualFactory.Load(actualPath); 59 | 60 | Image expectedImage = expectedFactory.Image; 61 | Image actualImage = actualFactory.Image; 62 | 63 | if (expectedImage.Size != actualImage.Size) 64 | { 65 | throw new ImagesSimilarityException("Images are not the same size!"); 66 | } 67 | 68 | // Copy and dispose of originals to allow fast comparison. 69 | // TODO: We only compare the first frame. Consider comparing each one. 70 | expectedClone = FormatUtilities.DeepCloneImageFrame(expectedImage, PixelFormat.Format32bppArgb); 71 | actualClone = FormatUtilities.DeepCloneImageFrame(actualImage, PixelFormat.Format32bppArgb); 72 | } 73 | 74 | expectedFast = new FastBitmap(expectedClone); 75 | actualFast = new FastBitmap(actualClone); 76 | 77 | for (int y = 0; y < expectedFast.Height; y++) 78 | { 79 | for (int x = 0; x < expectedFast.Width; x++) 80 | { 81 | Color expected = expectedFast.GetPixel(x, y); 82 | Color actual = actualFast.GetPixel(x, y); 83 | 84 | if (expected.ToArgb() != actual.ToArgb()) 85 | { 86 | throw new ImagesSimilarityException($"Images at {x}, {y} are different. {expected} : {actual}!"); 87 | } 88 | } 89 | } 90 | } 91 | finally 92 | { 93 | expectedFast?.Dispose(); 94 | actualFast?.Dispose(); 95 | expectedClone?.Dispose(); 96 | actualClone?.Dispose(); 97 | } 98 | 99 | return factory; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net452 5 | 7.3 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/ImagesSimilarityException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ImageProcessor.Tests 4 | { 5 | public class ImagesSimilarityException : Exception 6 | { 7 | public ImagesSimilarityException(string message) 8 | : base(message) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/AlphaTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ImageProcessor.Processing; 3 | using Xunit; 4 | 5 | namespace ImageProcessor.Tests.Processing 6 | { 7 | public class AlphaTests 8 | { 9 | private const string Category = "Alpha"; 10 | 11 | public static IEnumerable AlphaFiles = new[] 12 | { 13 | new object[]{ TestFiles.Jpeg.Penguins, 25 }, 14 | new object[]{ TestFiles.Png.Penguins, 25 }, 15 | new object[]{ TestFiles.Png.Penguins, 75 } 16 | }; 17 | 18 | [Fact] 19 | public void AlphaConstructorSetsOptions() 20 | { 21 | const int Expected = 50; 22 | var processor = new Alpha(Expected); 23 | 24 | Assert.Equal(Expected, processor.Options); 25 | } 26 | 27 | [Theory] 28 | [InlineData(-1)] 29 | [InlineData(101)] 30 | public void AlphaConstructorChecksInput(int percentage) 31 | { 32 | Assert.Throws(() => new Alpha(percentage)); 33 | } 34 | 35 | [Theory] 36 | [MemberData(nameof(AlphaFiles))] 37 | public void FactoryCanSetAlpha(TestFile file, int percentage) 38 | { 39 | using (var factory = new ImageFactory()) 40 | { 41 | factory.Load(file.FullName) 42 | .Alpha(percentage) 43 | .SaveAndCompare(file, Category, percentage); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/AutoRotateTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace ImageProcessor.Tests.Processing 4 | { 5 | public class AutoRotateTests 6 | { 7 | private const string Category = "AutoRotate"; 8 | 9 | [Theory] 10 | [InlineData(MetadataMode.All)] 11 | [InlineData(MetadataMode.Geolocation)] 12 | [InlineData(MetadataMode.Copyright)] 13 | [InlineData(MetadataMode.None)] 14 | public void FactoryCanAutoRotate(MetadataMode metadataMode) 15 | { 16 | using (var factory = new ImageFactory(metadataMode)) 17 | { 18 | TestFile file = TestFiles.Jpeg.AutoRtotate; 19 | 20 | factory.Load(file.FullName) 21 | .AutoRotate() 22 | .SaveAndCompare(file, Category, metadataMode); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/BackgroundColorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | using ImageProcessor.Processing; 4 | using Xunit; 5 | 6 | namespace ImageProcessor.Tests.Processing 7 | { 8 | public class BackgroundColorTests 9 | { 10 | private const string Category = "BackgroundColor"; 11 | 12 | public static IEnumerable BackgroundColorFiles = new[] 13 | { 14 | new object[]{ TestFiles.Png.Penguins, Color.HotPink } 15 | }; 16 | 17 | [Fact] 18 | public void BackgroundColorConstructorSetsOptions() 19 | { 20 | Color expected = Color.HotPink; 21 | var processor = new BackgroundColor(expected); 22 | 23 | Assert.Equal(expected, processor.Options); 24 | } 25 | 26 | [Theory] 27 | [MemberData(nameof(BackgroundColorFiles))] 28 | public void FactoryCanSetBackgroundColor(TestFile file, Color color) 29 | { 30 | using (var factory = new ImageFactory()) 31 | { 32 | factory.Load(file.FullName) 33 | .BackgroundColor(color) 34 | .SaveAndCompare(file, Category, color); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/BrightnessTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ImageProcessor.Processing; 3 | using Xunit; 4 | 5 | namespace ImageProcessor.Tests.Processing 6 | { 7 | public class BrightnessTests 8 | { 9 | private const string category = "Brightness"; 10 | 11 | public static IEnumerable BrightnessFiles = new[] 12 | { 13 | new object[]{ TestFiles.Gif.AnimatedPattern, 75 }, 14 | new object[]{ TestFiles.Bmp.Penguins, -25 }, 15 | new object[]{ TestFiles.Gif.Penguins, 75 }, 16 | new object[]{ TestFiles.Jpeg.Penguins, -25 }, 17 | new object[]{ TestFiles.Png.Penguins, 75 } 18 | }; 19 | 20 | [Fact] 21 | public void BrightnessConstructorSetsOptions() 22 | { 23 | const int Expected = 50; 24 | var processor = new Brightness(Expected); 25 | 26 | Assert.Equal(Expected, processor.Options); 27 | } 28 | 29 | [Theory] 30 | [InlineData(-101)] 31 | [InlineData(101)] 32 | public void BrightnessConstructorChecksInput(int percentage) 33 | { 34 | Assert.Throws(() => new Brightness(percentage)); 35 | } 36 | 37 | [Theory] 38 | [MemberData(nameof(BrightnessFiles))] 39 | public void FactoryCanSetBrightness(TestFile file, int percentage) 40 | { 41 | using (var factory = new ImageFactory()) 42 | { 43 | factory.Load(file.FullName) 44 | .Brightness(percentage) 45 | .SaveAndCompare(file, category, percentage); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/ContrastTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ImageProcessor.Processing; 3 | using Xunit; 4 | 5 | namespace ImageProcessor.Tests.Processing 6 | { 7 | public class ContrastTests 8 | { 9 | private const string category = "Contrast"; 10 | 11 | public static IEnumerable ContrastFiles = new[] 12 | { 13 | new object[]{ TestFiles.Gif.AnimatedPattern, 75 }, 14 | new object[]{ TestFiles.Gif.AnimatedPattern, -25 }, 15 | new object[]{ TestFiles.Bmp.Penguins, -25 }, 16 | new object[]{ TestFiles.Gif.Penguins, 75 }, 17 | new object[]{ TestFiles.Jpeg.Penguins, -25 }, 18 | new object[]{ TestFiles.Png.Penguins, 75 } 19 | }; 20 | 21 | [Fact] 22 | public void ContrastConstructorSetsOptions() 23 | { 24 | const int Expected = 50; 25 | var processor = new Contrast(Expected); 26 | 27 | Assert.Equal(Expected, processor.Options); 28 | } 29 | 30 | [Theory] 31 | [InlineData(-101)] 32 | [InlineData(101)] 33 | public void ContrastConstructorChecksInput(int percentage) 34 | { 35 | Assert.Throws(() => new Contrast(percentage)); 36 | } 37 | 38 | [Theory] 39 | [MemberData(nameof(ContrastFiles))] 40 | public void FactoryCanSetContrast(TestFile file, int percentage) 41 | { 42 | using (var factory = new ImageFactory()) 43 | { 44 | factory.Load(file.FullName) 45 | .Contrast(percentage) 46 | .SaveAndCompare(file, category, percentage); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/CropTests.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using ImageProcessor.Processing; 3 | using Xunit; 4 | 5 | namespace ImageProcessor.Tests.Processing 6 | { 7 | public class CropTests 8 | { 9 | private const string category = "Crop"; 10 | 11 | [Fact] 12 | public void CropSettingsConstructorSetsOptions() 13 | { 14 | const int Left = 1; 15 | const int Top = 1; 16 | const int Right = 1; 17 | const int Bottom = 1; 18 | 19 | var expected = new CropOptions(Left, Top, Right, Bottom, CropMode.Percentage); 20 | 21 | Assert.Equal(expected.Left, Left); 22 | Assert.Equal(expected.Top, Top); 23 | Assert.Equal(expected.Right, Right); 24 | Assert.Equal(expected.Bottom, Bottom); 25 | } 26 | 27 | [Fact] 28 | public void CropSettingsConstructorChecksInput() 29 | { 30 | Assert.Throws(() => new CropOptions(-1, 0, 0, 0)); 31 | Assert.Throws(() => new CropOptions(0, -1, 0, 0)); 32 | Assert.Throws(() => new CropOptions(0, 0, -1, 0)); 33 | Assert.Throws(() => new CropOptions(0, 0, 0, -1)); 34 | } 35 | 36 | [Fact] 37 | public void CropConstructorSetsOptions() 38 | { 39 | var expected = new CropOptions(1, 2, 3, 4, CropMode.Percentage); 40 | var processor = new Crop(expected); 41 | 42 | Assert.Equal(expected, processor.Options); 43 | } 44 | 45 | [Fact] 46 | public void FactoryCanCropRectangle() 47 | { 48 | // Test our issue crop. 49 | TestFile file = TestFiles.Jpeg.EXIFCropIssue559; 50 | var bounds = new Rectangle(939, 439, 2778, 2778); 51 | using (var factory = new ImageFactory()) 52 | { 53 | factory.Load(file.FullName) 54 | .Crop(bounds) 55 | .SaveAndCompare(file, category, bounds); 56 | } 57 | } 58 | 59 | [Fact] 60 | public void FactoryCanCropPercentile() 61 | { 62 | // Test our issue crop. 63 | TestFile file = TestFiles.Jpeg.Penguins; 64 | var settings = new CropOptions(15, 25, 10, 5, CropMode.Percentage); 65 | using (var factory = new ImageFactory()) 66 | { 67 | factory.Load(file.FullName) 68 | .Crop(settings) 69 | .SaveAndCompare(file, category, settings); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/DetectEdgesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ImageProcessor.Processing; 4 | using Xunit; 5 | 6 | namespace ImageProcessor.Tests.Processing 7 | { 8 | public class DetectEdgesTests 9 | { 10 | private const string category = "DetectEdges"; 11 | 12 | public static IEnumerable EdgeDetectionOperatorsData() 13 | { 14 | foreach (object value in Enum.GetValues(typeof(EdgeDetectionOperators))) 15 | { 16 | yield return new object[] { value }; 17 | } 18 | } 19 | 20 | [Theory] 21 | [MemberData(nameof(EdgeDetectionOperatorsData))] 22 | public void FactoryCanDetectEdges(EdgeDetectionOperators mode) 23 | { 24 | TestFile file = TestFiles.Png.Penguins; 25 | using (var factory = new ImageFactory()) 26 | { 27 | factory.Load(file.FullName) 28 | .DetectEdges(mode) 29 | .SaveAndCompare(file, category, mode); 30 | } 31 | } 32 | 33 | [Theory] 34 | [MemberData(nameof(EdgeDetectionOperatorsData))] 35 | public void FactoryCanDetectEdgesWithColor(EdgeDetectionOperators mode) 36 | { 37 | TestFile file = TestFiles.Png.Penguins; 38 | using (var factory = new ImageFactory()) 39 | { 40 | factory.Load(file.FullName) 41 | .DetectEdges(mode, false) 42 | .SaveAndCompare(file, category, mode, "color"); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/HueTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ImageProcessor.Processing; 3 | using Xunit; 4 | 5 | namespace ImageProcessor.Tests.Processing 6 | { 7 | public class HueTests 8 | { 9 | private const string category = "Hue"; 10 | 11 | public static IEnumerable HueFiles = new[] 12 | { 13 | new object[]{ TestFiles.Gif.AnimatedPattern, 45 }, 14 | new object[]{ TestFiles.Gif.AnimatedPattern, 90 }, 15 | new object[]{ TestFiles.Bmp.Penguins, 135 }, 16 | new object[]{ TestFiles.Gif.Penguins, 180 }, 17 | new object[]{ TestFiles.Jpeg.Penguins, 225 }, 18 | new object[]{ TestFiles.Png.Penguins, 270 } 19 | }; 20 | 21 | [Fact] 22 | public void HueConstructorSetsOptions() 23 | { 24 | const float Expected = 50; 25 | var processor = new Hue(Expected); 26 | 27 | Assert.Equal(Expected, processor.Options); 28 | } 29 | 30 | [Theory] 31 | [MemberData(nameof(HueFiles))] 32 | public void FactoryCanSetHue(TestFile file, float percentage) 33 | { 34 | using (var factory = new ImageFactory()) 35 | { 36 | factory.Load(file.FullName) 37 | .Hue(percentage) 38 | .SaveAndCompare(file, category, percentage); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/PixelateTests.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using ImageProcessor.Processing; 3 | using Xunit; 4 | 5 | namespace ImageProcessor.Tests.Processing 6 | { 7 | public class PixelateTests 8 | { 9 | private const string category = "Pixelate"; 10 | 11 | [Fact] 12 | public void PixelateSettingsConstructorSetsOptions() 13 | { 14 | const int Size = 1; 15 | var bounds = new Rectangle(1, 2, 3, 4); 16 | 17 | var expected = new PixelateOptions(Size, bounds); 18 | 19 | Assert.Equal(expected.Size, Size); 20 | Assert.Equal(expected.Rectangle, bounds); 21 | } 22 | 23 | [Fact] 24 | public void PixelateSettingsConstructorChecksInput() 25 | { 26 | Assert.Throws(() => new PixelateOptions(-1, default)); 27 | Assert.Throws(() => new PixelateOptions(0, default)); 28 | Assert.Throws(() => new PixelateOptions(-1, new Rectangle(1, 2, 3, 4))); 29 | Assert.Throws(() => new PixelateOptions(0, new Rectangle(1, 2, 3, 4))); 30 | } 31 | 32 | [Fact] 33 | public void PixelateConstructorSetsOptions() 34 | { 35 | var expected = new PixelateOptions(1, new Rectangle(1, 2, 3, 4)); 36 | var processor = new Pixelate(expected); 37 | 38 | Assert.Equal(expected, processor.Options); 39 | } 40 | 41 | [Fact] 42 | public void FactoryCanPixelate() 43 | { 44 | TestFile file = TestFiles.Bmp.Penguins; 45 | using (var factory = new ImageFactory()) 46 | { 47 | factory.Load(file.FullName) 48 | .Pixelate(4) 49 | .SaveAndCompare(file, category); 50 | } 51 | } 52 | 53 | [Fact] 54 | public void FactoryCanPixelateRectangle() 55 | { 56 | TestFile file = TestFiles.Bmp.Penguins; 57 | using (var factory = new ImageFactory()) 58 | { 59 | factory.Load(file.FullName); 60 | 61 | int width = factory.Image.Width; 62 | int height = factory.Image.Height; 63 | var bounds = new Rectangle(width / 4, height / 4, width / 2, height / 2); 64 | var options = new PixelateOptions(4, bounds); 65 | 66 | factory.Pixelate(options) 67 | .SaveAndCompare(file, category, bounds); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/ResizeTests.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Drawing2D; 3 | using ImageProcessor.Processing; 4 | using Xunit; 5 | 6 | namespace ImageProcessor.Tests.Processing 7 | { 8 | public class ResizeTests 9 | { 10 | private const string category = "Resize"; 11 | 12 | [Theory] 13 | [InlineData(ResizeMode.Crop)] 14 | [InlineData(ResizeMode.Pad)] 15 | [InlineData(ResizeMode.BoxPad)] 16 | [InlineData(ResizeMode.Max)] 17 | [InlineData(ResizeMode.Min)] 18 | [InlineData(ResizeMode.Stretch)] 19 | public void FactoryCanResize(ResizeMode mode) 20 | { 21 | TestFile file = TestFiles.Jpeg.Penguins; 22 | using (var factory = new ImageFactory()) 23 | { 24 | factory.Load(file.FullName) 25 | .Resize(factory.Image.Width / 2, (factory.Image.Height / 2) + 40, mode) 26 | .SaveAndCompare(file, category, mode); 27 | } 28 | } 29 | 30 | [Theory] 31 | [InlineData(InterpolationMode.Bilinear)] 32 | [InlineData(InterpolationMode.Bicubic)] 33 | [InlineData(InterpolationMode.NearestNeighbor)] 34 | [InlineData(InterpolationMode.HighQualityBilinear)] 35 | [InlineData(InterpolationMode.HighQualityBicubic)] 36 | public void FactoryCanResizeWithDifferentInterpolationModes(InterpolationMode mode) 37 | { 38 | TestFile file = TestFiles.Jpeg.Penguins; 39 | using (var factory = new ImageFactory()) 40 | { 41 | factory.Load(file.FullName) 42 | .Resize(new ResizeOptions 43 | { 44 | Size = new Size(factory.Image.Width / 2, (factory.Image.Height / 2) + 40), 45 | InterpolationMode = mode 46 | }) 47 | .SaveAndCompare(file, category, mode); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/Processing/SaturationTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ImageProcessor.Processing; 3 | using Xunit; 4 | 5 | namespace ImageProcessor.Tests.Processing 6 | { 7 | public class SaturationTests 8 | { 9 | private const string category = "Saturation"; 10 | 11 | public static IEnumerable SaturationFiles = new[] 12 | { 13 | new object[]{ TestFiles.Gif.AnimatedPattern, 75 }, 14 | new object[]{ TestFiles.Gif.AnimatedPattern, -25 }, 15 | new object[]{ TestFiles.Bmp.Penguins, -25 }, 16 | new object[]{ TestFiles.Gif.Penguins, 75 }, 17 | new object[]{ TestFiles.Jpeg.Penguins, -25 }, 18 | new object[]{ TestFiles.Png.Penguins, 75 } 19 | }; 20 | 21 | [Fact] 22 | public void SaturationConstructorSetsOptions() 23 | { 24 | const int Expected = 50; 25 | var processor = new Saturation(Expected); 26 | 27 | Assert.Equal(Expected, processor.Options); 28 | } 29 | 30 | [Theory] 31 | [InlineData(-101)] 32 | [InlineData(101)] 33 | public void SaturationConstructorChecksInput(int percentage) 34 | { 35 | Assert.Throws(() => new Saturation(percentage)); 36 | } 37 | 38 | [Theory] 39 | [MemberData(nameof(SaturationFiles))] 40 | public void FactoryCanSetSaturation(TestFile file, int percentage) 41 | { 42 | using (var factory = new ImageFactory()) 43 | { 44 | factory.Load(file.FullName) 45 | .Saturation(percentage) 46 | .SaveAndCompare(file, category, percentage); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/TestFiles.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace ImageProcessor.Tests 4 | { 5 | public static class TestFiles 6 | { 7 | public static class Jpeg 8 | { 9 | public static TestFile EXIFCropIssue559 = TestUtils.GetTestFileByName("exif-crop-issue-559.jfif"); 10 | public static TestFile Penguins = TestUtils.GetTestFileByName("format-Penguins.jpg"); 11 | public static TestFile AutoRtotate = TestUtils.GetTestFileByName("autorotate-landscape-2.jpg"); 12 | } 13 | 14 | public static class Png 15 | { 16 | public static TestFile Penguins = TestUtils.GetTestFileByName("format-Penguins.png"); 17 | } 18 | 19 | public static class Gif 20 | { 21 | public static TestFile AnimatedPattern = TestUtils.GetTestFileByName("animated-pattern.gif"); 22 | public static TestFile AnimatedZivan = TestUtils.GetTestFileByName("animated-zivan.gif"); 23 | public static TestFile Penguins = TestUtils.GetTestFileByName("format-Penguins.gif"); 24 | } 25 | 26 | public static class Bmp 27 | { 28 | public static TestFile Penguins = TestUtils.GetTestFileByName("format-Penguins.bmp"); 29 | } 30 | 31 | public static class Tiff 32 | { 33 | public static TestFile Penguins = TestUtils.GetTestFileByName("format-Penguins.tif"); 34 | } 35 | 36 | public static class WebP 37 | { 38 | public static TestFile Penguins = TestUtils.GetTestFileByName("format-Penguins.webp"); 39 | } 40 | } 41 | 42 | public class TestFile 43 | { 44 | public TestFile(FileInfo info, string expectedRoot, string actualRoot) 45 | { 46 | this.FullName = info.FullName; 47 | this.Name = Path.GetFileName(info.Name); 48 | this.Extension = Path.GetExtension(info.Extension); 49 | this.ExpectedRoot = expectedRoot; 50 | this.ActualRoot = actualRoot; 51 | } 52 | 53 | public string Name { get; } 54 | 55 | public string FullName { get; } 56 | 57 | public string Extension { get; } 58 | 59 | public string ExpectedRoot { get; } 60 | 61 | public string ActualRoot { get; } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/TestUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace ImageProcessor.Tests 8 | { 9 | public static class TestUtils 10 | { 11 | private static readonly object Lock = new object(); 12 | 13 | private static IEnumerable Files; 14 | 15 | /// 16 | /// Gets the input image path by name. 17 | /// 18 | /// The name of the image to return the path for. 19 | /// The . 20 | public static TestFile GetTestFileByName(string name) 21 | { 22 | return GetInputImageFiles().First(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); 23 | } 24 | 25 | private static IEnumerable GetInputImageFiles(params string[] extensions) 26 | { 27 | lock (Lock) 28 | { 29 | if (Files is null) 30 | { 31 | string codeBase = new Uri(Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase)).LocalPath; 32 | const string Root = "../../../../../Images/"; 33 | var input = new DirectoryInfo(Path.GetFullPath(codeBase + Root + "Input")); 34 | string expected = Path.GetFullPath(codeBase + Root + "Expected"); 35 | string actual = Path.GetFullPath(codeBase + Root + "Actual"); 36 | 37 | // TODO: Concat supported format extensions 38 | Files = GetFilesByExtensions(input, expected, actual, ".jpg", ".jpeg", ".jfif", ".png", ".gif", ".tiff", ".tif", ".bmp", ".webp"); 39 | } 40 | } 41 | 42 | if (extensions.Length > 0) 43 | { 44 | return Files.Where(x => extensions.Contains(x.Extension, StringComparer.OrdinalIgnoreCase)); 45 | } 46 | 47 | return Files; 48 | } 49 | 50 | private static IEnumerable GetFilesByExtensions(DirectoryInfo input, string expected, string actual, params string[] extensions) 51 | { 52 | IEnumerable files = input.EnumerateFiles(); 53 | return files.Where(x => extensions.Contains(x.Extension, StringComparer.OrdinalIgnoreCase)) 54 | .Select(x => new TestFile(x, expected, actual)); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/ImageProcessor.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadowCopy": false, 3 | "methodDisplay": "method", 4 | "diagnosticMessages": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/Images/Input/4.sm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/4.sm.webp -------------------------------------------------------------------------------- /tests/Images/Input/Teeth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/Teeth.png -------------------------------------------------------------------------------- /tests/Images/Input/animated-bird.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/animated-bird.gif -------------------------------------------------------------------------------- /tests/Images/Input/animated-meter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/animated-meter.gif -------------------------------------------------------------------------------- /tests/Images/Input/animated-pattern.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/animated-pattern.gif -------------------------------------------------------------------------------- /tests/Images/Input/animated-startrek.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/animated-startrek.gif -------------------------------------------------------------------------------- /tests/Images/Input/animated-zivan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/animated-zivan.gif -------------------------------------------------------------------------------- /tests/Images/Input/autorotate-landscape-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/autorotate-landscape-2.jpg -------------------------------------------------------------------------------- /tests/Images/Input/b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/b.jpg -------------------------------------------------------------------------------- /tests/Images/Input/color-tests/hi-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/color-tests/hi-color.png -------------------------------------------------------------------------------- /tests/Images/Input/color-tests/hi-contrast.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/color-tests/hi-contrast.jpg -------------------------------------------------------------------------------- /tests/Images/Input/color-tests/hi-saturation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/color-tests/hi-saturation.jpg -------------------------------------------------------------------------------- /tests/Images/Input/exif-crop-issue-559.jfif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/exif-crop-issue-559.jfif -------------------------------------------------------------------------------- /tests/Images/Input/exif/autorotate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/exif/autorotate.jpg -------------------------------------------------------------------------------- /tests/Images/Input/exif/exif-Tulips.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/exif/exif-Tulips.jpg -------------------------------------------------------------------------------- /tests/Images/Input/exif/exif-rocks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/exif/exif-rocks.jpg -------------------------------------------------------------------------------- /tests/Images/Input/format-Penguins-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/format-Penguins-8bit.png -------------------------------------------------------------------------------- /tests/Images/Input/format-Penguins.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/format-Penguins.bmp -------------------------------------------------------------------------------- /tests/Images/Input/format-Penguins.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/format-Penguins.gif -------------------------------------------------------------------------------- /tests/Images/Input/format-Penguins.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/format-Penguins.jpg -------------------------------------------------------------------------------- /tests/Images/Input/format-Penguins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/format-Penguins.png -------------------------------------------------------------------------------- /tests/Images/Input/format-Penguins.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/format-Penguins.tif -------------------------------------------------------------------------------- /tests/Images/Input/format-Penguins.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/format-Penguins.webp -------------------------------------------------------------------------------- /tests/Images/Input/gamma/gamma-1.0-or-2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/gamma/gamma-1.0-or-2.2.png -------------------------------------------------------------------------------- /tests/Images/Input/gamma/gamma-dalai-lama-gray-tft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/gamma/gamma-dalai-lama-gray-tft.jpg -------------------------------------------------------------------------------- /tests/Images/Input/gamma/gamma-dalai-lama-gray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/gamma/gamma-dalai-lama-gray.jpg -------------------------------------------------------------------------------- /tests/Images/Input/gamma/gamma-fly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/gamma/gamma-fly.jpg -------------------------------------------------------------------------------- /tests/Images/Input/gamma/gamma-saturn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/gamma/gamma-saturn.jpg -------------------------------------------------------------------------------- /tests/Images/Input/icc-profiles/cmyk-profile-euroscale.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/icc-profiles/cmyk-profile-euroscale.jpg -------------------------------------------------------------------------------- /tests/Images/Input/icc-profiles/profile-adobe-rgb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/icc-profiles/profile-adobe-rgb.jpg -------------------------------------------------------------------------------- /tests/Images/Input/icc-profiles/profile-srgb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/icc-profiles/profile-srgb.jpg -------------------------------------------------------------------------------- /tests/Images/Input/imageprocessor/mask/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/imageprocessor/mask/mask.png -------------------------------------------------------------------------------- /tests/Images/Input/imageprocessor/overlay/monster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/imageprocessor/overlay/monster.png -------------------------------------------------------------------------------- /tests/Images/Input/imageprocessor/overlay/monster24bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/imageprocessor/overlay/monster24bit.png -------------------------------------------------------------------------------- /tests/Images/Input/stretched.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/stretched.jpg -------------------------------------------------------------------------------- /tests/Images/Input/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/text.png -------------------------------------------------------------------------------- /tests/Images/Input/trans.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimBobSquarePants/ImageProcessor/95f44a217d22642ab7aaa2940286bf4c5f43c2ee/tests/Images/Input/trans.gif --------------------------------------------------------------------------------