From 8626276ed9698b44bdc7ed9712c5e528e745011e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 8 Nov 2016 04:11:19 +0100 Subject: [PATCH 01/10] 4.6 "branch" --- ImageSharp.46.sln | 28 + src/ImageSharp46/Bootstrapper.cs | 69 + src/ImageSharp46/Colors/Color.cs | 408 +++ src/ImageSharp46/Colors/ColorConstants.cs | 179 ++ src/ImageSharp46/Colors/ColorDefinitions.cs | 728 ++++++ src/ImageSharp46/Colors/ColorTransforms.cs | 74 + .../Colors/ColorspaceTransforms.cs | 292 +++ src/ImageSharp46/Colors/Colorspaces/Bgra32.cs | 167 ++ src/ImageSharp46/Colors/Colorspaces/CieLab.cs | 192 ++ src/ImageSharp46/Colors/Colorspaces/CieXyz.cs | 184 ++ src/ImageSharp46/Colors/Colorspaces/Cmyk.cs | 195 ++ src/ImageSharp46/Colors/Colorspaces/Hsl.cs | 213 ++ src/ImageSharp46/Colors/Colorspaces/Hsv.cs | 206 ++ .../Colors/Colorspaces/IAlmostEquatable.cs | 30 + src/ImageSharp46/Colors/Colorspaces/YCbCr.cs | 157 ++ src/ImageSharp46/Colors/ComponentOrder.cs | 33 + .../Colors/PackedPixel/IPackedBytes.cs | 31 + .../Colors/PackedPixel/IPackedPixel.cs | 16 + .../Colors/PackedPixel/IPackedVector.cs | 42 + src/ImageSharp46/Colors/RgbaComponent.cs | 33 + .../Common/Exceptions/ImageFormatException.cs | 45 + .../Exceptions/ImageProcessingException.cs | 44 + .../Common/Extensions/ByteExtensions.cs | 60 + .../Common/Extensions/ComparableExtensions.cs | 169 ++ .../Common/Extensions/EnumerableExtensions.cs | 88 + .../Common/Extensions/Vector4Extensions.cs | 83 + src/ImageSharp46/Common/Helpers/Guard.cs | 205 ++ src/ImageSharp46/Common/Helpers/ImageMaths.cs | 293 +++ src/ImageSharp46/Filters/Alpha.cs | 48 + src/ImageSharp46/Filters/BackgroundColor.cs | 30 + src/ImageSharp46/Filters/BinaryThreshold.cs | 48 + src/ImageSharp46/Filters/BlackWhite.cs | 46 + src/ImageSharp46/Filters/Blend.cs | 50 + src/ImageSharp46/Filters/Brightness.cs | 48 + src/ImageSharp46/Filters/ColorBlindness.cs | 85 + src/ImageSharp46/Filters/Contrast.cs | 48 + src/ImageSharp46/Filters/Glow.cs | 102 + src/ImageSharp46/Filters/Grayscale.cs | 52 + src/ImageSharp46/Filters/Hue.cs | 48 + src/ImageSharp46/Filters/Invert.cs | 46 + src/ImageSharp46/Filters/Kodachrome.cs | 46 + src/ImageSharp46/Filters/Lomograph.cs | 46 + .../Filters/Options/ColorBlindness.cs | 53 + .../Filters/Options/EdgeDetection.cs | 63 + .../Filters/Options/GrayscaleMode.cs | 23 + src/ImageSharp46/Filters/Polaroid.cs | 46 + .../Filters/Processors/AlphaProcessor.cs | 85 + .../Processors/BackgroundColorProcessor.cs | 98 + .../Binarization/BinaryThresholdProcessor.cs | 111 + .../Filters/Processors/BlendProcessor.cs | 106 + .../Filters/Processors/BrightnessProcessor.cs | 90 + .../ColorMatrix/BlackWhiteProcessor.cs | 36 + .../ColorBlindness/AchromatomalyProcessor.cs | 36 + .../ColorBlindness/AchromatopsiaProcessor.cs | 36 + .../ColorBlindness/DeuteranomalyProcessor.cs | 33 + .../ColorBlindness/DeuteranopiaProcessor.cs | 33 + .../ColorBlindness/ProtanomalyProcessor.cs | 33 + .../ColorBlindness/ProtanopiaProcessor.cs | 33 + .../ColorMatrix/ColorBlindness/README.md | 4 + .../ColorBlindness/TritanomalyProcessor.cs | 33 + .../ColorBlindness/TritanopiaProcessor.cs | 33 + .../ColorMatrix/ColorMatrixFilter.cs | 96 + .../ColorMatrix/GrayscaleBt601Processor.cs | 34 + .../ColorMatrix/GrayscaleBt709Processor.cs | 32 + .../Processors/ColorMatrix/HueProcessor.cs | 79 + .../ColorMatrix/IColorMatrixFilter.cs | 25 + .../ColorMatrix/KodachromeProcessor.cs | 30 + .../ColorMatrix/LomographProcessor.cs | 38 + .../ColorMatrix/PolaroidProcessor.cs | 48 + .../ColorMatrix/SaturationProcessor.cs | 62 + .../Processors/ColorMatrix/SepiaProcessor.cs | 37 + .../Filters/Processors/ContrastProcessor.cs | 90 + .../Filters/Processors/GlowProcessor.cs | 94 + .../Filters/Processors/IImageFilter.cs | 36 + .../Filters/Processors/ImageFilter.cs | 70 + .../Filters/Processors/InvertProcessor.cs | 68 + .../Filters/Processors/VignetteProcessor.cs | 96 + src/ImageSharp46/Filters/Saturation.cs | 48 + src/ImageSharp46/Filters/Sepia.cs | 46 + src/ImageSharp46/Filters/Vignette.cs | 104 + .../Formats/Bmp/BmpBitsPerPixel.cs | 23 + .../Formats/Bmp/BmpCompression.cs | 63 + src/ImageSharp46/Formats/Bmp/BmpDecoder.cs | 83 + .../Formats/Bmp/BmpDecoderCore.cs | 404 +++ src/ImageSharp46/Formats/Bmp/BmpEncoder.cs | 54 + .../Formats/Bmp/BmpEncoderCore.cs | 189 ++ src/ImageSharp46/Formats/Bmp/BmpFileHeader.cs | 49 + src/ImageSharp46/Formats/Bmp/BmpFormat.cs | 19 + src/ImageSharp46/Formats/Bmp/BmpInfoHeader.cs | 82 + src/ImageSharp46/Formats/Bmp/README.md | 8 + .../Formats/Gif/DisposalMethod.cs | 37 + src/ImageSharp46/Formats/Gif/GifConstants.cs | 83 + src/ImageSharp46/Formats/Gif/GifDecoder.cs | 65 + .../Formats/Gif/GifDecoderCore.cs | 425 ++++ src/ImageSharp46/Formats/Gif/GifEncoder.cs | 64 + .../Formats/Gif/GifEncoderCore.cs | 349 +++ src/ImageSharp46/Formats/Gif/GifFormat.cs | 19 + src/ImageSharp46/Formats/Gif/LzwDecoder.cs | 228 ++ src/ImageSharp46/Formats/Gif/LzwEncoder.cs | 419 ++++ src/ImageSharp46/Formats/Gif/PackedField.cs | 199 ++ src/ImageSharp46/Formats/Gif/README.md | 4 + .../Sections/GifGraphicsControlExtension.cs | 42 + .../Gif/Sections/GifImageDescriptor.cs | 59 + .../Sections/GifLogicalScreenDescriptor.cs | 55 + src/ImageSharp46/Formats/IImageDecoder.cs | 52 + src/ImageSharp46/Formats/IImageEncoder.cs | 51 + src/ImageSharp46/Formats/IImageFormat.cs | 23 + .../Formats/Jpg/Components/Bits.cs | 31 + .../Formats/Jpg/Components/Block.cs | 44 + .../Formats/Jpg/Components/Bytes.cs | 43 + .../Formats/Jpg/Components/Component.cs | 33 + .../Formats/Jpg/Components/FDCT.cs | 160 ++ .../Formats/Jpg/Components/GrayImage.cs | 101 + .../Formats/Jpg/Components/Huffman.cs | 64 + .../Formats/Jpg/Components/IDCT.cs | 169 ++ .../Formats/Jpg/Components/YCbCrImage.cs | 228 ++ src/ImageSharp46/Formats/Jpg/JpegConstants.cs | 230 ++ src/ImageSharp46/Formats/Jpg/JpegDecoder.cs | 139 ++ .../Formats/Jpg/JpegDecoderCore.cs | 2203 +++++++++++++++++ src/ImageSharp46/Formats/Jpg/JpegEncoder.cs | 100 + .../Formats/Jpg/JpegEncoderCore.cs | 1011 ++++++++ src/ImageSharp46/Formats/Jpg/JpegFormat.cs | 19 + src/ImageSharp46/Formats/Jpg/JpegSubsample.cs | 25 + src/ImageSharp46/Formats/Jpg/README.md | 3 + .../Formats/Png/Filters/AverageFilter.cs | 78 + .../Formats/Png/Filters/FilterType.cs | 45 + .../Formats/Png/Filters/NoneFilter.cs | 41 + .../Formats/Png/Filters/PaethFilter.cs | 97 + .../Formats/Png/Filters/SubFilter.cs | 58 + .../Formats/Png/Filters/UpFilter.cs | 58 + src/ImageSharp46/Formats/Png/PngChunk.cs | 39 + src/ImageSharp46/Formats/Png/PngChunkTypes.cs | 62 + src/ImageSharp46/Formats/Png/PngColorType.cs | 38 + src/ImageSharp46/Formats/Png/PngDecoder.cs | 91 + .../Formats/Png/PngDecoderCore.cs | 688 +++++ src/ImageSharp46/Formats/Png/PngEncoder.cs | 93 + .../Formats/Png/PngEncoderCore.cs | 707 ++++++ src/ImageSharp46/Formats/Png/PngFormat.cs | 19 + src/ImageSharp46/Formats/Png/PngHeader.cs | 62 + src/ImageSharp46/Formats/Png/README.md | 6 + src/ImageSharp46/Formats/Png/Zlib/Adler32.cs | 174 ++ src/ImageSharp46/Formats/Png/Zlib/Crc32.cs | 180 ++ .../Formats/Png/Zlib/IChecksum.cs | 60 + src/ImageSharp46/Formats/Png/Zlib/README.md | 2 + .../Formats/Png/Zlib/ZlibDeflateStream.cs | 208 ++ .../Formats/Png/Zlib/ZlibInflateStream.cs | 214 ++ src/ImageSharp46/IO/BigEndianBitConverter.cs | 48 + src/ImageSharp46/IO/EndianBinaryReader.cs | 615 +++++ src/ImageSharp46/IO/EndianBinaryWriter.cs | 385 +++ src/ImageSharp46/IO/EndianBitConverter.cs | 724 ++++++ src/ImageSharp46/IO/Endianness.cs | 23 + .../IO/LittleEndianBitConverter.cs | 47 + src/ImageSharp46/Image.cs | 66 + src/ImageSharp46/Image/IImageBase.cs | 119 + src/ImageSharp46/Image/IImageFrame.cs | 18 + src/ImageSharp46/Image/IImageProcessor.cs | 26 + src/ImageSharp46/Image/Image.cs | 361 +++ src/ImageSharp46/Image/ImageBase.cs | 166 ++ src/ImageSharp46/Image/ImageFrame.cs | 41 + src/ImageSharp46/Image/ImageIOExtensions.cs | 86 + .../Image/ImageProcessingExtensions.cs | 203 ++ src/ImageSharp46/Image/ImageProperty.cs | 153 ++ src/ImageSharp46/Image/PixelAccessor.cs | 398 +++ src/ImageSharp46/Image/PixelRow.cs | 179 ++ src/ImageSharp46/ImageProcessor.cs | 25 + src/ImageSharp46/ImageSharp46.csproj | 393 +++ src/ImageSharp46/Numerics/Ellipse.cs | 174 ++ src/ImageSharp46/Numerics/LongRational.cs | 355 +++ src/ImageSharp46/Numerics/Point.cs | 282 +++ src/ImageSharp46/Numerics/Rational.cs | 189 ++ src/ImageSharp46/Numerics/Rectangle.cs | 291 +++ src/ImageSharp46/Numerics/SignedRational.cs | 189 ++ src/ImageSharp46/Numerics/Size.cs | 166 ++ src/ImageSharp46/PixelAccessor.cs | 90 + .../Profiles/Exif/ExifDataType.cs | 78 + src/ImageSharp46/Profiles/Exif/ExifParts.cs | 41 + src/ImageSharp46/Profiles/Exif/ExifProfile.cs | 239 ++ src/ImageSharp46/Profiles/Exif/ExifReader.cs | 515 ++++ src/ImageSharp46/Profiles/Exif/ExifTag.cs | 1547 ++++++++++++ .../Exif/ExifTagDescriptionAttribute.cs | 52 + src/ImageSharp46/Profiles/Exif/ExifValue.cs | 716 ++++++ src/ImageSharp46/Profiles/Exif/ExifWriter.cs | 585 +++++ src/ImageSharp46/Profiles/Exif/README.md | 3 + src/ImageSharp46/ProgressEventArgs.cs | 23 + src/ImageSharp46/Properties/AssemblyInfo.cs | 40 + src/ImageSharp46/Quantizers/IQuantizer.cs | 34 + .../Quantizers/Octree/OctreeQuantizer.cs | 518 ++++ .../Quantizers/Octree/Quantizer.cs | 143 ++ .../Quantizers/Options/Quantization.cs | 28 + .../Quantizers/Palette/PaletteQuantizer.cs | 130 + src/ImageSharp46/Quantizers/Quantize.cs | 65 + src/ImageSharp46/Quantizers/QuantizedImage.cs | 94 + src/ImageSharp46/Quantizers/Wu/Box.cs | 58 + src/ImageSharp46/Quantizers/Wu/WuQuantizer.cs | 777 ++++++ src/ImageSharp46/Samplers/AutoOrient.cs | 87 + src/ImageSharp46/Samplers/BoxBlur.cs | 48 + src/ImageSharp46/Samplers/Crop.cs | 65 + src/ImageSharp46/Samplers/DetectEdges.cs | 161 ++ src/ImageSharp46/Samplers/EntropyCrop.cs | 31 + src/ImageSharp46/Samplers/Flip.cs | 31 + src/ImageSharp46/Samplers/GuassianBlur.cs | 48 + src/ImageSharp46/Samplers/GuassianSharpen.cs | 48 + src/ImageSharp46/Samplers/OilPainting.cs | 59 + .../Samplers/Options/AnchorPosition.cs | 58 + src/ImageSharp46/Samplers/Options/FlipType.cs | 28 + .../Samplers/Options/Orientation.cs | 20 + .../Samplers/Options/ResizeHelper.cs | 441 ++++ .../Samplers/Options/ResizeMode.cs | 49 + .../Samplers/Options/ResizeOptions.cs | 47 + .../Samplers/Options/RotateType.cs | 33 + src/ImageSharp46/Samplers/Pad.cs | 36 + src/ImageSharp46/Samplers/Pixelate.cs | 55 + .../Processors/CompandingResizeProcessor.cs | 159 ++ .../Convolution/BoxBlurProcessor.cs | 101 + .../Convolution/Convolution2DFilter.cs | 129 + .../Convolution/Convolution2PassFilter.cs | 118 + .../Convolution/ConvolutionFilter.cs | 105 + .../EdgeDetection/EdgeDetector2DFilter.cs | 47 + .../EdgeDetectorCompassFilter.cs | 139 ++ .../EdgeDetection/EdgeDetectorFilter.cs | 40 + .../EdgeDetection/IEdgeDetectorFilter.cs | 29 + .../EdgeDetection/KayyaliProcessor.cs | 47 + .../EdgeDetection/KirschProcessor.cs | 124 + .../EdgeDetection/Laplacian3X3Processor.cs | 34 + .../EdgeDetection/Laplacian5X5Processor.cs | 36 + .../LaplacianOfGaussianProcessor.cs | 36 + .../EdgeDetection/PrewittProcessor.cs | 47 + .../EdgeDetection/RobertsCrossProcessor.cs | 45 + .../EdgeDetection/RobinsonProcessor.cs | 124 + .../EdgeDetection/ScharrProcessor.cs | 47 + .../EdgeDetection/SobelProcessor.cs | 47 + .../Convolution/GuassianBlurProcessor.cs | 142 ++ .../Convolution/GuassianSharpenProcessor.cs | 180 ++ .../Samplers/Processors/CropProcessor.cs | 44 + .../Processors/EntropyCropProcessor.cs | 107 + .../Samplers/Processors/FlipProcessor.cs | 115 + .../Samplers/Processors/IImageSampler.cs | 58 + .../Samplers/Processors/ImageSampler.cs | 119 + .../Samplers/Processors/Matrix3x2Processor.cs | 51 + .../Processors/OilPaintingProcessor.cs | 151 ++ .../Samplers/Processors/PixelateProcessor.cs | 111 + .../Processors/ResamplingWeightedProcessor.cs | 171 ++ .../Samplers/Processors/ResizeProcessor.cs | 158 ++ .../Samplers/Processors/RotateProcessor.cs | 215 ++ .../Samplers/Processors/SkewProcessor.cs | 79 + .../Samplers/Resamplers/BicubicResampler.cs | 43 + .../Samplers/Resamplers/BoxResampler.cs | 28 + .../Resamplers/CatmullRomResampler.cs | 28 + .../Samplers/Resamplers/HermiteResampler.cs | 27 + .../Samplers/Resamplers/IResampler.cs | 27 + .../Samplers/Resamplers/Lanczos2Resampler.cs | 34 + .../Samplers/Resamplers/Lanczos3Resampler.cs | 34 + .../Samplers/Resamplers/Lanczos5Resampler.cs | 34 + .../Samplers/Resamplers/Lanczos8Resampler.cs | 34 + .../Resamplers/MitchellNetravaliResampler.cs | 26 + .../Resamplers/NearestNeighborResampler.cs | 23 + .../Samplers/Resamplers/RobidouxResampler.cs | 26 + .../Resamplers/RobidouxSharpResampler.cs | 26 + .../Samplers/Resamplers/SplineResampler.cs | 26 + .../Samplers/Resamplers/TriangleResampler.cs | 34 + .../Samplers/Resamplers/WelchResampler.cs | 33 + src/ImageSharp46/Samplers/Resize.cs | 168 ++ src/ImageSharp46/Samplers/Rotate.cs | 62 + src/ImageSharp46/Samplers/RotateFlip.cs | 29 + src/ImageSharp46/Samplers/Skew.cs | 49 + src/ImageSharp46/app.config | 19 + src/ImageSharp46/packages.config | 49 + .../Benchmark/DecodeJpeg.cs | 67 + .../Colors/ColorConversionTests.cs | 511 ++++ tests/ImageSharp.Tests46/Colors/ColorTests.cs | 135 + tests/ImageSharp.Tests46/FileTestBase.cs | 51 + .../Formats/Bmp/BitmapTests.cs | 41 + .../Formats/GeneralFormatTests.cs | 158 ++ .../Formats/Png/PngTests.cs | 33 + .../ImageSharp.Tests46/Helpers/GuardTests.cs | 242 ++ .../Image/ImagePropertyTests.cs | 78 + .../Image/PixelAccessorTests.cs | 168 ++ .../ImageSharp.Tests46.csproj | 158 ++ .../ImageSharp.Tests46/Numerics/PointTests.cs | 50 + .../Numerics/RationalTests.cs | 115 + .../Numerics/RectangleTests.cs | 66 + .../Numerics/SignedRationalTests.cs | 122 + .../ImageSharp.Tests46/Numerics/SizeTests.cs | 50 + .../Processors/Filters/AlphaTest.cs | 59 + .../Processors/Filters/BackgroundColorTest.cs | 31 + .../Processors/Filters/BinaryThreshold.cs | 40 + .../Processors/Filters/BlackWhiteTest.cs | 31 + .../Processors/Filters/BlendTest.cs | 37 + .../Processors/Filters/BoxBlurTest.cs | 40 + .../Processors/Filters/BrightnessTest.cs | 40 + .../Processors/Filters/ColorBlindnessTest.cs | 46 + .../Processors/Filters/ContrastTest.cs | 39 + .../Processors/Filters/GlowTest.cs | 85 + .../Processors/Filters/GrayscaleTest.cs | 41 + .../Processors/Filters/HueTest.cs | 40 + .../Processors/Filters/InvertTest.cs | 48 + .../Processors/Filters/KodachromeTest.cs | 31 + .../Processors/Filters/LomographTest.cs | 49 + .../Processors/Filters/PolaroidTest.cs | 31 + .../Processors/Filters/SaturationTest.cs | 40 + .../Processors/Filters/SepiaTest.cs | 31 + .../Processors/Filters/VignetteTest.cs | 85 + .../Processors/Samplers/AutoOrientTests.cs | 56 + .../Processors/Samplers/CropTest.cs | 31 + .../Processors/Samplers/DetectEdgesTest.cs | 67 + .../Processors/Samplers/EntropyCropTest.cs | 40 + .../Processors/Samplers/FlipTests.cs | 41 + .../Processors/Samplers/GuassianBlurTest.cs | 40 + .../Samplers/GuassianSharpenTest.cs | 40 + .../Processors/Samplers/OilPaintTest.cs | 60 + .../Processors/Samplers/PadTest.cs | 31 + .../Processors/Samplers/PixelateTest.cs | 59 + .../Processors/Samplers/ResizeTests.cs | 304 +++ .../Processors/Samplers/RotateFlipTest.cs | 43 + .../Processors/Samplers/RotateTest.cs | 68 + .../Processors/Samplers/SkewTest.cs | 42 + .../Profiles/Exif/ExifProfileTests.cs | 325 +++ .../Exif/ExifTagDescriptionAttributeTests.cs | 38 + .../Profiles/Exif/ExifValueTests.cs | 50 + .../Properties/AssemblyInfo.cs | 23 + tests/ImageSharp.Tests46/TestFile.cs | 57 + tests/ImageSharp.Tests46/TestImages.cs | 56 + .../TestImages/Formats/Bmp/Car.bmp | 3 + .../TestImages/Formats/Bmp/F.bmp | 3 + .../TestImages/Formats/Bmp/neg_height.bmp | 3 + .../TestImages/Formats/Gif/giphy.gif | 3 + .../TestImages/Formats/Gif/rings.gif | 3 + .../TestImages/Formats/Jpg/Calliphora.jpg | 3 + .../TestImages/Formats/Jpg/Floorplan.jpeg | 3 + .../TestImages/Formats/Jpg/cmyk.jpg | 3 + .../TestImages/Formats/Jpg/exif.jpg | 3 + .../TestImages/Formats/Jpg/fb.jpg | 3 + .../Formats/Jpg/gamma_dalai_lama_gray.jpg | 3 + .../TestImages/Formats/Jpg/progress.jpg | 3 + .../TestImages/Formats/Jpg/turtle.jpg | 3 + .../TestImages/Formats/Png/blur.png | 3 + .../TestImages/Formats/Png/indexed.png | 3 + .../TestImages/Formats/Png/pd.png | 3 + .../TestImages/Formats/Png/pl.png | 3 + .../TestImages/Formats/Png/splash.png | 3 + tests/ImageSharp.Tests46/packages.config | 9 + 341 files changed, 39176 insertions(+) create mode 100644 ImageSharp.46.sln create mode 100644 src/ImageSharp46/Bootstrapper.cs create mode 100644 src/ImageSharp46/Colors/Color.cs create mode 100644 src/ImageSharp46/Colors/ColorConstants.cs create mode 100644 src/ImageSharp46/Colors/ColorDefinitions.cs create mode 100644 src/ImageSharp46/Colors/ColorTransforms.cs create mode 100644 src/ImageSharp46/Colors/ColorspaceTransforms.cs create mode 100644 src/ImageSharp46/Colors/Colorspaces/Bgra32.cs create mode 100644 src/ImageSharp46/Colors/Colorspaces/CieLab.cs create mode 100644 src/ImageSharp46/Colors/Colorspaces/CieXyz.cs create mode 100644 src/ImageSharp46/Colors/Colorspaces/Cmyk.cs create mode 100644 src/ImageSharp46/Colors/Colorspaces/Hsl.cs create mode 100644 src/ImageSharp46/Colors/Colorspaces/Hsv.cs create mode 100644 src/ImageSharp46/Colors/Colorspaces/IAlmostEquatable.cs create mode 100644 src/ImageSharp46/Colors/Colorspaces/YCbCr.cs create mode 100644 src/ImageSharp46/Colors/ComponentOrder.cs create mode 100644 src/ImageSharp46/Colors/PackedPixel/IPackedBytes.cs create mode 100644 src/ImageSharp46/Colors/PackedPixel/IPackedPixel.cs create mode 100644 src/ImageSharp46/Colors/PackedPixel/IPackedVector.cs create mode 100644 src/ImageSharp46/Colors/RgbaComponent.cs create mode 100644 src/ImageSharp46/Common/Exceptions/ImageFormatException.cs create mode 100644 src/ImageSharp46/Common/Exceptions/ImageProcessingException.cs create mode 100644 src/ImageSharp46/Common/Extensions/ByteExtensions.cs create mode 100644 src/ImageSharp46/Common/Extensions/ComparableExtensions.cs create mode 100644 src/ImageSharp46/Common/Extensions/EnumerableExtensions.cs create mode 100644 src/ImageSharp46/Common/Extensions/Vector4Extensions.cs create mode 100644 src/ImageSharp46/Common/Helpers/Guard.cs create mode 100644 src/ImageSharp46/Common/Helpers/ImageMaths.cs create mode 100644 src/ImageSharp46/Filters/Alpha.cs create mode 100644 src/ImageSharp46/Filters/BackgroundColor.cs create mode 100644 src/ImageSharp46/Filters/BinaryThreshold.cs create mode 100644 src/ImageSharp46/Filters/BlackWhite.cs create mode 100644 src/ImageSharp46/Filters/Blend.cs create mode 100644 src/ImageSharp46/Filters/Brightness.cs create mode 100644 src/ImageSharp46/Filters/ColorBlindness.cs create mode 100644 src/ImageSharp46/Filters/Contrast.cs create mode 100644 src/ImageSharp46/Filters/Glow.cs create mode 100644 src/ImageSharp46/Filters/Grayscale.cs create mode 100644 src/ImageSharp46/Filters/Hue.cs create mode 100644 src/ImageSharp46/Filters/Invert.cs create mode 100644 src/ImageSharp46/Filters/Kodachrome.cs create mode 100644 src/ImageSharp46/Filters/Lomograph.cs create mode 100644 src/ImageSharp46/Filters/Options/ColorBlindness.cs create mode 100644 src/ImageSharp46/Filters/Options/EdgeDetection.cs create mode 100644 src/ImageSharp46/Filters/Options/GrayscaleMode.cs create mode 100644 src/ImageSharp46/Filters/Polaroid.cs create mode 100644 src/ImageSharp46/Filters/Processors/AlphaProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/BackgroundColorProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/Binarization/BinaryThresholdProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/BlendProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/BrightnessProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/README.md create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/HueProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/KodachromeProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/LomographProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/PolaroidProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/SaturationProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ColorMatrix/SepiaProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/ContrastProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/GlowProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/IImageFilter.cs create mode 100644 src/ImageSharp46/Filters/Processors/ImageFilter.cs create mode 100644 src/ImageSharp46/Filters/Processors/InvertProcessor.cs create mode 100644 src/ImageSharp46/Filters/Processors/VignetteProcessor.cs create mode 100644 src/ImageSharp46/Filters/Saturation.cs create mode 100644 src/ImageSharp46/Filters/Sepia.cs create mode 100644 src/ImageSharp46/Filters/Vignette.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpBitsPerPixel.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpCompression.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpDecoder.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpDecoderCore.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpEncoder.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpEncoderCore.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpFileHeader.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpFormat.cs create mode 100644 src/ImageSharp46/Formats/Bmp/BmpInfoHeader.cs create mode 100644 src/ImageSharp46/Formats/Bmp/README.md create mode 100644 src/ImageSharp46/Formats/Gif/DisposalMethod.cs create mode 100644 src/ImageSharp46/Formats/Gif/GifConstants.cs create mode 100644 src/ImageSharp46/Formats/Gif/GifDecoder.cs create mode 100644 src/ImageSharp46/Formats/Gif/GifDecoderCore.cs create mode 100644 src/ImageSharp46/Formats/Gif/GifEncoder.cs create mode 100644 src/ImageSharp46/Formats/Gif/GifEncoderCore.cs create mode 100644 src/ImageSharp46/Formats/Gif/GifFormat.cs create mode 100644 src/ImageSharp46/Formats/Gif/LzwDecoder.cs create mode 100644 src/ImageSharp46/Formats/Gif/LzwEncoder.cs create mode 100644 src/ImageSharp46/Formats/Gif/PackedField.cs create mode 100644 src/ImageSharp46/Formats/Gif/README.md create mode 100644 src/ImageSharp46/Formats/Gif/Sections/GifGraphicsControlExtension.cs create mode 100644 src/ImageSharp46/Formats/Gif/Sections/GifImageDescriptor.cs create mode 100644 src/ImageSharp46/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs create mode 100644 src/ImageSharp46/Formats/IImageDecoder.cs create mode 100644 src/ImageSharp46/Formats/IImageEncoder.cs create mode 100644 src/ImageSharp46/Formats/IImageFormat.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/Bits.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/Block.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/Bytes.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/Component.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/FDCT.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/GrayImage.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/Huffman.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/IDCT.cs create mode 100644 src/ImageSharp46/Formats/Jpg/Components/YCbCrImage.cs create mode 100644 src/ImageSharp46/Formats/Jpg/JpegConstants.cs create mode 100644 src/ImageSharp46/Formats/Jpg/JpegDecoder.cs create mode 100644 src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs create mode 100644 src/ImageSharp46/Formats/Jpg/JpegEncoder.cs create mode 100644 src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs create mode 100644 src/ImageSharp46/Formats/Jpg/JpegFormat.cs create mode 100644 src/ImageSharp46/Formats/Jpg/JpegSubsample.cs create mode 100644 src/ImageSharp46/Formats/Jpg/README.md create mode 100644 src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs create mode 100644 src/ImageSharp46/Formats/Png/Filters/FilterType.cs create mode 100644 src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs create mode 100644 src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs create mode 100644 src/ImageSharp46/Formats/Png/Filters/SubFilter.cs create mode 100644 src/ImageSharp46/Formats/Png/Filters/UpFilter.cs create mode 100644 src/ImageSharp46/Formats/Png/PngChunk.cs create mode 100644 src/ImageSharp46/Formats/Png/PngChunkTypes.cs create mode 100644 src/ImageSharp46/Formats/Png/PngColorType.cs create mode 100644 src/ImageSharp46/Formats/Png/PngDecoder.cs create mode 100644 src/ImageSharp46/Formats/Png/PngDecoderCore.cs create mode 100644 src/ImageSharp46/Formats/Png/PngEncoder.cs create mode 100644 src/ImageSharp46/Formats/Png/PngEncoderCore.cs create mode 100644 src/ImageSharp46/Formats/Png/PngFormat.cs create mode 100644 src/ImageSharp46/Formats/Png/PngHeader.cs create mode 100644 src/ImageSharp46/Formats/Png/README.md create mode 100644 src/ImageSharp46/Formats/Png/Zlib/Adler32.cs create mode 100644 src/ImageSharp46/Formats/Png/Zlib/Crc32.cs create mode 100644 src/ImageSharp46/Formats/Png/Zlib/IChecksum.cs create mode 100644 src/ImageSharp46/Formats/Png/Zlib/README.md create mode 100644 src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs create mode 100644 src/ImageSharp46/Formats/Png/Zlib/ZlibInflateStream.cs create mode 100644 src/ImageSharp46/IO/BigEndianBitConverter.cs create mode 100644 src/ImageSharp46/IO/EndianBinaryReader.cs create mode 100644 src/ImageSharp46/IO/EndianBinaryWriter.cs create mode 100644 src/ImageSharp46/IO/EndianBitConverter.cs create mode 100644 src/ImageSharp46/IO/Endianness.cs create mode 100644 src/ImageSharp46/IO/LittleEndianBitConverter.cs create mode 100644 src/ImageSharp46/Image.cs create mode 100644 src/ImageSharp46/Image/IImageBase.cs create mode 100644 src/ImageSharp46/Image/IImageFrame.cs create mode 100644 src/ImageSharp46/Image/IImageProcessor.cs create mode 100644 src/ImageSharp46/Image/Image.cs create mode 100644 src/ImageSharp46/Image/ImageBase.cs create mode 100644 src/ImageSharp46/Image/ImageFrame.cs create mode 100644 src/ImageSharp46/Image/ImageIOExtensions.cs create mode 100644 src/ImageSharp46/Image/ImageProcessingExtensions.cs create mode 100644 src/ImageSharp46/Image/ImageProperty.cs create mode 100644 src/ImageSharp46/Image/PixelAccessor.cs create mode 100644 src/ImageSharp46/Image/PixelRow.cs create mode 100644 src/ImageSharp46/ImageProcessor.cs create mode 100644 src/ImageSharp46/ImageSharp46.csproj create mode 100644 src/ImageSharp46/Numerics/Ellipse.cs create mode 100644 src/ImageSharp46/Numerics/LongRational.cs create mode 100644 src/ImageSharp46/Numerics/Point.cs create mode 100644 src/ImageSharp46/Numerics/Rational.cs create mode 100644 src/ImageSharp46/Numerics/Rectangle.cs create mode 100644 src/ImageSharp46/Numerics/SignedRational.cs create mode 100644 src/ImageSharp46/Numerics/Size.cs create mode 100644 src/ImageSharp46/PixelAccessor.cs create mode 100644 src/ImageSharp46/Profiles/Exif/ExifDataType.cs create mode 100644 src/ImageSharp46/Profiles/Exif/ExifParts.cs create mode 100644 src/ImageSharp46/Profiles/Exif/ExifProfile.cs create mode 100644 src/ImageSharp46/Profiles/Exif/ExifReader.cs create mode 100644 src/ImageSharp46/Profiles/Exif/ExifTag.cs create mode 100644 src/ImageSharp46/Profiles/Exif/ExifTagDescriptionAttribute.cs create mode 100644 src/ImageSharp46/Profiles/Exif/ExifValue.cs create mode 100644 src/ImageSharp46/Profiles/Exif/ExifWriter.cs create mode 100644 src/ImageSharp46/Profiles/Exif/README.md create mode 100644 src/ImageSharp46/ProgressEventArgs.cs create mode 100644 src/ImageSharp46/Properties/AssemblyInfo.cs create mode 100644 src/ImageSharp46/Quantizers/IQuantizer.cs create mode 100644 src/ImageSharp46/Quantizers/Octree/OctreeQuantizer.cs create mode 100644 src/ImageSharp46/Quantizers/Octree/Quantizer.cs create mode 100644 src/ImageSharp46/Quantizers/Options/Quantization.cs create mode 100644 src/ImageSharp46/Quantizers/Palette/PaletteQuantizer.cs create mode 100644 src/ImageSharp46/Quantizers/Quantize.cs create mode 100644 src/ImageSharp46/Quantizers/QuantizedImage.cs create mode 100644 src/ImageSharp46/Quantizers/Wu/Box.cs create mode 100644 src/ImageSharp46/Quantizers/Wu/WuQuantizer.cs create mode 100644 src/ImageSharp46/Samplers/AutoOrient.cs create mode 100644 src/ImageSharp46/Samplers/BoxBlur.cs create mode 100644 src/ImageSharp46/Samplers/Crop.cs create mode 100644 src/ImageSharp46/Samplers/DetectEdges.cs create mode 100644 src/ImageSharp46/Samplers/EntropyCrop.cs create mode 100644 src/ImageSharp46/Samplers/Flip.cs create mode 100644 src/ImageSharp46/Samplers/GuassianBlur.cs create mode 100644 src/ImageSharp46/Samplers/GuassianSharpen.cs create mode 100644 src/ImageSharp46/Samplers/OilPainting.cs create mode 100644 src/ImageSharp46/Samplers/Options/AnchorPosition.cs create mode 100644 src/ImageSharp46/Samplers/Options/FlipType.cs create mode 100644 src/ImageSharp46/Samplers/Options/Orientation.cs create mode 100644 src/ImageSharp46/Samplers/Options/ResizeHelper.cs create mode 100644 src/ImageSharp46/Samplers/Options/ResizeMode.cs create mode 100644 src/ImageSharp46/Samplers/Options/ResizeOptions.cs create mode 100644 src/ImageSharp46/Samplers/Options/RotateType.cs create mode 100644 src/ImageSharp46/Samplers/Pad.cs create mode 100644 src/ImageSharp46/Samplers/Pixelate.cs create mode 100644 src/ImageSharp46/Samplers/Processors/CompandingResizeProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/BoxBlurProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/Convolution2DFilter.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/Convolution2PassFilter.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/ConvolutionFilter.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorCompassFilter.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KirschProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/PrewittProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/ScharrProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/SobelProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/GuassianBlurProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Convolution/GuassianSharpenProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/CropProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/EntropyCropProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/FlipProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/IImageSampler.cs create mode 100644 src/ImageSharp46/Samplers/Processors/ImageSampler.cs create mode 100644 src/ImageSharp46/Samplers/Processors/Matrix3x2Processor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/OilPaintingProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/PixelateProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/ResamplingWeightedProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/ResizeProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/RotateProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Processors/SkewProcessor.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/BicubicResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/BoxResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/CatmullRomResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/HermiteResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/IResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/Lanczos2Resampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/Lanczos3Resampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/Lanczos5Resampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/Lanczos8Resampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/MitchellNetravaliResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/NearestNeighborResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/RobidouxResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/RobidouxSharpResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/SplineResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/TriangleResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resamplers/WelchResampler.cs create mode 100644 src/ImageSharp46/Samplers/Resize.cs create mode 100644 src/ImageSharp46/Samplers/Rotate.cs create mode 100644 src/ImageSharp46/Samplers/RotateFlip.cs create mode 100644 src/ImageSharp46/Samplers/Skew.cs create mode 100644 src/ImageSharp46/app.config create mode 100644 src/ImageSharp46/packages.config create mode 100644 tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs create mode 100644 tests/ImageSharp.Tests46/Colors/ColorConversionTests.cs create mode 100644 tests/ImageSharp.Tests46/Colors/ColorTests.cs create mode 100644 tests/ImageSharp.Tests46/FileTestBase.cs create mode 100644 tests/ImageSharp.Tests46/Formats/Bmp/BitmapTests.cs create mode 100644 tests/ImageSharp.Tests46/Formats/GeneralFormatTests.cs create mode 100644 tests/ImageSharp.Tests46/Formats/Png/PngTests.cs create mode 100644 tests/ImageSharp.Tests46/Helpers/GuardTests.cs create mode 100644 tests/ImageSharp.Tests46/Image/ImagePropertyTests.cs create mode 100644 tests/ImageSharp.Tests46/Image/PixelAccessorTests.cs create mode 100644 tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj create mode 100644 tests/ImageSharp.Tests46/Numerics/PointTests.cs create mode 100644 tests/ImageSharp.Tests46/Numerics/RationalTests.cs create mode 100644 tests/ImageSharp.Tests46/Numerics/RectangleTests.cs create mode 100644 tests/ImageSharp.Tests46/Numerics/SignedRationalTests.cs create mode 100644 tests/ImageSharp.Tests46/Numerics/SizeTests.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/AlphaTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/BackgroundColorTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/BinaryThreshold.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/BlackWhiteTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/BlendTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/BrightnessTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/ColorBlindnessTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/ContrastTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/GlowTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/GrayscaleTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/HueTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/InvertTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/KodachromeTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/LomographTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/PolaroidTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/SaturationTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/SepiaTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Filters/VignetteTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/AutoOrientTests.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/CropTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/DetectEdgesTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/EntropyCropTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/FlipTests.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/GuassianBlurTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/GuassianSharpenTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/OilPaintTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/PadTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/PixelateTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/ResizeTests.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/RotateFlipTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/RotateTest.cs create mode 100644 tests/ImageSharp.Tests46/Processors/Samplers/SkewTest.cs create mode 100644 tests/ImageSharp.Tests46/Profiles/Exif/ExifProfileTests.cs create mode 100644 tests/ImageSharp.Tests46/Profiles/Exif/ExifTagDescriptionAttributeTests.cs create mode 100644 tests/ImageSharp.Tests46/Profiles/Exif/ExifValueTests.cs create mode 100644 tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs create mode 100644 tests/ImageSharp.Tests46/TestFile.cs create mode 100644 tests/ImageSharp.Tests46/TestImages.cs create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Bmp/Car.bmp create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Bmp/F.bmp create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Bmp/neg_height.bmp create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Gif/giphy.gif create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Gif/rings.gif create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Calliphora.jpg create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Floorplan.jpeg create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Jpg/cmyk.jpg create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Jpg/exif.jpg create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Jpg/fb.jpg create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Jpg/progress.jpg create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Jpg/turtle.jpg create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Png/blur.png create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Png/indexed.png create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Png/pd.png create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Png/pl.png create mode 100644 tests/ImageSharp.Tests46/TestImages/Formats/Png/splash.png create mode 100644 tests/ImageSharp.Tests46/packages.config diff --git a/ImageSharp.46.sln b/ImageSharp.46.sln new file mode 100644 index 000000000..fe112b9ea --- /dev/null +++ b/ImageSharp.46.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp46", "src\ImageSharp46\ImageSharp46.csproj", "{FBA0B5F6-09C2-4317-8EF6-6ADB9B20E6B1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Tests46", "tests\ImageSharp.Tests46\ImageSharp.Tests46.csproj", "{635E0A15-3893-4763-A7F6-FCCFF85BCCA4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FBA0B5F6-09C2-4317-8EF6-6ADB9B20E6B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBA0B5F6-09C2-4317-8EF6-6ADB9B20E6B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBA0B5F6-09C2-4317-8EF6-6ADB9B20E6B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBA0B5F6-09C2-4317-8EF6-6ADB9B20E6B1}.Release|Any CPU.Build.0 = Release|Any CPU + {635E0A15-3893-4763-A7F6-FCCFF85BCCA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {635E0A15-3893-4763-A7F6-FCCFF85BCCA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {635E0A15-3893-4763-A7F6-FCCFF85BCCA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {635E0A15-3893-4763-A7F6-FCCFF85BCCA4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ImageSharp46/Bootstrapper.cs b/src/ImageSharp46/Bootstrapper.cs new file mode 100644 index 000000000..35a4a5225 --- /dev/null +++ b/src/ImageSharp46/Bootstrapper.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Threading.Tasks; + + using Formats; + + /// + /// Provides initialization code which allows extending the library. + /// + public class Bootstrapper + { + /// + /// A new instance Initializes a new instance of the class. + /// with lazy initialization. + /// + private static readonly Lazy Lazy = new Lazy(() => new Bootstrapper()); + + /// + /// The default list of supported + /// + private readonly List imageFormats; + + /// + /// Prevents a default instance of the class from being created. + /// + private Bootstrapper() + { + this.imageFormats = new List + { + new BmpFormat(), + new JpegFormat(), + new PngFormat(), + new GifFormat() + }; + } + + /// + /// Gets the current bootstrapper instance. + /// + public static Bootstrapper Instance => Lazy.Value; + + /// + /// Gets the collection of supported + /// + public IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormats); + + /// + /// Gets or sets the global parallel options for processing tasks in parallel. + /// + public ParallelOptions ParallelOptions { get; set; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; + + /// + /// Adds a new to the collection of supported image formats. + /// + /// The new format to add. + public void AddImageFormat(IImageFormat format) + { + this.imageFormats.Add(format); + } + } +} diff --git a/src/ImageSharp46/Colors/Color.cs b/src/ImageSharp46/Colors/Color.cs new file mode 100644 index 000000000..c4ea2d762 --- /dev/null +++ b/src/ImageSharp46/Colors/Color.cs @@ -0,0 +1,408 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public partial struct Color : IPackedPixel, IEquatable + { + private const int RedShift = 0; + private const int GreenShift = 8; + private const int BlueShift = 16; + private const int AlphaShift = 24; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5f); + + /// + /// The packed value. + /// + private uint packedValue; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Color(byte r, byte g, byte b, byte a = 255) + { + this.packedValue = Pack(r, g, b, a); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + public Color(string hex) + { + Guard.NotNullOrEmpty(hex, nameof(hex)); + + hex = ToRgbaHex(hex); + + if (hex == null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out this.packedValue)) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + // Order parsed from hex string will be backwards, so reverse it. + this.packedValue = Pack(this.A, this.B, this.G, this.R); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Color(float r, float g, float b, float a = 1) + { + this.packedValue = Pack(r, g, b, a); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Color(Vector3 vector) + { + this.packedValue = Pack(ref vector); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Color(Vector4 vector) + { + this.packedValue = Pack(ref vector); + } + + /// + /// Gets or sets the red component. + /// + public byte R + { + get + { + return (byte)(this.packedValue >> RedShift); + } + + set + { + this.packedValue = this.packedValue & 0xFFFFFF00 | (uint)value << RedShift; + } + } + + /// + /// Gets or sets the green component. + /// + public byte G + { + get + { + return (byte)(this.packedValue >> GreenShift); + } + + set + { + this.packedValue = this.packedValue & 0xFFFF00FF | (uint)value << GreenShift; + } + } + + /// + /// Gets or sets the blue component. + /// + public byte B + { + get + { + return (byte)(this.packedValue >> BlueShift); + } + + set + { + this.packedValue = this.packedValue & 0xFF00FFFF | (uint)value << BlueShift; + } + } + + /// + /// Gets or sets the alpha component. + /// + public byte A + { + get + { + return (byte)(this.packedValue >> AlphaShift); + } + + set + { + this.packedValue = this.packedValue & 0x00FFFFFF | (uint)value << AlphaShift; + } + } + + /// + public uint PackedValue + { + get + { + return this.packedValue; + } + + set + { + this.packedValue = value; + } + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Color left, Color right) + { + return left.packedValue == right.packedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator !=(Color left, Color right) + { + return left.packedValue != right.packedValue; + } + + /// + /// Creates a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static Color FromHex(string hex) + { + return new Color(hex); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.packedValue = Pack(x, y, z, w); + } + + /// + /// Converts the value of this instance to a hexadecimal string. + /// + /// A hexadecimal string representation of the value. + public string ToHex() + { + uint hexOrder = Pack(this.A, this.B, this.G, this.R); + return hexOrder.ToString("X8"); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = this.B; + bytes[startIndex + 1] = this.G; + bytes[startIndex + 2] = this.R; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = this.B; + bytes[startIndex + 1] = this.G; + bytes[startIndex + 2] = this.R; + bytes[startIndex + 3] = this.A; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = this.R; + bytes[startIndex + 1] = this.G; + bytes[startIndex + 2] = this.B; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = this.R; + bytes[startIndex + 1] = this.G; + bytes[startIndex + 2] = this.B; + bytes[startIndex + 3] = this.A; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.packedValue = Pack(ref vector); + } + + /// + public Vector4 ToVector4() + { + return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + } + + /// + public override bool Equals(object obj) + { + return (obj is Color) && this.Equals((Color)obj); + } + + /// + public bool Equals(Color other) + { + return this.packedValue == other.packedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.packedValue.GetHashCode(); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + /// The containing the packed values. + private static uint Pack(ref Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector *= MaxBytes; + vector += Half; + return (uint)(((byte)vector.X << RedShift) + | ((byte)vector.Y << GreenShift) + | ((byte)vector.Z << BlueShift) + | (byte)vector.W << AlphaShift); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + /// The containing the packed values. + private static uint Pack(ref Vector3 vector) + { + Vector4 value = new Vector4(vector, 1); + return Pack(ref value); + } + + /// + /// Packs the four floats into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The + private static uint Pack(float x, float y, float z, float w) + { + Vector4 value = new Vector4(x, y, z, w); + return Pack(ref value); + } + + /// + /// Packs the four floats into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The + private static uint Pack(byte x, byte y, byte z, byte w) + { + return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); + } + + /// + /// Converts the specified hex value to an rrggbbaa hex value. + /// + /// The hex value to convert. + /// + /// A rrggbbaa hex value. + /// + private static string ToRgbaHex(string hex) + { + hex = hex.StartsWith("#") ? hex.Substring(1) : hex; + + if (hex.Length == 8) + { + return hex; + } + + if (hex.Length == 6) + { + return hex + "FF"; + } + + if (hex.Length < 3 || hex.Length > 4) + { + return null; + } + + string red = char.ToString(hex[0]); + string green = char.ToString(hex[1]); + string blue = char.ToString(hex[2]); + string alpha = hex.Length == 3 ? "F" : char.ToString(hex[3]); + + return red + red + green + green + blue + blue + alpha + alpha; + } + } +} diff --git a/src/ImageSharp46/Colors/ColorConstants.cs b/src/ImageSharp46/Colors/ColorConstants.cs new file mode 100644 index 000000000..f3b01f466 --- /dev/null +++ b/src/ImageSharp46/Colors/ColorConstants.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + + /// + /// Provides useful color definitions. + /// + public static class ColorConstants + { + /// + /// Provides a lazy, one time method of returning the colors. + /// + private static readonly Lazy SafeColors = new Lazy(GetWebSafeColors); + + /// + /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. + /// + public static Color[] WebSafeColors => SafeColors.Value; + + /// + /// Returns an array of web safe colors. + /// + /// + private static Color[] GetWebSafeColors() + { + return new List + { + Color.AliceBlue, + Color.AntiqueWhite, + Color.Aqua, + Color.Aquamarine, + Color.Azure, + Color.Beige, + Color.Bisque, + Color.Black, + Color.BlanchedAlmond, + Color.Blue, + Color.BlueViolet, + Color.Brown, + Color.BurlyWood, + Color.CadetBlue, + Color.Chartreuse, + Color.Chocolate, + Color.Coral, + Color.CornflowerBlue, + Color.Cornsilk, + Color.Crimson, + Color.Cyan, + Color.DarkBlue, + Color.DarkCyan, + Color.DarkGoldenrod, + Color.DarkGray, + Color.DarkGreen, + Color.DarkKhaki, + Color.DarkMagenta, + Color.DarkOliveGreen, + Color.DarkOrange, + Color.DarkOrchid, + Color.DarkRed, + Color.DarkSalmon, + Color.DarkSeaGreen, + Color.DarkSlateBlue, + Color.DarkSlateGray, + Color.DarkTurquoise, + Color.DarkViolet, + Color.DeepPink, + Color.DeepSkyBlue, + Color.DimGray, + Color.DodgerBlue, + Color.Firebrick, + Color.FloralWhite, + Color.ForestGreen, + Color.Fuchsia, + Color.Gainsboro, + Color.GhostWhite, + Color.Gold, + Color.Goldenrod, + Color.Gray, + Color.Green, + Color.GreenYellow, + Color.Honeydew, + Color.HotPink, + Color.IndianRed, + Color.Indigo, + Color.Ivory, + Color.Khaki, + Color.Lavender, + Color.LavenderBlush, + Color.LawnGreen, + Color.LemonChiffon, + Color.LightBlue, + Color.LightCoral, + Color.LightCyan, + Color.LightGoldenrodYellow, + Color.LightGray, + Color.LightGreen, + Color.LightPink, + Color.LightSalmon, + Color.LightSeaGreen, + Color.LightSkyBlue, + Color.LightSlateGray, + Color.LightSteelBlue, + Color.LightYellow, + Color.Lime, + Color.LimeGreen, + Color.Linen, + Color.Magenta, + Color.Maroon, + Color.MediumAquamarine, + Color.MediumBlue, + Color.MediumOrchid, + Color.MediumPurple, + Color.MediumSeaGreen, + Color.MediumSlateBlue, + Color.MediumSpringGreen, + Color.MediumTurquoise, + Color.MediumVioletRed, + Color.MidnightBlue, + Color.MintCream, + Color.MistyRose, + Color.Moccasin, + Color.NavajoWhite, + Color.Navy, + Color.OldLace, + Color.Olive, + Color.OliveDrab, + Color.Orange, + Color.OrangeRed, + Color.Orchid, + Color.PaleGoldenrod, + Color.PaleGreen, + Color.PaleTurquoise, + Color.PaleVioletRed, + Color.PapayaWhip, + Color.PeachPuff, + Color.Peru, + Color.Pink, + Color.Plum, + Color.PowderBlue, + Color.Purple, + Color.RebeccaPurple, + Color.Red, + Color.RosyBrown, + Color.RoyalBlue, + Color.SaddleBrown, + Color.Salmon, + Color.SandyBrown, + Color.SeaGreen, + Color.SeaShell, + Color.Sienna, + Color.Silver, + Color.SkyBlue, + Color.SlateBlue, + Color.SlateGray, + Color.Snow, + Color.SpringGreen, + Color.SteelBlue, + Color.Tan, + Color.Teal, + Color.Thistle, + Color.Tomato, + Color.Transparent, + Color.Turquoise, + Color.Violet, + Color.Wheat, + Color.White, + Color.WhiteSmoke, + Color.Yellow, + Color.YellowGreen + }.ToArray(); + } + } +} diff --git a/src/ImageSharp46/Colors/ColorDefinitions.cs b/src/ImageSharp46/Colors/ColorDefinitions.cs new file mode 100644 index 000000000..46e0f8709 --- /dev/null +++ b/src/ImageSharp46/Colors/ColorDefinitions.cs @@ -0,0 +1,728 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public partial struct Color + { + /// + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. + /// + public static readonly Color AliceBlue = new Color(240, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly Color AntiqueWhite = new Color(250, 235, 215, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Color Aqua = new Color(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly Color Aquamarine = new Color(127, 255, 212, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly Color Azure = new Color(240, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly Color Beige = new Color(245, 245, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly Color Bisque = new Color(255, 228, 196, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly Color Black = new Color(0, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. + /// + public static readonly Color BlanchedAlmond = new Color(255, 235, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly Color Blue = new Color(0, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. + /// + public static readonly Color BlueViolet = new Color(138, 43, 226, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A52A2A. + /// + public static readonly Color Brown = new Color(165, 42, 42, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DEB887. + /// + public static readonly Color BurlyWood = new Color(222, 184, 135, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. + /// + public static readonly Color CadetBlue = new Color(95, 158, 160, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFF00. + /// + public static readonly Color Chartreuse = new Color(127, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2691E. + /// + public static readonly Color Chocolate = new Color(210, 105, 30, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF7F50. + /// + public static readonly Color Coral = new Color(255, 127, 80, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6495ED. + /// + public static readonly Color CornflowerBlue = new Color(100, 149, 237, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. + /// + public static readonly Color Cornsilk = new Color(255, 248, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DC143C. + /// + public static readonly Color Crimson = new Color(220, 20, 60, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Color Cyan = new Color(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00008B. + /// + public static readonly Color DarkBlue = new Color(0, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008B8B. + /// + public static readonly Color DarkCyan = new Color(0, 139, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B8860B. + /// + public static readonly Color DarkGoldenrod = new Color(184, 134, 11, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly Color DarkGray = new Color(169, 169, 169, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #006400. + /// + public static readonly Color DarkGreen = new Color(0, 100, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BDB76B. + /// + public static readonly Color DarkKhaki = new Color(189, 183, 107, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B008B. + /// + public static readonly Color DarkMagenta = new Color(139, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #556B2F. + /// + public static readonly Color DarkOliveGreen = new Color(85, 107, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF8C00. + /// + public static readonly Color DarkOrange = new Color(255, 140, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9932CC. + /// + public static readonly Color DarkOrchid = new Color(153, 50, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B0000. + /// + public static readonly Color DarkRed = new Color(139, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E9967A. + /// + public static readonly Color DarkSalmon = new Color(233, 150, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8FBC8B. + /// + public static readonly Color DarkSeaGreen = new Color(143, 188, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #483D8B. + /// + public static readonly Color DarkSlateBlue = new Color(72, 61, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly Color DarkSlateGray = new Color(47, 79, 79, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00CED1. + /// + public static readonly Color DarkTurquoise = new Color(0, 206, 209, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9400D3. + /// + public static readonly Color DarkViolet = new Color(148, 0, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF1493. + /// + public static readonly Color DeepPink = new Color(255, 20, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00BFFF. + /// + public static readonly Color DeepSkyBlue = new Color(0, 191, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly Color DimGray = new Color(105, 105, 105, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #1E90FF. + /// + public static readonly Color DodgerBlue = new Color(30, 144, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B22222. + /// + public static readonly Color Firebrick = new Color(178, 34, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. + /// + public static readonly Color FloralWhite = new Color(255, 250, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #228B22. + /// + public static readonly Color ForestGreen = new Color(34, 139, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Color Fuchsia = new Color(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. + /// + public static readonly Color Gainsboro = new Color(220, 220, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. + /// + public static readonly Color GhostWhite = new Color(248, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFD700. + /// + public static readonly Color Gold = new Color(255, 215, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DAA520. + /// + public static readonly Color Goldenrod = new Color(218, 165, 32, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Color Gray = new Color(128, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly Color Green = new Color(0, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. + /// + public static readonly Color GreenYellow = new Color(173, 255, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFF0. + /// + public static readonly Color Honeydew = new Color(240, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF69B4. + /// + public static readonly Color HotPink = new Color(255, 105, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. + /// + public static readonly Color IndianRed = new Color(205, 92, 92, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4B0082. + /// + public static readonly Color Indigo = new Color(75, 0, 130, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. + /// + public static readonly Color Ivory = new Color(255, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0E68C. + /// + public static readonly Color Khaki = new Color(240, 230, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. + /// + public static readonly Color Lavender = new Color(230, 230, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. + /// + public static readonly Color LavenderBlush = new Color(255, 240, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7CFC00. + /// + public static readonly Color LawnGreen = new Color(124, 252, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFACD. + /// + public static readonly Color LemonChiffon = new Color(255, 250, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. + /// + public static readonly Color LightBlue = new Color(173, 216, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F08080. + /// + public static readonly Color LightCoral = new Color(240, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. + /// + public static readonly Color LightCyan = new Color(224, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. + /// + public static readonly Color LightGoldenrodYellow = new Color(250, 250, 210, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly Color LightGray = new Color(211, 211, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #90EE90. + /// + public static readonly Color LightGreen = new Color(144, 238, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFB6C1. + /// + public static readonly Color LightPink = new Color(255, 182, 193, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA07A. + /// + public static readonly Color LightSalmon = new Color(255, 160, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #20B2AA. + /// + public static readonly Color LightSeaGreen = new Color(32, 178, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEFA. + /// + public static readonly Color LightSkyBlue = new Color(135, 206, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly Color LightSlateGray = new Color(119, 136, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0C4DE. + /// + public static readonly Color LightSteelBlue = new Color(176, 196, 222, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. + /// + public static readonly Color LightYellow = new Color(255, 255, 224, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly Color Lime = new Color(0, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #32CD32. + /// + public static readonly Color LimeGreen = new Color(50, 205, 50, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. + /// + public static readonly Color Linen = new Color(250, 240, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Color Magenta = new Color(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800000. + /// + public static readonly Color Maroon = new Color(128, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #66CDAA. + /// + public static readonly Color MediumAquamarine = new Color(102, 205, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000CD. + /// + public static readonly Color MediumBlue = new Color(0, 0, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BA55D3. + /// + public static readonly Color MediumOrchid = new Color(186, 85, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9370DB. + /// + public static readonly Color MediumPurple = new Color(147, 112, 219, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #3CB371. + /// + public static readonly Color MediumSeaGreen = new Color(60, 179, 113, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7B68EE. + /// + public static readonly Color MediumSlateBlue = new Color(123, 104, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FA9A. + /// + public static readonly Color MediumSpringGreen = new Color(0, 250, 154, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #48D1CC. + /// + public static readonly Color MediumTurquoise = new Color(72, 209, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C71585. + /// + public static readonly Color MediumVioletRed = new Color(199, 21, 133, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #191970. + /// + public static readonly Color MidnightBlue = new Color(25, 25, 112, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. + /// + public static readonly Color MintCream = new Color(245, 255, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. + /// + public static readonly Color MistyRose = new Color(255, 228, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. + /// + public static readonly Color Moccasin = new Color(255, 228, 181, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. + /// + public static readonly Color NavajoWhite = new Color(255, 222, 173, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly Color Navy = new Color(0, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. + /// + public static readonly Color OldLace = new Color(253, 245, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly Color Olive = new Color(128, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6B8E23. + /// + public static readonly Color OliveDrab = new Color(107, 142, 35, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA500. + /// + public static readonly Color Orange = new Color(255, 165, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF4500. + /// + public static readonly Color OrangeRed = new Color(255, 69, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DA70D6. + /// + public static readonly Color Orchid = new Color(218, 112, 214, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. + /// + public static readonly Color PaleGoldenrod = new Color(238, 232, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #98FB98. + /// + public static readonly Color PaleGreen = new Color(152, 251, 152, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. + /// + public static readonly Color PaleTurquoise = new Color(175, 238, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DB7093. + /// + public static readonly Color PaleVioletRed = new Color(219, 112, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. + /// + public static readonly Color PapayaWhip = new Color(255, 239, 213, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. + /// + public static readonly Color PeachPuff = new Color(255, 218, 185, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD853F. + /// + public static readonly Color Peru = new Color(205, 133, 63, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. + /// + public static readonly Color Pink = new Color(255, 192, 203, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. + /// + public static readonly Color Plum = new Color(221, 160, 221, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. + /// + public static readonly Color PowderBlue = new Color(176, 224, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly Color Purple = new Color(128, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0. + /// + public static readonly Color RebeccaPurple = new Color(102, 51, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly Color Red = new Color(255, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. + /// + public static readonly Color RosyBrown = new Color(188, 143, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4169E1. + /// + public static readonly Color RoyalBlue = new Color(65, 105, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B4513. + /// + public static readonly Color SaddleBrown = new Color(139, 69, 19, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FA8072. + /// + public static readonly Color Salmon = new Color(250, 128, 114, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F4A460. + /// + public static readonly Color SandyBrown = new Color(244, 164, 96, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2E8B57. + /// + public static readonly Color SeaGreen = new Color(46, 139, 87, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. + /// + public static readonly Color SeaShell = new Color(255, 245, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A0522D. + /// + public static readonly Color Sienna = new Color(160, 82, 45, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly Color Silver = new Color(192, 192, 192, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEEB. + /// + public static readonly Color SkyBlue = new Color(135, 206, 235, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. + /// + public static readonly Color SlateBlue = new Color(106, 90, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly Color SlateGray = new Color(112, 128, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAFA. + /// + public static readonly Color Snow = new Color(255, 250, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF7F. + /// + public static readonly Color SpringGreen = new Color(0, 255, 127, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4682B4. + /// + public static readonly Color SteelBlue = new Color(70, 130, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2B48C. + /// + public static readonly Color Tan = new Color(210, 180, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly Color Teal = new Color(0, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. + /// + public static readonly Color Thistle = new Color(216, 191, 216, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF6347. + /// + public static readonly Color Tomato = new Color(255, 99, 71, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Color Transparent = new Color(255, 255, 255, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #40E0D0. + /// + public static readonly Color Turquoise = new Color(64, 224, 208, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EE82EE. + /// + public static readonly Color Violet = new Color(238, 130, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. + /// + public static readonly Color Wheat = new Color(245, 222, 179, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Color White = new Color(255, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. + /// + public static readonly Color WhiteSmoke = new Color(245, 245, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly Color Yellow = new Color(255, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9ACD32. + /// + public static readonly Color YellowGreen = new Color(154, 205, 50, 255); + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Colors/ColorTransforms.cs b/src/ImageSharp46/Colors/ColorTransforms.cs new file mode 100644 index 000000000..e710b69e9 --- /dev/null +++ b/src/ImageSharp46/Colors/ColorTransforms.cs @@ -0,0 +1,74 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public partial struct Color + { + /// + /// Blends two colors by multiplication. + /// + /// The source color is multiplied by the destination color and replaces the destination. + /// The resultant color is always at least as dark as either the source or destination color. + /// Multiplying any color with black results in black. Multiplying any color with white preserves the + /// original color. + /// + /// + /// The source color. + /// The destination color. + /// + /// The . + /// + public static Color Multiply(Color source, Color destination) + { + if (destination == Color.Black) + { + return Color.Black; + } + + if (destination == Color.White) + { + return source; + } + + // TODO: This will use less memory than using Vector4 + // but we should test speed vs memory to see which is best balance. + byte r = (byte)(source.R * destination.R).Clamp(0, 255); + byte g = (byte)(source.G * destination.G).Clamp(0, 255); + byte b = (byte)(source.B * destination.B).Clamp(0, 255); + byte a = (byte)(source.A * destination.A).Clamp(0, 255); + + return new Color(r, g, b, a); + } + + /// + /// Linearly interpolates from one color to another based on the given weighting. + /// + /// The first color value. + /// The second color value. + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + /// + /// The + /// + public static Color Lerp(Color from, Color to, float amount) + { + return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount)); + } + } +} diff --git a/src/ImageSharp46/Colors/ColorspaceTransforms.cs b/src/ImageSharp46/Colors/ColorspaceTransforms.cs new file mode 100644 index 000000000..0c70dd98b --- /dev/null +++ b/src/ImageSharp46/Colors/ColorspaceTransforms.cs @@ -0,0 +1,292 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public partial struct Color + { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001F; + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Bgra32 color) + { + return new Color(color.R, color.G, color.B, color.A); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Cmyk cmykColor) + { + float r = (1 - cmykColor.C) * (1 - cmykColor.K); + float g = (1 - cmykColor.M) * (1 - cmykColor.K); + float b = (1 - cmykColor.Y) * (1 - cmykColor.K); + return new Color(r, g, b, 1); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(YCbCr color) + { + byte y = color.Y; + int cb = color.Cb - 128; + int cr = color.Cr - 128; + + byte r = (byte)(y + (1.402F * cr)).Clamp(0, 255); + byte g = (byte)(y - (0.34414F * cb) - (0.71414F * cr)).Clamp(0, 255); + byte b = (byte)(y + (1.772F * cb)).Clamp(0, 255); + + return new Color(r, g, b); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(CieXyz color) + { + float x = color.X / 100F; + float y = color.Y / 100F; + float z = color.Z / 100F; + + // Then XYZ to RGB (multiplication by 100 was done above already) + float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); + float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); + float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); + + Vector4 vector = new Vector4(r, g, b, 1).Compress(); + return new Color(vector); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Hsv color) + { + float s = color.S; + float v = color.V; + + if (Math.Abs(s) < Epsilon) + { + return new Color(v, v, v, 1); + } + + float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60; + int i = (int)Math.Truncate(h); + float f = h - i; + + float p = v * (1.0F - s); + float q = v * (1.0F - (s * f)); + float t = v * (1.0F - (s * (1.0F - f))); + + float r, g, b; + switch (i) + { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: + r = v; + g = p; + b = q; + break; + } + + return new Color(r, g, b, 1); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Hsl color) + { + float rangedH = color.H / 360F; + float r = 0; + float g = 0; + float b = 0; + float s = color.S; + float l = color.L; + + if (Math.Abs(l) > Epsilon) + { + if (Math.Abs(s) < Epsilon) + { + r = g = b = l; + } + else + { + float temp2 = (l < 0.5f) ? l * (1f + s) : l + s - (l * s); + float temp1 = (2f * l) - temp2; + + r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); + g = GetColorComponent(temp1, temp2, rangedH); + b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); + } + } + + return new Color(r, g, b, 1); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(CieLab cieLabColor) + { + // First convert back to XYZ... + float y = (cieLabColor.L + 16F) / 116F; + float x = (cieLabColor.A / 500F) + y; + float z = y - (cieLabColor.B / 200F); + + float x3 = x * x * x; + float y3 = y * y * y; + float z3 = z * z * z; + + x = x3 > 0.008856F ? x3 : (x - 0.137931F) / 7.787F; + y = (cieLabColor.L > 7.999625F) ? y3 : (cieLabColor.L / 903.3F); + z = (z3 > 0.008856F) ? z3 : (z - 0.137931F) / 7.787F; + + x *= 0.95047F; + z *= 1.08883F; + + // Then XYZ to RGB (multiplication by 100 was done above already) + float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); + float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); + float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); + + return new Color(new Vector4(r, g, b, 1F).Compress()); + } + + /// + /// Gets the color component from the given values. + /// + /// The first value. + /// The second value. + /// The third value. + /// + /// The . + /// + private static float GetColorComponent(float first, float second, float third) + { + third = MoveIntoRange(third); + if (third < 0.1666667F) + { + return first + ((second - first) * 6.0f * third); + } + + if (third < 0.5) + { + return second; + } + + if (third < 0.6666667F) + { + return first + ((second - first) * (0.6666667F - third) * 6.0f); + } + + return first; + } + + /// + /// Moves the specific value within the acceptable range for + /// conversion. + /// Used for converting colors to this type. + /// + /// The value to shift. + /// + /// The . + /// + private static float MoveIntoRange(float value) + { + if (value < 0.0) + { + value += 1.0f; + } + else if (value > 1.0) + { + value -= 1.0f; + } + + return value; + } + } +} diff --git a/src/ImageSharp46/Colors/Colorspaces/Bgra32.cs b/src/ImageSharp46/Colors/Colorspaces/Bgra32.cs new file mode 100644 index 000000000..ff9203b03 --- /dev/null +++ b/src/ImageSharp46/Colors/Colorspaces/Bgra32.cs @@ -0,0 +1,167 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an BGRA (blue, green, red, alpha) color. + /// + public struct Bgra32 : IEquatable + { + /// + /// Represents a 32 bit that has B, G, R, and A values set to zero. + /// + public static readonly Bgra32 Empty = default(Bgra32); + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The blue component of this . + /// The green component of this . + /// The red component of this . + /// The alpha component of this . + public Bgra32(byte b, byte g, byte r, byte a = 255) + : this() + { + this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, new Vector4(255)); + } + + /// + /// Gets the blue component of the color + /// + public byte B => (byte)this.backingVector.X; + + /// + /// Gets the green component of the color + /// + public byte G => (byte)this.backingVector.Y; + + /// + /// Gets the red component of the color + /// + public byte R => (byte)this.backingVector.Z; + + /// + /// Gets the alpha component of the color + /// + public byte A => (byte)this.backingVector.W; + + /// + /// Gets the integer representation of the color. + /// + public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24); + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator Bgra32(Color color) + { + return new Bgra32(color.B, color.G, color.R, color.A); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgra32 left, Bgra32 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgra32 left, Bgra32 right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + if (obj is Bgra32) + { + Bgra32 color = (Bgra32)obj; + + return this.backingVector == color.backingVector; + } + + return false; + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Bgra32 [ Empty ]"; + } + + return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]"; + } + + /// + public bool Equals(Bgra32 other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageSharp46/Colors/Colorspaces/CieLab.cs b/src/ImageSharp46/Colors/Colorspaces/CieLab.cs new file mode 100644 index 000000000..b0c042a3c --- /dev/null +++ b/src/ImageSharp46/Colors/Colorspaces/CieLab.cs @@ -0,0 +1,192 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an CIE LAB 1976 color. + /// + /// + public struct CieLab : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has L, A, B values set to zero. + /// + public static readonly CieLab Empty = default(CieLab); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + public CieLab(float l, float a, float b) + : this() + { + this.backingVector = Vector3.Clamp(new Vector3(l, a, b), new Vector3(0, -100, -100), new Vector3(100)); + } + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public float L => this.backingVector.X; + + /// + /// Gets the a color component. + /// Negative is green, positive magenta. + /// + public float A => this.backingVector.Y; + + /// + /// Gets the b color component. + /// Negative is blue, positive is yellow + /// + public float B => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator CieLab(Color color) + { + // First convert to CIE XYZ + Vector4 vector = color.ToVector4().Expand(); + float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F); + float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F); + float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F); + + // Now to LAB + x /= 0.95047F; + //y /= 1F; + z /= 1.08883F; + + x = x > 0.008856F ? (float)Math.Pow(x, 0.3333333F) : ((903.3F * x) + 16F) / 116F; + y = y > 0.008856F ? (float)Math.Pow(y, 0.3333333F) : ((903.3F * y) + 16F) / 116F; + z = z > 0.008856F ? (float)Math.Pow(z, 0.3333333F) : ((903.3F * z) + 16F) / 116F; + + float l = Math.Max(0, (116F * y) - 16F); + float a = 500F * (x - y); + float b = 200F * (y - z); + + return new CieLab(l, a, b); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(CieLab left, CieLab right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(CieLab left, CieLab right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieLab [Empty]"; + } + + return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is CieLab) + { + return this.Equals((CieLab)obj); + } + + return false; + } + + /// + public bool Equals(CieLab other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(CieLab other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(CieLab color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageSharp46/Colors/Colorspaces/CieXyz.cs b/src/ImageSharp46/Colors/Colorspaces/CieXyz.cs new file mode 100644 index 000000000..8e9d5f796 --- /dev/null +++ b/src/ImageSharp46/Colors/Colorspaces/CieXyz.cs @@ -0,0 +1,184 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an CIE 1931 color + /// + /// + public struct CieXyz : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has Y, Cb, and Cr values set to zero. + /// + public static readonly CieXyz Empty = default(CieXyz); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The y luminance component. + /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative + /// Z is quasi-equal to blue stimulation, or the S cone of the human eye. + public CieXyz(float x, float y, float z) + : this() + { + // Not clamping as documentation about this space seems to indicate "usual" ranges + this.backingVector = new Vector3(x, y, z); + } + + /// + /// Gets the Y luminance component. + /// A value ranging between 380 and 780. + /// + public float X => this.backingVector.X; + + /// + /// Gets the Cb chroma component. + /// A value ranging between 380 and 780. + /// + public float Y => this.backingVector.Y; + + /// + /// Gets the Cr chroma component. + /// A value ranging between 380 and 780. + /// + public float Z => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator CieXyz(Color color) + { + Vector4 vector = color.ToVector4().Expand(); + + float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F); + float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F); + float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F); + + x *= 100F; + y *= 100F; + z *= 100F; + + return new CieXyz(x, y, z); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(CieXyz left, CieXyz right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(CieXyz left, CieXyz right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieXyz [ Empty ]"; + } + + return $"CieXyz [ X={this.X:#0.##}, Y={this.Y:#0.##}, Z={this.Z:#0.##} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is CieXyz) + { + return this.Equals((CieXyz)obj); + } + + return false; + } + + /// + public bool Equals(CieXyz other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(CieXyz other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(CieXyz color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageSharp46/Colors/Colorspaces/Cmyk.cs b/src/ImageSharp46/Colors/Colorspaces/Cmyk.cs new file mode 100644 index 000000000..15bea263c --- /dev/null +++ b/src/ImageSharp46/Colors/Colorspaces/Cmyk.cs @@ -0,0 +1,195 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an CMYK (cyan, magenta, yellow, keyline) color. + /// + public struct Cmyk : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has C, M, Y, and K values set to zero. + /// + public static readonly Cmyk Empty = default(Cmyk); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The cyan component. + /// The magenta component. + /// The yellow component. + /// The keyline black component. + public Cmyk(float c, float m, float y, float k) + : this() + { + this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), Vector4.Zero, Vector4.One); + } + + /// + /// Gets the cyan color component. + /// A value ranging between 0 and 1. + /// + public float C => this.backingVector.X; + + /// + /// Gets the magenta color component. + /// A value ranging between 0 and 1. + /// + public float M => this.backingVector.Y; + + /// + /// Gets the yellow color component. + /// A value ranging between 0 and 1. + /// + public float Y => this.backingVector.Z; + + /// + /// Gets the keyline black color component. + /// A value ranging between 0 and 1. + /// + public float K => this.backingVector.W; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator Cmyk(Color color) + { + float c = 1f - (color.R / 255F); + float m = 1f - (color.G / 255F); + float y = 1f - (color.B / 255F); + + float k = Math.Min(c, Math.Min(m, y)); + + if (Math.Abs(k - 1.0f) <= Epsilon) + { + return new Cmyk(0, 0, 0, 1); + } + + c = (c - k) / (1 - k); + m = (m - k) / (1 - k); + y = (y - k) / (1 - k); + + return new Cmyk(c, m, y, k); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Cmyk left, Cmyk right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Cmyk left, Cmyk right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Cmyk [Empty]"; + } + + return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Cmyk) + { + return this.Equals((Cmyk)obj); + } + + return false; + } + + /// + public bool Equals(Cmyk other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(Cmyk other, float precision) + { + Vector4 result = Vector4.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision + && result.W < precision; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Cmyk color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageSharp46/Colors/Colorspaces/Hsl.cs b/src/ImageSharp46/Colors/Colorspaces/Hsl.cs new file mode 100644 index 000000000..1cc54ec8c --- /dev/null +++ b/src/ImageSharp46/Colors/Colorspaces/Hsl.cs @@ -0,0 +1,213 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents a Hsl (hue, saturation, lightness) color. + /// + public struct Hsl : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has H, S, and L values set to zero. + /// + public static readonly Hsl Empty = default(Hsl); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001F; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The h hue component. + /// The s saturation component. + /// The l value (lightness) component. + public Hsl(float h, float s, float l) + { + this.backingVector = Vector3.Clamp(new Vector3(h, s, l), Vector3.Zero, new Vector3(360, 1, 1)); + } + + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public float H => this.backingVector.X; + + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public float S => this.backingVector.Y; + + /// + /// Gets the lightness component. + /// A value ranging between 0 and 1. + /// + public float L => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Hsl(Color color) + { + float r = color.R / 255F; + float g = color.G / 255F; + float b = color.B / 255F; + + float max = Math.Max(r, Math.Max(g, b)); + float min = Math.Min(r, Math.Min(g, b)); + float chroma = max - min; + float h = 0; + float s = 0; + float l = (max + min) / 2; + + if (Math.Abs(chroma) < Epsilon) + { + return new Hsl(0, s, l); + } + + if (Math.Abs(r - max) < Epsilon) + { + h = (g - b) / chroma; + } + else if (Math.Abs(g - max) < Epsilon) + { + h = 2 + ((b - r) / chroma); + } + else if (Math.Abs(b - max) < Epsilon) + { + h = 4 + ((r - g) / chroma); + } + + h *= 60; + if (h < 0.0) + { + h += 360; + } + + if (l <= .5f) + { + s = chroma / (max + min); + } + else + { + s = chroma / (2 - chroma); + } + + return new Hsl(h, s, l); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Hsl left, Hsl right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Hsl left, Hsl right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Hsl [ Empty ]"; + } + + return $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={this.L:#0.##} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Hsl) + { + return this.Equals((Hsl)obj); + } + + return false; + } + + /// + public bool Equals(Hsl other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(Hsl other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Hsl color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageSharp46/Colors/Colorspaces/Hsv.cs b/src/ImageSharp46/Colors/Colorspaces/Hsv.cs new file mode 100644 index 000000000..b55e07e6b --- /dev/null +++ b/src/ImageSharp46/Colors/Colorspaces/Hsv.cs @@ -0,0 +1,206 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). + /// + public struct Hsv : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has H, S, and V values set to zero. + /// + public static readonly Hsv Empty = default(Hsv); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001F; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The h hue component. + /// The s saturation component. + /// The v value (brightness) component. + public Hsv(float h, float s, float v) + { + this.backingVector = Vector3.Clamp(new Vector3(h, s, v), Vector3.Zero, new Vector3(360, 1, 1)); + } + + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public float H => this.backingVector.X; + + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public float S => this.backingVector.Y; + + /// + /// Gets the value (brightness) component. + /// A value ranging between 0 and 1. + /// + public float V => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Hsv(Color color) + { + float r = color.R / 255F; + float g = color.G / 255F; + float b = color.B / 255F; + + float max = Math.Max(r, Math.Max(g, b)); + float min = Math.Min(r, Math.Min(g, b)); + float chroma = max - min; + float h = 0; + float s = 0; + float v = max; + + if (Math.Abs(chroma) < Epsilon) + { + return new Hsv(0, s, v); + } + + if (Math.Abs(r - max) < Epsilon) + { + h = (g - b) / chroma; + } + else if (Math.Abs(g - max) < Epsilon) + { + h = 2 + ((b - r) / chroma); + } + else if (Math.Abs(b - max) < Epsilon) + { + h = 4 + ((r - g) / chroma); + } + + h *= 60; + if (h < 0.0) + { + h += 360; + } + + s = chroma / v; + + return new Hsv(h, s, v); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Hsv left, Hsv right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Hsv left, Hsv right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Hsv [ Empty ]"; + } + + return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Hsv) + { + return this.Equals((Hsv)obj); + } + + return false; + } + + /// + public bool Equals(Hsv other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(Hsv other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Hsv color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageSharp46/Colors/Colorspaces/IAlmostEquatable.cs b/src/ImageSharp46/Colors/Colorspaces/IAlmostEquatable.cs new file mode 100644 index 000000000..1163657ed --- /dev/null +++ b/src/ImageSharp46/Colors/Colorspaces/IAlmostEquatable.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Defines a generalized method that a value type or class implements to create + /// a type-specific method for determining approximate equality of instances. + /// + /// The type of objects to compare. + /// The object specifying the type to specify precision with. + public interface IAlmostEquatable + where TPrecision : struct, IComparable + { + /// + /// Indicates whether the current object is equal to another object of the same type + /// when compared to the specified precision level. + /// + /// An object to compare with this object. + /// The object specifying the level of precision. + /// + /// true if the current object is equal to the other parameter; otherwise, false. + /// + bool AlmostEquals(TColor other, TPrecision precision); + } +} diff --git a/src/ImageSharp46/Colors/Colorspaces/YCbCr.cs b/src/ImageSharp46/Colors/Colorspaces/YCbCr.cs new file mode 100644 index 000000000..c9a0872c5 --- /dev/null +++ b/src/ImageSharp46/Colors/Colorspaces/YCbCr.cs @@ -0,0 +1,157 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + + /// + /// Represents an YCbCr (luminance, blue chroma, red chroma) color conforming to the full range standard used in digital imaging systems. + /// + /// + public struct YCbCr : IEquatable + { + /// + /// Represents a that has Y, Cb, and Cr values set to zero. + /// + public static readonly YCbCr Empty = default(YCbCr); + + /// + /// Initializes a new instance of the struct. + /// + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + public YCbCr(byte y, byte cb, byte cr) + : this() + { + this.Y = y; + this.Cb = cb; + this.Cr = cr; + } + + /// + /// Gets the Y luminance component. + /// A value ranging between 0 and 255. + /// + public byte Y { get; } + + /// + /// Gets the Cb chroma component. + /// A value ranging between 0 and 255. + /// + public byte Cb { get; } + + /// + /// Gets the Cr chroma component. + /// A value ranging between 0 and 255. + /// + public byte Cr { get; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator YCbCr(Color color) + { + byte r = color.R; + byte g = color.G; + byte b = color.B; + + byte y = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b)); + byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))); + byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))); + + return new YCbCr(y, cb, cr); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(YCbCr left, YCbCr right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(YCbCr left, YCbCr right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Y.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Cb.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Cr.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "YCbCr [ Empty ]"; + } + + return $"YCbCr [ Y={this.Y}, Cb={this.Cb}, Cr={this.Cr} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is YCbCr) + { + return this.Equals((YCbCr)obj); + } + + return false; + } + + /// + public bool Equals(YCbCr other) + { + return this.Y == other.Y && this.Cb == other.Cb && this.Cr == other.Cr; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Colors/ComponentOrder.cs b/src/ImageSharp46/Colors/ComponentOrder.cs new file mode 100644 index 000000000..7372ab9bf --- /dev/null +++ b/src/ImageSharp46/Colors/ComponentOrder.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerates the various component orders. + /// + public enum ComponentOrder + { + /// + /// Z-> Y-> X order. Equivalent to B-> G-> R in + /// + ZYX, + + /// + /// Z-> Y-> X-> W order. Equivalent to B-> G-> R-> A in + /// + ZYXW, + + /// + /// X-> Y-> Z order. Equivalent to R-> G-> B in + /// + XYZ, + + /// + /// X-> Y-> Z-> W order. Equivalent to R-> G-> B-> A in + /// + XYZW, + } +} diff --git a/src/ImageSharp46/Colors/PackedPixel/IPackedBytes.cs b/src/ImageSharp46/Colors/PackedPixel/IPackedBytes.cs new file mode 100644 index 000000000..5793ea1ba --- /dev/null +++ b/src/ImageSharp46/Colors/PackedPixel/IPackedBytes.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// An interface that converts packed vector types to and from values, + /// allowing multiple encodings to be manipulated in a generic manner. + /// + public interface IPackedBytes + { + /// + /// Gets the packed representation from the gives bytes. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + void PackFromBytes(byte x, byte y, byte z, byte w); + + /// + /// Sets the packed representation into the gives bytes. + /// + /// The bytes to set the color in. + /// The starting index of the . + /// The order of the components. + void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder); + } +} diff --git a/src/ImageSharp46/Colors/PackedPixel/IPackedPixel.cs b/src/ImageSharp46/Colors/PackedPixel/IPackedPixel.cs new file mode 100644 index 000000000..54602380f --- /dev/null +++ b/src/ImageSharp46/Colors/PackedPixel/IPackedPixel.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// An interface that represents a packed pixel type. + /// + /// The packed format. uint, long, float. + public interface IPackedPixel : IPackedVector, IPackedBytes + where TPacked : struct + { + } +} diff --git a/src/ImageSharp46/Colors/PackedPixel/IPackedVector.cs b/src/ImageSharp46/Colors/PackedPixel/IPackedVector.cs new file mode 100644 index 000000000..cdf250a01 --- /dev/null +++ b/src/ImageSharp46/Colors/PackedPixel/IPackedVector.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + /// + /// An interface that converts packed vector types to and from values, + /// allowing multiple encodings to be manipulated in a generic manner. + /// + /// The packed format. uint, long, float. + public interface IPackedVector : IPackedVector + where TPacked : struct + { + /// + /// Gets or sets the packed representation of the value. + /// + TPacked PackedValue { get; set; } + } + + /// + /// An interface that converts packed vector types to and from values. + /// + public interface IPackedVector + { + /// + /// Sets the packed representation from a . + /// + /// The vector to create the packed representation from. + void PackFromVector4(Vector4 vector); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToVector4(); + } +} diff --git a/src/ImageSharp46/Colors/RgbaComponent.cs b/src/ImageSharp46/Colors/RgbaComponent.cs new file mode 100644 index 000000000..ed85fb86b --- /dev/null +++ b/src/ImageSharp46/Colors/RgbaComponent.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerates the RGBA (red, green, blue, alpha) color components. + /// + public enum RgbaComponent + { + /// + /// The red component. + /// + R = 0, + + /// + /// The green component. + /// + G = 1, + + /// + /// The blue component. + /// + B = 2, + + /// + /// The alpha component. + /// + A = 3 + } +} diff --git a/src/ImageSharp46/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp46/Common/Exceptions/ImageFormatException.cs new file mode 100644 index 000000000..70491ba22 --- /dev/null +++ b/src/ImageSharp46/Common/Exceptions/ImageFormatException.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// The exception that is thrown when the library tries to load + /// an image, which has an invalid format. + /// + public class ImageFormatException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ImageFormatException() + { + } + + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageFormatException(string errorMessage) + : base(errorMessage) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageFormatException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } + } +} diff --git a/src/ImageSharp46/Common/Exceptions/ImageProcessingException.cs b/src/ImageSharp46/Common/Exceptions/ImageProcessingException.cs new file mode 100644 index 000000000..a59be9ca8 --- /dev/null +++ b/src/ImageSharp46/Common/Exceptions/ImageProcessingException.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// The exception that is thrown when an error occurs when applying a process to an image. + /// + public class ImageProcessingException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ImageProcessingException() + { + } + + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageProcessingException(string errorMessage) + : base(errorMessage) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageProcessingException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } + } +} diff --git a/src/ImageSharp46/Common/Extensions/ByteExtensions.cs b/src/ImageSharp46/Common/Extensions/ByteExtensions.cs new file mode 100644 index 000000000..00eab47fc --- /dev/null +++ b/src/ImageSharp46/Common/Extensions/ByteExtensions.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Extension methods for the struct. + /// + internal static class ByteExtensions + { + /// + /// Converts a byte array to a new array where each value in the original array is represented + /// by a the specified number of bits. + /// + /// The bytes to convert from. Cannot be null. + /// The number of bits per value. + /// The resulting array. Is never null. + /// is null. + /// is less than or equals than zero. + public static byte[] ToArrayByBitsLength(this byte[] bytes, int bits) + { + Guard.NotNull(bytes, "bytes"); + Guard.MustBeGreaterThan(bits, 0, "bits"); + + byte[] result; + + if (bits < 8) + { + result = new byte[bytes.Length * 8 / bits]; + + // BUGFIX I dont think it should be there, but I am not sure if it breaks something else + // int factor = (int)Math.Pow(2, bits) - 1; + int mask = 0xFF >> (8 - bits); + int resultOffset = 0; + + foreach (byte b in bytes) + { + for (int shift = 0; shift < 8; shift += bits) + { + int colorIndex = (b >> (8 - bits - shift)) & mask; // * (255 / factor); + + result[resultOffset] = (byte)colorIndex; + + resultOffset++; + } + } + } + else + { + result = bytes; + } + + return result; + } + } +} diff --git a/src/ImageSharp46/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp46/Common/Extensions/ComparableExtensions.cs new file mode 100644 index 000000000..8f056ff9d --- /dev/null +++ b/src/ImageSharp46/Common/Extensions/ComparableExtensions.cs @@ -0,0 +1,169 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Extension methods for classes that implement . + /// + internal static class ComparableExtensions + { + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static byte Clamp(this byte value, byte min, byte max) + { + // Order is important here as someone might set min to higher than max. + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static uint Clamp(this uint value, uint min, uint max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static int Clamp(this int value, int min, int max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static float Clamp(this float value, float min, float max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static double Clamp(this double value, double min, double max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this int value) + { + return (byte)value.Clamp(0, 255); + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this float value) + { + return (byte)value.Clamp(0, 255); + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this double value) + { + return (byte)value.Clamp(0, 255); + } + } +} diff --git a/src/ImageSharp46/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp46/Common/Extensions/EnumerableExtensions.cs new file mode 100644 index 000000000..af8e50c7b --- /dev/null +++ b/src/ImageSharp46/Common/Extensions/EnumerableExtensions.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + + /// + /// Encapsulates a series of time saving extension methods to the interface. + /// + public static class EnumerableExtensions + { + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// The end index, exclusive. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, int toExclusive, int step) + { + // Borrowed from Enumerable.Range + long num = (fromInclusive + toExclusive) - 1L; + if ((toExclusive < 0) || (num > 0x7fffffffL)) + { + throw new ArgumentOutOfRangeException(nameof(toExclusive)); + } + + return RangeIterator(fromInclusive, i => i < toExclusive, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) + { + return RangeIterator(fromInclusive, toDelegate, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) + { + int i = fromInclusive; + while (toDelegate(i)) + { + yield return i; + i += step; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp46/Common/Extensions/Vector4Extensions.cs new file mode 100644 index 000000000..23fce0173 --- /dev/null +++ b/src/ImageSharp46/Common/Extensions/Vector4Extensions.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Extension methods for the struct. + /// + public static class Vector4Extensions + { + /// + /// Compresses a linear color signal to its sRGB equivalent. + /// + /// + /// + /// The whose signal to compress. + /// The . + public static Vector4 Compress(this Vector4 linear) + { + // TODO: Is there a faster way to do this? + return new Vector4(Compress(linear.X), Compress(linear.Y), Compress(linear.Z), linear.W); + } + + /// + /// Expands an sRGB color signal to its linear equivalent. + /// + /// + /// + /// The whose signal to expand. + /// The . + public static Vector4 Expand(this Vector4 gamma) + { + // TODO: Is there a faster way to do this? + return new Vector4(Expand(gamma.X), Expand(gamma.Y), Expand(gamma.Z), gamma.W); + } + + /// + /// Gets the compressed sRGB value from an linear signal. + /// + /// + /// + /// The signal value to compress. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Compress(float signal) + { + if (signal <= 0.0031308F) + { + return signal * 12.92F; + } + + return (1.055F * (float)Math.Pow(signal, 0.41666666F)) - 0.055F; + } + + /// + /// Gets the expanded linear value from an sRGB signal. + /// + /// + /// + /// The signal value to expand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Expand(float signal) + { + if (signal <= 0.04045F) + { + return signal / 12.92F; + } + + return (float)Math.Pow((signal + 0.055F) / 1.055F, 2.4F); + } + } +} diff --git a/src/ImageSharp46/Common/Helpers/Guard.cs b/src/ImageSharp46/Common/Helpers/Guard.cs new file mode 100644 index 000000000..ecb497d3e --- /dev/null +++ b/src/ImageSharp46/Common/Helpers/Guard.cs @@ -0,0 +1,205 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + + /// + /// Provides methods to protect against invalid parameters. + /// + [DebuggerStepThrough] + internal static class Guard + { + /// + /// Verifies, that the method parameter with specified object value is not null + /// and throws an exception if it is found to be so. + /// + /// The target object, which cannot be null. + /// The name of the parameter that is to be checked. + /// The error message, if any to add to the exception. + /// is null + public static void NotNull(object target, string parameterName, string message = "") + { + if (target == null) + { + if (!string.IsNullOrWhiteSpace(message)) + { + throw new ArgumentNullException(parameterName, message); + } + + throw new ArgumentNullException(parameterName); + } + } + + /// + /// Verifies, that the string method parameter with specified object value and message + /// is not null, not empty and does not contain only blanks and throws an exception + /// if the object is null. + /// + /// The target string, which should be checked against being null or empty. + /// Name of the parameter. + /// is null. + /// is empty or contains only blanks. + public static void NotNullOrEmpty(string target, string parameterName) + { + if (target == null) + { + throw new ArgumentNullException(parameterName); + } + + if (string.IsNullOrWhiteSpace(target)) + { + throw new ArgumentException("Value cannot be null or empty and cannot contain only blanks.", parameterName); + } + } + + /// + /// Verifies that the specified value is less than a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + public static void MustBeLessThan(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) >= 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than {max}."); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than or equal to {max}."); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + public static void MustBeGreaterThan(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) <= 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + public static void MustBeGreaterThanOrEqualTo(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value of greater than the maximum value. + /// + public static void MustBeBetweenOrEqualTo(TValue value, TValue min, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min} and less than or equal to {max}."); + } + } + + /// + /// Verifies, that the method parameter with specified target value is true + /// and throws an exception if it is found to be so. + /// + /// + /// The target value, which cannot be false. + /// + /// + /// The name of the parameter that is to be checked. + /// + /// + /// The error message, if any to add to the exception. + /// + /// + /// is false + /// + public static void IsTrue(bool target, string parameterName, string message) + { + if (!target) + { + throw new ArgumentException(message, parameterName); + } + } + + /// + /// Verifies, that the method parameter with specified target value is false + /// and throws an exception if it is found to be so. + /// + /// The target value, which cannot be true. + /// The name of the parameter that is to be checked. + /// The error message, if any to add to the exception. + /// + /// is true + /// + public static void IsFalse(bool target, string parameterName, string message) + { + if (target) + { + throw new ArgumentException(message, parameterName); + } + } + } +} diff --git a/src/ImageSharp46/Common/Helpers/ImageMaths.cs b/src/ImageSharp46/Common/Helpers/ImageMaths.cs new file mode 100644 index 000000000..9b3c62382 --- /dev/null +++ b/src/ImageSharp46/Common/Helpers/ImageMaths.cs @@ -0,0 +1,293 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// Provides common mathematical methods. + /// + internal static class ImageMaths + { + /// + /// Returns how many bits are required to store the specified number of colors. + /// Performs a Log2() on the value. + /// + /// The number of colors. + /// + /// The + /// + public static int GetBitsNeededForColorDepth(int colors) + { + return (int)Math.Ceiling(Math.Log(colors, 2)); + } + + /// + /// Implementation of 1D Gaussian G(x) function + /// + /// The x provided to G(x). + /// The spread of the blur. + /// The Gaussian G(x) + public static float Gaussian(float x, float sigma) + { + const float Numerator = 1.0f; + float denominator = (float)(Math.Sqrt(2 * Math.PI) * sigma); + + float exponentNumerator = -x * x; + float exponentDenominator = (float)(2 * Math.Pow(sigma, 2)); + + float left = Numerator / denominator; + float right = (float)Math.Exp(exponentNumerator / exponentDenominator); + + return left * right; + } + + /// + /// Returns the result of a B-C filter against the given value. + /// + /// + /// The value to process. + /// The B-Spline curve variable. + /// The Cardinal curve variable. + /// + /// The . + /// + public static float GetBcValue(float x, float b, float c) + { + float temp; + + if (x < 0F) + { + x = -x; + } + + temp = x * x; + if (x < 1F) + { + x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); + return x / 6F; + } + + if (x < 2F) + { + x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); + return x / 6F; + } + + return 0F; + } + + /// + /// Gets the result of a sine cardinal function for the given value. + /// + /// The value to calculate the result for. + /// + /// The . + /// + public static float SinC(float x) + { + const float Epsilon = .00001F; + + if (Math.Abs(x) > Epsilon) + { + x *= (float)Math.PI; + return Clean((float)Math.Sin(x) / x); + } + + return 1.0f; + } + + /// + /// Returns the given degrees converted to radians. + /// + /// The angle in degrees. + /// + /// The representing the degree as radians. + /// + public static float DegreesToRadians(float degrees) + { + return degrees * (float)(Math.PI / 180); + } + + /// + /// Gets the bounding from the given points. + /// + /// + /// The designating the top left position. + /// + /// + /// The designating the bottom right position. + /// + /// + /// The bounding . + /// + public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) + { + return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); + } + + /// + /// Gets the bounding from the given matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) + { + Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + + Vector2[] allCorners = { leftTop, rightTop, leftBottom, rightBottom }; + float extentX = allCorners.Select(v => v.X).Max() - allCorners.Select(v => v.X).Min(); + float extentY = allCorners.Select(v => v.Y).Max() - allCorners.Select(v => v.Y).Min(); + return new Rectangle(0, 0, (int)extentX, (int)extentY); + } + + /// + /// Finds the bounding rectangle based on the first instance of any color component other + /// than the given one. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to search within. + /// The color component value to remove. + /// The channel to test against. + /// + /// The . + /// + public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) + where TColor : struct, IPackedPixel + where TPacked : struct + { + const float Epsilon = .00001f; + int width = bitmap.Width; + int height = bitmap.Height; + Point topLeft = default(Point); + Point bottomRight = default(Point); + + Func, int, int, float, bool> delegateFunc; + + // Determine which channel to check against + switch (channel) + { + case RgbaComponent.R: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToVector4().X - b) > Epsilon; + break; + + case RgbaComponent.G: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToVector4().Y - b) > Epsilon; + break; + + case RgbaComponent.B: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToVector4().Z - b) > Epsilon; + break; + + default: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToVector4().W - b) > Epsilon; + break; + } + + Func, int> getMinY = pixels => + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return 0; + }; + + Func, int> getMaxY = pixels => + { + for (int y = height - 1; y > -1; y--) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return height; + }; + + Func, int> getMinX = pixels => + { + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return 0; + }; + + Func, int> getMaxX = pixels => + { + for (int x = width - 1; x > -1; x--) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return height; + }; + + using (PixelAccessor bitmapPixels = bitmap.Lock()) + { + topLeft.Y = getMinY(bitmapPixels); + topLeft.X = getMinX(bitmapPixels); + bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); + bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); + } + + return GetBoundingRectangle(topLeft, bottomRight); + } + + /// + /// Ensures that any passed double is correctly rounded to zero + /// + /// The value to clean. + /// + /// The + /// . + private static float Clean(float x) + { + const float Epsilon = .00001F; + + if (Math.Abs(x) < Epsilon) + { + return 0F; + } + + return x; + } + } +} diff --git a/src/ImageSharp46/Filters/Alpha.cs b/src/ImageSharp46/Filters/Alpha.cs new file mode 100644 index 000000000..a985165ad --- /dev/null +++ b/src/ImageSharp46/Filters/Alpha.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the alpha component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The new opacity of the image. Must be between 0 and 100. + /// The . + public static Image Alpha(this Image source, int percent) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Alpha(source, percent, source.Bounds); + } + + /// + /// Alters the alpha component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The new opacity of the image. Must be between 0 and 100. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Alpha(this Image source, int percent, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new AlphaProcessor(percent)); + } + } +} diff --git a/src/ImageSharp46/Filters/BackgroundColor.cs b/src/ImageSharp46/Filters/BackgroundColor.cs new file mode 100644 index 000000000..47cbee4fb --- /dev/null +++ b/src/ImageSharp46/Filters/BackgroundColor.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The color to set as the background. + /// The . + public static Image BackgroundColor(this Image source, TColor color) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(source.Bounds, new BackgroundColorProcessor(color)); + } + } +} diff --git a/src/ImageSharp46/Filters/BinaryThreshold.cs b/src/ImageSharp46/Filters/BinaryThreshold.cs new file mode 100644 index 000000000..d97441fe2 --- /dev/null +++ b/src/ImageSharp46/Filters/BinaryThreshold.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies binerization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The threshold to apply binerization of the image. Must be between 0 and 1. + /// The . + public static Image BinaryThreshold(this Image source, float threshold) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return BinaryThreshold(source, threshold, source.Bounds); + } + + /// + /// Applies binerization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The threshold to apply binerization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image BinaryThreshold(this Image source, float threshold, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new BinaryThresholdProcessor(threshold)); + } + } +} diff --git a/src/ImageSharp46/Filters/BlackWhite.cs b/src/ImageSharp46/Filters/BlackWhite.cs new file mode 100644 index 000000000..7e6c6601e --- /dev/null +++ b/src/ImageSharp46/Filters/BlackWhite.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies black and white toning to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image BlackWhite(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return BlackWhite(source, source.Bounds); + } + + /// + /// Applies black and white toning to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image BlackWhite(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new BlackWhiteProcessor()); + } + } +} diff --git a/src/ImageSharp46/Filters/Blend.cs b/src/ImageSharp46/Filters/Blend.cs new file mode 100644 index 000000000..448fb7aa0 --- /dev/null +++ b/src/ImageSharp46/Filters/Blend.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Combines the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// The . + public static Image Blend(this Image source, ImageBase image, int percent = 50) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Blend(source, image, percent, source.Bounds); + } + + /// + /// Combines the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The packed format. uint, long, float. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Blend(this Image source, ImageBase image, int percent, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new BlendProcessor(image, percent)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Brightness.cs b/src/ImageSharp46/Filters/Brightness.cs new file mode 100644 index 000000000..2e167c54f --- /dev/null +++ b/src/ImageSharp46/Filters/Brightness.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the brightness component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The new brightness of the image. Must be between -100 and 100. + /// The . + public static Image Brightness(this Image source, int amount) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Brightness(source, amount, source.Bounds); + } + + /// + /// Alters the brightness component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The new brightness of the image. Must be between -100 and 100. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Brightness(this Image source, int amount, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new BrightnessProcessor(amount)); + } + } +} diff --git a/src/ImageSharp46/Filters/ColorBlindness.cs b/src/ImageSharp46/Filters/ColorBlindness.cs new file mode 100644 index 000000000..e1a14e0f0 --- /dev/null +++ b/src/ImageSharp46/Filters/ColorBlindness.cs @@ -0,0 +1,85 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies the given colorblindness simulator to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The type of color blindness simulator to apply. + /// The . + public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return ColorBlindness(source, colorBlindness, source.Bounds); + } + + /// + /// Applies the given colorblindness simulator to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The type of color blindness simulator to apply. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + IImageFilter processor; + + switch (colorBlindness) + { + case ImageSharp.ColorBlindness.Achromatomaly: + processor = new AchromatomalyProcessor(); + break; + + case ImageSharp.ColorBlindness.Achromatopsia: + processor = new AchromatopsiaProcessor(); + break; + + case ImageSharp.ColorBlindness.Deuteranomaly: + processor = new DeuteranomalyProcessor(); + break; + + case ImageSharp.ColorBlindness.Deuteranopia: + processor = new DeuteranopiaProcessor(); + break; + + case ImageSharp.ColorBlindness.Protanomaly: + processor = new ProtanomalyProcessor(); + break; + + case ImageSharp.ColorBlindness.Protanopia: + processor = new ProtanopiaProcessor(); + break; + + case ImageSharp.ColorBlindness.Tritanomaly: + processor = new TritanomalyProcessor(); + break; + + default: + processor = new TritanopiaProcessor(); + break; + } + + return source.Process(rectangle, processor); + } + } +} diff --git a/src/ImageSharp46/Filters/Contrast.cs b/src/ImageSharp46/Filters/Contrast.cs new file mode 100644 index 000000000..6f9c45cc6 --- /dev/null +++ b/src/ImageSharp46/Filters/Contrast.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the contrast component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The new contrast of the image. Must be between -100 and 100. + /// The . + public static Image Contrast(this Image source, int amount) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Contrast(source, amount, source.Bounds); + } + + /// + /// Alters the contrast component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The new contrast of the image. Must be between -100 and 100. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Contrast(this Image source, int amount, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new ContrastProcessor(amount)); + } + } +} diff --git a/src/ImageSharp46/Filters/Glow.cs b/src/ImageSharp46/Filters/Glow.cs new file mode 100644 index 000000000..1b829850d --- /dev/null +++ b/src/ImageSharp46/Filters/Glow.cs @@ -0,0 +1,102 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image Glow(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Glow(source, default(TColor), source.Bounds.Width * .5F, source.Bounds); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The color to set as the glow. + /// The . + public static Image Glow(this Image source, TColor color) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Glow(source, color, source.Bounds.Width * .5F, source.Bounds); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The the radius. + /// The . + public static Image Glow(this Image source, float radius) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Glow(source, default(TColor), radius, source.Bounds); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Glow(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Glow(source, default(TColor), 0, rectangle); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Glow(this Image source, TColor color, float radius, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + GlowProcessor processor = new GlowProcessor { Radius = radius, }; + + if (!color.Equals(default(TColor))) + { + processor.GlowColor = color; + } + + return source.Process(rectangle, processor); + } + } +} diff --git a/src/ImageSharp46/Filters/Grayscale.cs b/src/ImageSharp46/Filters/Grayscale.cs new file mode 100644 index 000000000..ce827e54c --- /dev/null +++ b/src/ImageSharp46/Filters/Grayscale.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies Grayscale toning to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The . + public static Image Grayscale(this Image source, GrayscaleMode mode = GrayscaleMode.Bt709) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Grayscale(source, source.Bounds, mode); + } + + /// + /// Applies Grayscale toning to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The formula to apply to perform the operation. + /// The . + public static Image Grayscale(this Image source, Rectangle rectangle, GrayscaleMode mode = GrayscaleMode.Bt709) + where TColor : struct, IPackedPixel + where TPacked : struct + { + IImageFilter processor = mode == GrayscaleMode.Bt709 + ? (IImageFilter)new GrayscaleBt709Processor() + : new GrayscaleBt601Processor(); + + return source.Process(rectangle, processor); + } + } +} diff --git a/src/ImageSharp46/Filters/Hue.cs b/src/ImageSharp46/Filters/Hue.cs new file mode 100644 index 000000000..c146667a0 --- /dev/null +++ b/src/ImageSharp46/Filters/Hue.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the hue component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The angle in degrees to adjust the image. + /// The . + public static Image Hue(this Image source, float degrees) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Hue(source, degrees, source.Bounds); + } + + /// + /// Alters the hue component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The angle in degrees to adjust the image. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Hue(this Image source, float degrees, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new HueProcessor(degrees)); + } + } +} diff --git a/src/ImageSharp46/Filters/Invert.cs b/src/ImageSharp46/Filters/Invert.cs new file mode 100644 index 000000000..a948c511d --- /dev/null +++ b/src/ImageSharp46/Filters/Invert.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Inverts the colors of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image Invert(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Invert(source, source.Bounds); + } + + /// + /// Inverts the colors of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Invert(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new InvertProcessor()); + } + } +} diff --git a/src/ImageSharp46/Filters/Kodachrome.cs b/src/ImageSharp46/Filters/Kodachrome.cs new file mode 100644 index 000000000..f47bfa5c3 --- /dev/null +++ b/src/ImageSharp46/Filters/Kodachrome.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the colors of the image recreating an old Kodachrome camera effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image Kodachrome(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Kodachrome(source, source.Bounds); + } + + /// + /// Alters the colors of the image recreating an old Kodachrome camera effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Kodachrome(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new KodachromeProcessor()); + } + } +} diff --git a/src/ImageSharp46/Filters/Lomograph.cs b/src/ImageSharp46/Filters/Lomograph.cs new file mode 100644 index 000000000..84fccd374 --- /dev/null +++ b/src/ImageSharp46/Filters/Lomograph.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image Lomograph(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Lomograph(source, source.Bounds); + } + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Lomograph(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new LomographProcessor()); + } + } +} diff --git a/src/ImageSharp46/Filters/Options/ColorBlindness.cs b/src/ImageSharp46/Filters/Options/ColorBlindness.cs new file mode 100644 index 000000000..1e0bc596b --- /dev/null +++ b/src/ImageSharp46/Filters/Options/ColorBlindness.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerates the various types of defined color blindness filters. + /// + public enum ColorBlindness + { + /// + /// Partial color desensitivity. + /// + Achromatomaly, + + /// + /// Complete color desensitivity (Monochrome) + /// + Achromatopsia, + + /// + /// Green weak + /// + Deuteranomaly, + + /// + /// Green blind + /// + Deuteranopia, + + /// + /// Red weak + /// + Protanomaly, + + /// + /// Red blind + /// + Protanopia, + + /// + /// Blue weak + /// + Tritanomaly, + + /// + /// Blue blind + /// + Tritanopia + } +} diff --git a/src/ImageSharp46/Filters/Options/EdgeDetection.cs b/src/ImageSharp46/Filters/Options/EdgeDetection.cs new file mode 100644 index 000000000..67fad0de4 --- /dev/null +++ b/src/ImageSharp46/Filters/Options/EdgeDetection.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerates the various types of defined edge detection filters. + /// + public enum EdgeDetection + { + /// + /// The Kayyali operator filter. + /// + Kayyali, + + /// + /// The Kirsch operator filter. + /// + Kirsch, + + /// + /// The Lapacian3X3 operator filter. + /// + Lapacian3X3, + + /// + /// The Lapacian5X5 operator filter. + /// + Lapacian5X5, + + /// + /// The LaplacianOfGaussian operator filter. + /// + LaplacianOfGaussian, + + /// + /// The Prewitt operator filter. + /// + Prewitt, + + /// + /// The RobertsCross operator filter. + /// + RobertsCross, + + /// + /// The Robinson operator filter. + /// + Robinson, + + /// + /// The Scharr operator filter. + /// + Scharr, + + /// + /// The Sobel operator filter. + /// + Sobel + } +} diff --git a/src/ImageSharp46/Filters/Options/GrayscaleMode.cs b/src/ImageSharp46/Filters/Options/GrayscaleMode.cs new file mode 100644 index 000000000..6ecd5bb36 --- /dev/null +++ b/src/ImageSharp46/Filters/Options/GrayscaleMode.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerates the various types of defined Grayscale filters. + /// + public enum GrayscaleMode + { + /// + /// ITU-R Recommendation BT.709 + /// + Bt709, + + /// + /// ITU-R Recommendation BT.601 + /// + Bt601 + } +} diff --git a/src/ImageSharp46/Filters/Polaroid.cs b/src/ImageSharp46/Filters/Polaroid.cs new file mode 100644 index 000000000..f77c95a4b --- /dev/null +++ b/src/ImageSharp46/Filters/Polaroid.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image Polaroid(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Polaroid(source, source.Bounds); + } + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Polaroid(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new PolaroidProcessor()); + } + } +} diff --git a/src/ImageSharp46/Filters/Processors/AlphaProcessor.cs b/src/ImageSharp46/Filters/Processors/AlphaProcessor.cs new file mode 100644 index 000000000..08c05acdd --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/AlphaProcessor.cs @@ -0,0 +1,85 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to change the alpha component of an . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class AlphaProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The percentage to adjust the opacity of the image. Must be between 0 and 100. + /// + /// is less than 0 or is greater than 100. + /// + public AlphaProcessor(int percent) + { + Guard.MustBeBetweenOrEqualTo(percent, 0, 100, nameof(percent)); + this.Value = percent; + } + + /// + /// Gets the alpha value. + /// + public int Value { get; } + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + float alpha = this.Value / 100F; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + Vector4 alphaVector = new Vector4(1, 1, 1, alpha); + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TColor packed = default(TColor); + packed.PackFromVector4(sourcePixels[offsetX, offsetY].ToVector4() * alphaVector); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } +} diff --git a/src/ImageSharp46/Filters/Processors/BackgroundColorProcessor.cs b/src/ImageSharp46/Filters/Processors/BackgroundColorProcessor.cs new file mode 100644 index 000000000..37dd580fa --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/BackgroundColorProcessor.cs @@ -0,0 +1,98 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Sets the background color of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class BackgroundColorProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// Initializes a new instance of the class. + /// + /// The to set the background color to. + public BackgroundColorProcessor(TColor color) + { + this.Value = color; + } + + /// + /// Gets the background color value. + /// + public TColor Value { get; } + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + Vector4 backgroundColor = this.Value.ToVector4(); + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + float a = color.W; + + if (a < 1 && a > 0) + { + color = Vector4.Lerp(color, backgroundColor, .5F); + } + + if (Math.Abs(a) < Epsilon) + { + color = backgroundColor; + } + + TColor packed = default(TColor); + packed.PackFromVector4(color); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp46/Filters/Processors/Binarization/BinaryThresholdProcessor.cs new file mode 100644 index 000000000..09c6a96f3 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/Binarization/BinaryThresholdProcessor.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Threading.Tasks; + + /// + /// An to perform binary threshold filtering against an + /// . The image will be converted to grayscale before thresholding occurs. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class BinaryThresholdProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// + /// is less than 0 or is greater than 1. + /// + public BinaryThresholdProcessor(float threshold) + { + // TODO: Check limit. + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + this.Value = threshold; + + TColor upper = default(TColor); + upper.PackFromVector4(Color.White.ToVector4()); + this.UpperColor = upper; + + TColor lower = default(TColor); + lower.PackFromVector4(Color.Black.ToVector4()); + this.LowerColor = lower; + } + + /// + /// Gets the threshold value. + /// + public float Value { get; } + + /// + /// Gets or sets the color to use for pixels that are above the threshold. + /// + public TColor UpperColor { get; set; } + + /// + /// Gets or sets the color to use for pixels that fall below the threshold. + /// + public TColor LowerColor { get; set; } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + float threshold = this.Value; + TColor upper = this.UpperColor; + TColor lower = this.LowerColor; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TColor color = sourcePixels[offsetX, offsetY]; + + // Any channel will do since it's Grayscale. + sourcePixels[offsetX, offsetY] = color.ToVector4().X >= threshold ? upper : lower; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/BlendProcessor.cs b/src/ImageSharp46/Filters/Processors/BlendProcessor.cs new file mode 100644 index 000000000..4f326361f --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/BlendProcessor.cs @@ -0,0 +1,106 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Combines two images together by blending the pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class BlendProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The image to blend. + /// + private readonly ImageBase blend; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The image to blend with the currently processing image. + /// Disposal of this image is the responsibility of the developer. + /// + /// The opacity of the image to blend. Between 0 and 100. + public BlendProcessor(ImageBase image, int alpha = 100) + { + Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); + this.blend = image; + this.Value = alpha; + } + + /// + /// Gets the alpha percentage value. + /// + public int Value { get; } + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Rectangle bounds = this.blend.Bounds; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + float alpha = this.Value / 100F; + + using (PixelAccessor toBlendPixels = this.blend.Lock()) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + + if (bounds.Contains(offsetX, offsetY)) + { + Vector4 blendedColor = toBlendPixels[offsetX, offsetY].ToVector4(); + + if (blendedColor.W > 0) + { + // Lerping colors is dependent on the alpha of the blended color + color = Vector4.Lerp(color, blendedColor, alpha > 0 ? alpha : blendedColor.W); + } + } + + TColor packed = default(TColor); + packed.PackFromVector4(color); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/BrightnessProcessor.cs b/src/ImageSharp46/Filters/Processors/BrightnessProcessor.cs new file mode 100644 index 000000000..b74759b6b --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/BrightnessProcessor.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to change the brightness of an . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class BrightnessProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The new brightness of the image. Must be between -100 and 100. + /// + /// is less than -100 or is greater than 100. + /// + public BrightnessProcessor(int brightness) + { + Guard.MustBeBetweenOrEqualTo(brightness, -100, 100, nameof(brightness)); + this.Value = brightness; + } + + /// + /// Gets the brightness value. + /// + public int Value { get; } + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + float brightness = this.Value / 100F; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + + // TODO: Check this with other formats. + Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); + Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); + vector = new Vector4(transformed, vector.W); + + TColor packed = default(TColor); + packed.PackFromVector4(vector.Compress()); + + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs new file mode 100644 index 000000000..cbb351aaf --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to their black and white equivalent. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class BlackWhiteProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 1.5f, + M12 = 1.5f, + M13 = 1.5f, + M21 = 1.5f, + M22 = 1.5f, + M23 = 1.5f, + M31 = 1.5f, + M32 = 1.5f, + M33 = 1.5f, + M41 = -1f, + M42 = -1f, + M43 = -1f, + }; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs new file mode 100644 index 000000000..012954c2f --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class AchromatomalyProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .618f, + M12 = .163f, + M13 = .163f, + M21 = .320f, + M22 = .775f, + M23 = .320f, + M31 = .062f, + M32 = .062f, + M33 = .516f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs new file mode 100644 index 000000000..6ce50ba43 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class AchromatopsiaProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .299f, + M12 = .299f, + M13 = .299f, + M21 = .587f, + M22 = .587f, + M23 = .587f, + M31 = .114f, + M32 = .114f, + M33 = .114f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs new file mode 100644 index 000000000..e1fbac60a --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class DeuteranomalyProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.8f, + M12 = 0.258f, + M21 = 0.2f, + M22 = 0.742f, + M23 = 0.142f, + M33 = 0.858f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs new file mode 100644 index 000000000..35e76b235 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class DeuteranopiaProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.625f, + M12 = 0.7f, + M21 = 0.375f, + M22 = 0.3f, + M23 = 0.3f, + M33 = 0.7f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs new file mode 100644 index 000000000..38199b956 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Protanopia (Red-Weak) color blindness. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class ProtanomalyProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.817f, + M12 = 0.333f, + M21 = 0.183f, + M22 = 0.667f, + M23 = 0.125f, + M33 = 0.875f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs new file mode 100644 index 000000000..19936a849 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class ProtanopiaProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.567f, + M12 = 0.558f, + M21 = 0.433f, + M22 = 0.442f, + M23 = 0.242f, + M33 = 0.758f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/README.md b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/README.md new file mode 100644 index 000000000..209f3b67b --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/README.md @@ -0,0 +1,4 @@ +Color blindness matrices adapted from and tested against: + +http://web.archive.org/web/20090413045433/http://nofunc.org/Color_Matrix_Library +http://www.color-blindness.com/coblis-color-blindness-simulator/ \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs new file mode 100644 index 000000000..84d22c78a --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class TritanomalyProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.967f, + M21 = 0.33f, + M22 = 0.733f, + M23 = 0.183f, + M32 = 0.267f, + M33 = 0.817f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs new file mode 100644 index 000000000..82d7415b7 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class TritanopiaProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.95f, + M21 = 0.05f, + M22 = 0.433f, + M23 = 0.475f, + M32 = 0.567f, + M33 = 0.525f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs new file mode 100644 index 000000000..be6f277e0 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// The color matrix filter. Inherit from this class to perform operation involving color matrices. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class ColorMatrixFilter : ImageFilter, IColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public abstract Matrix4x4 Matrix { get; } + + /// + public override bool Compand { get; set; } = true; + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + Matrix4x4 matrix = this.Matrix; + bool compand = this.Compand; + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + sourcePixels[offsetX, offsetY] = this.ApplyMatrix(sourcePixels[offsetX, offsetY], matrix, compand); + } + }); + } + } + + /// + /// Applies the color matrix against the given color. + /// + /// The source color. + /// The matrix. + /// Whether to compand the color during processing. + /// + /// The . + /// + private TColor ApplyMatrix(TColor color, Matrix4x4 matrix, bool compand) + { + Vector4 vector = color.ToVector4(); + + if (compand) + { + vector = vector.Expand(); + } + + Vector3 transformed = Vector3.Transform(new Vector3(vector.X, vector.Y, vector.Z), matrix); + vector = new Vector4(transformed, vector.W); + TColor packed = default(TColor); + packed.PackFromVector4(compand ? vector.Compress() : vector); + return packed; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs new file mode 100644 index 000000000..d6c1762e8 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to Grayscale applying the formula as specified by + /// ITU-R Recommendation BT.601 . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class GrayscaleBt601Processor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .299f, + M12 = .299f, + M13 = .299f, + M21 = .587f, + M22 = .587f, + M23 = .587f, + M31 = .114f, + M32 = .114f, + M33 = .114f + }; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs new file mode 100644 index 000000000..6fbde6418 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to Grayscale applying the formula as specified by + /// ITU-R Recommendation BT.709 . + /// + public class GrayscaleBt709Processor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .2126f, + M12 = .2126f, + M13 = .2126f, + M21 = .7152f, + M22 = .7152f, + M23 = .7152f, + M31 = .0722f, + M32 = .0722f, + M33 = .0722f + }; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/HueProcessor.cs new file mode 100644 index 000000000..a829331c9 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/HueProcessor.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + + /// + /// An to change the hue of an . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class HueProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The new brightness of the image. Must be between -100 and 100. + public HueProcessor(float angle) + { + // Wrap the angle round at 360. + angle = angle % 360; + + // Make sure it's not negative. + while (angle < 0) + { + angle += 360; + } + + this.Angle = angle; + + float radians = ImageMaths.DegreesToRadians(angle); + double cosradians = Math.Cos(radians); + double sinradians = Math.Sin(radians); + + float lumR = .213f; + float lumG = .715f; + float lumB = .072f; + + float oneMinusLumR = 1 - lumR; + float oneMinusLumG = 1 - lumG; + float oneMinusLumB = 1 - lumB; + + // The matrix is set up to preserve the luminance of the image. + // See http://graficaobscura.com/matrix/index.html + // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx + Matrix4x4 matrix4X4 = new Matrix4x4() + { + M11 = (float)(lumR + (cosradians * oneMinusLumR) - (sinradians * lumR)), + M12 = (float)(lumR - (cosradians * lumR) - (sinradians * 0.143)), + M13 = (float)(lumR - (cosradians * lumR) - (sinradians * oneMinusLumR)), + M21 = (float)(lumG - (cosradians * lumG) - (sinradians * lumG)), + M22 = (float)(lumG + (cosradians * oneMinusLumG) + (sinradians * 0.140)), + M23 = (float)(lumG - (cosradians * lumG) + (sinradians * lumG)), + M31 = (float)(lumB - (cosradians * lumB) + (sinradians * oneMinusLumB)), + M32 = (float)(lumB - (cosradians * lumB) - (sinradians * 0.283)), + M33 = (float)(lumB + (cosradians * oneMinusLumB) + (sinradians * lumB)) + }; + + this.Matrix = matrix4X4; + } + + /// + /// Gets the rotation value. + /// + public float Angle { get; } + + /// + public override Matrix4x4 Matrix { get; } + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs new file mode 100644 index 000000000..ede66fd71 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Encapsulates properties and methods for creating processors that utilize a matrix to + /// alter the image pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public interface IColorMatrixFilter : IImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Gets the used to alter the image. + /// + Matrix4x4 Matrix { get; } + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/KodachromeProcessor.cs new file mode 100644 index 000000000..a4f58cc41 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/KodachromeProcessor.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Kodachrome camera effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class KodachromeProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.6997023f, + M22 = 0.4609577f, + M33 = 0.397218f, + M41 = 0.005f, + M42 = -0.005f, + M43 = 0.005f + }; + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/LomographProcessor.cs new file mode 100644 index 000000000..628c13087 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/LomographProcessor.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Lomograph effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class LomographProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 1.5f, + M22 = 1.45f, + M33 = 1.11f, + M41 = -.1f, + M42 = .0f, + M43 = -.08f + }; + + /// + protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) + { + TColor packed = default(TColor); + packed.PackFromVector4(new Color(0, 10, 0).ToVector4()); // Very dark (mostly black) lime green. + new VignetteProcessor { VignetteColor = packed }.Apply(source, sourceRectangle); + } + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/PolaroidProcessor.cs new file mode 100644 index 000000000..72fc0a98d --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/PolaroidProcessor.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Polaroid effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class PolaroidProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 1.538f, + M12 = -0.062f, + M13 = -0.262f, + M21 = -0.022f, + M22 = 1.578f, + M23 = -0.022f, + M31 = .216f, + M32 = -.16f, + M33 = 1.5831f, + M41 = 0.02f, + M42 = -0.05f, + M43 = -0.05f + }; + + /// + protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) + { + TColor packedV = default(TColor); + packedV.PackFromVector4(new Color(102, 34, 0).ToVector4()); // Very dark orange [Brown tone] + new VignetteProcessor { VignetteColor = packedV }.Apply(source, sourceRectangle); + + TColor packedG = default(TColor); + packedG.PackFromVector4(new Color(255, 153, 102, 178).ToVector4()); // Light orange + new GlowProcessor { GlowColor = packedG, Radius = source.Width / 4F }.Apply(source, sourceRectangle); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/SaturationProcessor.cs new file mode 100644 index 000000000..60f7c7815 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/SaturationProcessor.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// An to change the saturation of an . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class SaturationProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The new saturation of the image. Must be between -100 and 100. + /// + /// is less than -100 or is greater than 100. + /// + public SaturationProcessor(int saturation) + { + Guard.MustBeBetweenOrEqualTo(saturation, -100, 100, nameof(saturation)); + float saturationFactor = saturation / 100f; + + // Stop at -1 to prevent inversion. + saturationFactor++; + + // The matrix is set up to "shear" the colour space using the following set of values. + // Note that each colour component has an effective luminance which contributes to the + // overall brightness of the pixel. + // See http://graficaobscura.com/matrix/index.html + float saturationComplement = 1.0f - saturationFactor; + float saturationComplementR = 0.3086f * saturationComplement; + float saturationComplementG = 0.6094f * saturationComplement; + float saturationComplementB = 0.0820f * saturationComplement; + + Matrix4x4 matrix4X4 = new Matrix4x4() + { + M11 = saturationComplementR + saturationFactor, + M12 = saturationComplementR, + M13 = saturationComplementR, + M21 = saturationComplementG, + M22 = saturationComplementG + saturationFactor, + M23 = saturationComplementG, + M31 = saturationComplementB, + M32 = saturationComplementB, + M33 = saturationComplementB + saturationFactor, + }; + + this.Matrix = matrix4X4; + } + + /// + public override Matrix4x4 Matrix { get; } + } +} diff --git a/src/ImageSharp46/Filters/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageSharp46/Filters/Processors/ColorMatrix/SepiaProcessor.cs new file mode 100644 index 000000000..80be3bf4c --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ColorMatrix/SepiaProcessor.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to their sepia equivalent. + /// The formula used matches the svg specification. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class SepiaProcessor : ColorMatrixFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .393f, + M12 = .349f, + M13 = .272f, + M21 = .769f, + M22 = .686f, + M23 = .534f, + M31 = .189f, + M32 = .168f, + M33 = .131f + }; + + /// + public override bool Compand => false; + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/ContrastProcessor.cs b/src/ImageSharp46/Filters/Processors/ContrastProcessor.cs new file mode 100644 index 000000000..fbcaaa1cd --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ContrastProcessor.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to change the contrast of an . + /// + /// The pixel format. + /// The packed format. long, float. + public class ContrastProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The new contrast of the image. Must be between -100 and 100. + /// + /// is less than -100 or is greater than 100. + /// + public ContrastProcessor(int contrast) + { + Guard.MustBeBetweenOrEqualTo(contrast, -100, 100, nameof(contrast)); + this.Value = contrast; + } + + /// + /// Gets the contrast value. + /// + public int Value { get; } + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + float contrast = (100F + this.Value) / 100F; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); + Vector4 shiftVector = new Vector4(.5F, .5F, .5F, 1); + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + + Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); + vector -= shiftVector; + vector *= contrastVector; + vector += shiftVector; + TColor packed = default(TColor); + packed.PackFromVector4(vector.Compress()); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/GlowProcessor.cs b/src/ImageSharp46/Filters/Processors/GlowProcessor.cs new file mode 100644 index 000000000..d671d1d6f --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/GlowProcessor.cs @@ -0,0 +1,94 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An that applies a radial glow effect an . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class GlowProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + public GlowProcessor() + { + TColor color = default(TColor); + color.PackFromVector4(Color.Black.ToVector4()); + this.GlowColor = color; + } + + /// + /// Gets or sets the glow color to apply. + /// + public TColor GlowColor { get; set; } + + /// + /// Gets or sets the the radius. + /// + public float Radius { get; set; } + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + TColor glowColor = this.GlowColor; + Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + float maxDistance = this.Radius > 0 ? Math.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + Ellipse ellipse = new Ellipse(new Point(centre), maxDistance, maxDistance); + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + if (ellipse.Contains(offsetX, offsetY)) + { + // TODO: Premultiply? + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); + TColor packed = default(TColor); + packed.PackFromVector4(Vector4.Lerp(glowColor.ToVector4(), sourceColor, distance / maxDistance)); + sourcePixels[offsetX, offsetY] = packed; + } + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/IImageFilter.cs b/src/ImageSharp46/Filters/Processors/IImageFilter.cs new file mode 100644 index 000000000..fc0a7625b --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/IImageFilter.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + /// + /// Encapsulates methods to alter the pixels of an image. The processor operates on the original source pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public interface IImageFilter : IImageProcessor + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Applies the process to the specified portion of the specified . + /// + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image processing filter as new image. + /// + /// + /// is null. + /// + /// + /// doesnt fit the dimension of the image. + /// + void Apply(ImageBase source, Rectangle sourceRectangle); + } +} diff --git a/src/ImageSharp46/Filters/Processors/ImageFilter.cs b/src/ImageSharp46/Filters/Processors/ImageFilter.cs new file mode 100644 index 000000000..34bfdb1fb --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/ImageFilter.cs @@ -0,0 +1,70 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + + /// + /// Encapsulates methods to alter the pixels of an image. The processor operates on the original source pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class ImageFilter : ImageProcessor, IImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public void Apply(ImageBase source, Rectangle sourceRectangle) + { + try + { + this.OnApply(source, sourceRectangle); + + this.Apply(source, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); + + this.AfterApply(source, sourceRectangle); + } + catch (Exception ex) + { + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The index of the row within the source image to start processing. + /// The index of the row within the source image to end processing. + protected abstract void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY); + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void OnApply(ImageBase source, Rectangle sourceRectangle) + { + } + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void AfterApply(ImageBase source, Rectangle sourceRectangle) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/InvertProcessor.cs b/src/ImageSharp46/Filters/Processors/InvertProcessor.cs new file mode 100644 index 000000000..d010eaf36 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/InvertProcessor.cs @@ -0,0 +1,68 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to invert the colors of an . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class InvertProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Vector3 inverseVector = Vector3.One; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + Vector3 vector = inverseVector - new Vector3(color.X, color.Y, color.Z); + + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(vector, color.W)); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Processors/VignetteProcessor.cs b/src/ImageSharp46/Filters/Processors/VignetteProcessor.cs new file mode 100644 index 000000000..b60e49647 --- /dev/null +++ b/src/ImageSharp46/Filters/Processors/VignetteProcessor.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An that applies a radial vignette effect to an . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class VignetteProcessor : ImageFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + public VignetteProcessor() + { + TColor color = default(TColor); + color.PackFromVector4(Color.Black.ToVector4()); + this.VignetteColor = color; + } + + /// + /// Gets or sets the vignette color to apply. + /// + public TColor VignetteColor { get; set; } + + /// + /// Gets or sets the the x-radius. + /// + public float RadiusX { get; set; } + + /// + /// Gets or sets the the y-radius. + /// + public float RadiusY { get; set; } + + /// + protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + TColor vignetteColor = this.VignetteColor; + Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + float rX = this.RadiusX > 0 ? Math.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + float rY = this.RadiusY > 0 ? Math.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; + float maxDistance = (float)Math.Sqrt((rX * rX) + (rY * rY)); + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); + TColor packed = default(TColor); + packed.PackFromVector4(Vector4.Lerp(vignetteColor.ToVector4(), sourceColor, 1 - (.9F * (distance / maxDistance)))); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Saturation.cs b/src/ImageSharp46/Filters/Saturation.cs new file mode 100644 index 000000000..6fb0a463c --- /dev/null +++ b/src/ImageSharp46/Filters/Saturation.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the saturation component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The new saturation of the image. Must be between -100 and 100. + /// The . + public static Image Saturation(this Image source, int amount) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Saturation(source, amount, source.Bounds); + } + + /// + /// Alters the saturation component of the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The new saturation of the image. Must be between -100 and 100. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Saturation(this Image source, int amount, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new SaturationProcessor(amount)); + } + } +} diff --git a/src/ImageSharp46/Filters/Sepia.cs b/src/ImageSharp46/Filters/Sepia.cs new file mode 100644 index 000000000..6bd070cc8 --- /dev/null +++ b/src/ImageSharp46/Filters/Sepia.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies sepia toning to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image Sepia(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Sepia(source, source.Bounds); + } + + /// + /// Applies sepia toning to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Sepia(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new SepiaProcessor()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Filters/Vignette.cs b/src/ImageSharp46/Filters/Vignette.cs new file mode 100644 index 000000000..b8f10b27a --- /dev/null +++ b/src/ImageSharp46/Filters/Vignette.cs @@ -0,0 +1,104 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image Vignette(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Vignette(source, default(TColor), source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The color to set as the vignette. + /// The . + public static Image Vignette(this Image source, TColor color) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// The . + public static Image Vignette(this Image source, float radiusX, float radiusY) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Vignette(source, default(TColor), radiusX, radiusY, source.Bounds); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Vignette(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Vignette(source, default(TColor), 0, 0, rectangle); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Vignette(this Image source, TColor color, float radiusX, float radiusY, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + VignetteProcessor processor = new VignetteProcessor { RadiusX = radiusX, RadiusY = radiusY }; + + if (!color.Equals(default(TColor))) + { + processor.VignetteColor = color; + } + + return source.Process(rectangle, processor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp46/Formats/Bmp/BmpBitsPerPixel.cs new file mode 100644 index 000000000..330326acf --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpBitsPerPixel.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Enumerates the available bits per pixel for bitmap. + /// + public enum BmpBitsPerPixel + { + /// + /// 24 bits per pixel. Each pixel consists of 3 bytes. + /// + Pixel24 = 3, + + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes. + /// + Pixel32 = 4, + } +} diff --git a/src/ImageSharp46/Formats/Bmp/BmpCompression.cs b/src/ImageSharp46/Formats/Bmp/BmpCompression.cs new file mode 100644 index 000000000..a9246d449 --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpCompression.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Defines how the compression type of the image data + /// in the bitmap file. + /// + internal enum BmpCompression + { + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// The format depends on the number of bits, stored in the info header. + /// If the number of bits are one, four or eight each pixel data is + /// a index to the palette. If the number of bits are sixteen, + /// twenty-four or thirty-two each pixel contains a color. + /// + RGB = 0, + + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next two half bytes will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// Not supported at the moment. + /// + RLE8 = 1, + + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next byte will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// Not supported at the moment. + /// + RLE4 = 2, + + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// Not supported at the moment. + /// + BitFields = 3, + + /// + /// The bitmap contains a JPG image. + /// Not supported at the moment. + /// + JPEG = 4, + + /// + /// The bitmap contains a PNG image. + /// Not supported at the moment. + /// + PNG = 5 + } +} diff --git a/src/ImageSharp46/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp46/Formats/Bmp/BmpDecoder.cs new file mode 100644 index 000000000..b99a96462 --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpDecoder.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Image decoder for generating an image out of a Windows bitmap stream. + /// + /// + /// Does not support the following formats at the moment: + /// + /// JPG + /// PNG + /// RLE4 + /// RLE8 + /// BitFields + /// + /// Formats will be supported in a later releases. We advise always + /// to use only 24 Bit Windows bitmaps. + /// + public class BmpDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 2; + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals("BMP", StringComparison.OrdinalIgnoreCase) + || extension.Equals("DIP", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + public bool IsSupportedFileFormat(byte[] header) + { + bool isBmp = false; + if (header.Length >= 2) + { + isBmp = header[0] == 0x42 && // B + header[1] == 0x4D; // M + } + + return isBmp; + } + + /// + public void Decode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Guard.NotNull(image, "image"); + Guard.NotNull(stream, "stream"); + + new BmpDecoderCore().Decode(image, stream); + } + } +} diff --git a/src/ImageSharp46/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp46/Formats/Bmp/BmpDecoderCore.cs new file mode 100644 index 000000000..7c3e842aa --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpDecoderCore.cs @@ -0,0 +1,404 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Performs the bmp decoding operation. + /// + internal sealed class BmpDecoderCore + { + /// + /// The mask for the red part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16RMask = 0x00007C00; + + /// + /// The mask for the green part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16GMask = 0x000003E0; + + /// + /// The mask for the blue part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16BMask = 0x0000001F; + + /// + /// The stream to decode from. + /// + private Stream currentStream; + + /// + /// The file header containing general information. + /// TODO: Why is this not used? We advance the stream but do not use the values parsed. + /// + private BmpFileHeader fileHeader; + + /// + /// The info header containing detailed information about the bitmap. + /// + private BmpInfoHeader infoHeader; + + /// + /// Decodes the image from the specified this._stream and sets + /// the data to image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image, where the data should be set to. + /// Cannot be null (Nothing in Visual Basic). + /// The stream, where the image should be + /// decoded from. Cannot be null (Nothing in Visual Basic). + /// + /// is null. + /// - or - + /// is null. + /// + public void Decode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + this.currentStream = stream; + + try + { + this.ReadFileHeader(); + this.ReadInfoHeader(); + + // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 + // If the height is negative, then this is a Windows bitmap whose origin + // is the upper-left corner and not the lower-left.The inverted flag + // indicates a lower-left origin.Our code will be outputting an + // upper-left origin pixel array. + bool inverted = false; + if (this.infoHeader.Height < 0) + { + inverted = true; + this.infoHeader.Height = -this.infoHeader.Height; + } + + int colorMapSize = -1; + + if (this.infoHeader.ClrUsed == 0) + { + if (this.infoHeader.BitsPerPixel == 1 || + this.infoHeader.BitsPerPixel == 4 || + this.infoHeader.BitsPerPixel == 8) + { + colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4; + } + } + else + { + colorMapSize = this.infoHeader.ClrUsed * 4; + } + + byte[] palette = null; + + if (colorMapSize > 0) + { + // 256 * 4 + if (colorMapSize > 1024) + { + throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); + } + + palette = new byte[colorMapSize]; + + this.currentStream.Read(palette, 0, colorMapSize); + } + + if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight) + { + throw new ArgumentOutOfRangeException( + $"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " + + $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); + } + + image.InitPixels(this.infoHeader.Width, this.infoHeader.Height); + + using (PixelAccessor pixels = image.Lock()) + { + switch (this.infoHeader.Compression) + { + case BmpCompression.RGB: + if (this.infoHeader.HeaderSize != 40) + { + throw new ImageFormatException($"Header Size value '{this.infoHeader.HeaderSize}' is not valid."); + } + + if (this.infoHeader.BitsPerPixel == 32) + { + this.ReadRgb32(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel == 24) + { + this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel == 16) + { + this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel <= 8) + { + this.ReadRgbPalette(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, inverted); + } + + break; + default: + throw new NotSupportedException("Does not support this kind of bitmap files."); + } + } + } + catch (IndexOutOfRangeException e) + { + throw new ImageFormatException("Bitmap does not have a valid format.", e); + } + } + + /// + /// Returns the y- value based on the given height. + /// + /// The y- value representing the current row. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + /// The representing the inverted value. + private static int Invert(int y, int height, bool inverted) + { + int row; + + if (!inverted) + { + row = height - y - 1; + } + else + { + row = y; + } + + return row; + } + + /// + /// Calculates the amount of bytes to pad a row. + /// + /// The image width. + /// The pixel component count. + /// + /// The . + /// + private static int CalculatePadding(int width, int componentCount) + { + int padding = (width * componentCount) % 4; + + if (padding != 0) + { + padding = 4 - padding; + } + + return padding; + } + + /// + /// Reads the color palette from the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to assign the palette to. + /// The containing the colors. + /// The width of the bitmap. + /// The height of the bitmap. + /// The number of bits per pixel. + /// Whether the bitmap is inverted. + private void ReadRgbPalette(PixelAccessor pixels, byte[] colors, int width, int height, int bits, bool inverted) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // Pixels per byte (bits per pixel) + int ppb = 8 / bits; + + int arrayWidth = (width + ppb - 1) / ppb; + + // Bit mask + int mask = 0xFF >> (8 - bits); + + // Rows are aligned on 4 byte boundaries + int padding = arrayWidth % 4; + if (padding != 0) + { + padding = 4 - padding; + } + + byte[] row = new byte[arrayWidth + padding]; + TColor color = default(TColor); + + for (int y = 0; y < height; y++) + { + int newY = Invert(y, height, inverted); + + this.currentStream.Read(row, 0, row.Length); + + int offset = 0; + for (int x = 0; x < arrayWidth; x++) + { + int colOffset = x * ppb; + + for (int shift = 0; shift < ppb && (x + shift) < width; shift++) + { + int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4; + int newX = colOffset + shift; + + // Stored in b-> g-> r order. + color.PackFromBytes(colors[colorIndex + 2], colors[colorIndex + 1], colors[colorIndex], 255); + pixels[newX, newY] = color; + } + + offset++; + } + } + } + + /// + /// Reads the 16 bit color palette from the stream + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb16(PixelAccessor pixels, int width, int height, bool inverted) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // We divide here as we will store the colors in our floating point format. + const int ScaleR = 8; // 256/32 + const int ScaleG = 4; // 256/64 + const int ComponentCount = 2; + + TColor color = default(TColor); + using (PixelRow row = new PixelRow(width, ComponentOrder.XYZ)) + { + for (int y = 0; y < height; y++) + { + row.Read(this.currentStream); + + int newY = Invert(y, height, inverted); + + int offset = 0; + for (int x = 0; x < width; x++) + { + short temp = BitConverter.ToInt16(row.Bytes, offset); + + byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); + byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); + byte b = (byte)((temp & Rgb16BMask) * ScaleR); + + color.PackFromBytes(r, g, b, 255); + pixels[x, newY] = color; + offset += ComponentCount; + } + } + } + } + + /// + /// Reads the 24 bit color palette from the stream + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb24(PixelAccessor pixels, int width, int height, bool inverted) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int padding = CalculatePadding(width, 3); + using (PixelRow row = new PixelRow(width, ComponentOrder.ZYX, padding)) + { + for (int y = 0; y < height; y++) + { + row.Read(this.currentStream); + + int newY = Invert(y, height, inverted); + pixels.CopyFrom(row, newY); + } + } + } + + /// + /// Reads the 32 bit color palette from the stream + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb32(PixelAccessor pixels, int width, int height, bool inverted) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int padding = CalculatePadding(width, 4); + using (PixelRow row = new PixelRow(width, ComponentOrder.ZYXW, padding)) + { + for (int y = 0; y < height; y++) + { + row.Read(this.currentStream); + + int newY = Invert(y, height, inverted); + pixels.CopyFrom(row, newY); + } + } + } + + /// + /// Reads the from the stream. + /// + private void ReadInfoHeader() + { + byte[] data = new byte[BmpInfoHeader.Size]; + + this.currentStream.Read(data, 0, BmpInfoHeader.Size); + + this.infoHeader = new BmpInfoHeader + { + HeaderSize = BitConverter.ToInt32(data, 0), + Width = BitConverter.ToInt32(data, 4), + Height = BitConverter.ToInt32(data, 8), + Planes = BitConverter.ToInt16(data, 12), + BitsPerPixel = BitConverter.ToInt16(data, 14), + ImageSize = BitConverter.ToInt32(data, 20), + XPelsPerMeter = BitConverter.ToInt32(data, 24), + YPelsPerMeter = BitConverter.ToInt32(data, 28), + ClrUsed = BitConverter.ToInt32(data, 32), + ClrImportant = BitConverter.ToInt32(data, 36), + Compression = (BmpCompression)BitConverter.ToInt32(data, 16) + }; + } + + /// + /// Reads the from the stream. + /// + private void ReadFileHeader() + { + byte[] data = new byte[BmpFileHeader.Size]; + + this.currentStream.Read(data, 0, BmpFileHeader.Size); + + this.fileHeader = new BmpFileHeader + { + Type = BitConverter.ToInt16(data, 0), + FileSize = BitConverter.ToInt32(data, 2), + Reserved = BitConverter.ToInt32(data, 6), + Offset = BitConverter.ToInt32(data, 10) + }; + } + } +} diff --git a/src/ImageSharp46/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp46/Formats/Bmp/BmpEncoder.cs new file mode 100644 index 000000000..7205840b7 --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpEncoder.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Image encoder for writing an image to a stream as a Windows bitmap. + /// + /// The encoder can currently only write 24-bit rgb images to streams. + public class BmpEncoder : IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + /// Bitmap is a lossless format so this is not used in this encoder. + public int Quality { get; set; } + + /// + public string MimeType => "image/bmp"; + + /// + public string Extension => "bmp"; + + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase) + || extension.Equals("dip", StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + BmpEncoderCore encoder = new BmpEncoderCore(); + encoder.Encode(image, stream, this.BitsPerPixel); + } + } +} diff --git a/src/ImageSharp46/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp46/Formats/Bmp/BmpEncoderCore.cs new file mode 100644 index 000000000..7c3d18b93 --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpEncoderCore.cs @@ -0,0 +1,189 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.IO; + + using IO; + + /// + /// Image encoder for writing an image to a stream as a Windows bitmap. + /// + internal sealed class BmpEncoderCore + { + /// + /// The number of bits per pixel. + /// + private BmpBitsPerPixel bmpBitsPerPixel; + + /// + /// The amount to pad each row by. + /// + private int padding; + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The packed format. long, float. + /// The to encode from. + /// The to encode the image data to. + /// The + public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.bmpBitsPerPixel = bitsPerPixel; + + // Cast to int will get the bytes per pixel + short bpp = (short)(8 * (int)bitsPerPixel); + int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); + this.padding = bytesPerLine - (image.Width * (int)bitsPerPixel); + + // Do not use IDisposable pattern here as we want to preserve the stream. + EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream); + + BmpInfoHeader infoHeader = new BmpInfoHeader + { + HeaderSize = BmpInfoHeader.Size, + Height = image.Height, + Width = image.Width, + BitsPerPixel = bpp, + Planes = 1, + ImageSize = image.Height * bytesPerLine, + ClrUsed = 0, + ClrImportant = 0 + }; + + BmpFileHeader fileHeader = new BmpFileHeader + { + Type = 19778, // BM + Offset = 54, + FileSize = 54 + infoHeader.ImageSize + }; + + WriteHeader(writer, fileHeader); + this.WriteInfo(writer, infoHeader); + this.WriteImage(writer, image); + + writer.Flush(); + } + + /// + /// Writes the bitmap header data to the binary stream. + /// + /// + /// The containing the stream to write to. + /// + /// + /// The containing the header data. + /// + private static void WriteHeader(EndianBinaryWriter writer, BmpFileHeader fileHeader) + { + writer.Write(fileHeader.Type); + writer.Write(fileHeader.FileSize); + writer.Write(fileHeader.Reserved); + writer.Write(fileHeader.Offset); + } + + /// + /// Writes the bitmap information to the binary stream. + /// + /// + /// The containing the stream to write to. + /// + /// + /// The containing the detailed information about the image. + /// + private void WriteInfo(EndianBinaryWriter writer, BmpInfoHeader infoHeader) + { + writer.Write(infoHeader.HeaderSize); + writer.Write(infoHeader.Width); + writer.Write(infoHeader.Height); + writer.Write(infoHeader.Planes); + writer.Write(infoHeader.BitsPerPixel); + writer.Write((int)infoHeader.Compression); + writer.Write(infoHeader.ImageSize); + writer.Write(infoHeader.XPelsPerMeter); + writer.Write(infoHeader.YPelsPerMeter); + writer.Write(infoHeader.ClrUsed); + writer.Write(infoHeader.ClrImportant); + } + + /// + /// Writes the pixel data to the binary stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The containing the stream to write to. + /// + /// The containing pixel data. + /// + private void WriteImage(EndianBinaryWriter writer, ImageBase image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + using (PixelAccessor pixels = image.Lock()) + { + switch (this.bmpBitsPerPixel) + { + case BmpBitsPerPixel.Pixel32: + this.Write32Bit(writer, pixels); + break; + + case BmpBitsPerPixel.Pixel24: + this.Write24Bit(writer, pixels); + break; + } + } + } + + /// + /// Writes the 32bit color palette to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The containing the stream to write to. + /// The containing pixel data. + private void Write32Bit(EndianBinaryWriter writer, PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + using (PixelRow row = new PixelRow(pixels.Width, ComponentOrder.ZYXW, this.padding)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + pixels.CopyTo(row, y); + writer.Write(row.Bytes); + } + } + } + + /// + /// Writes the 24bit color palette to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The containing the stream to write to. + /// The containing pixel data. + private void Write24Bit(EndianBinaryWriter writer, PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + using (PixelRow row = new PixelRow(pixels.Width, ComponentOrder.ZYX, this.padding)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + pixels.CopyTo(row, y); + writer.Write(row.Bytes); + } + } + } + } +} diff --git a/src/ImageSharp46/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp46/Formats/Bmp/BmpFileHeader.cs new file mode 100644 index 000000000..4be602f4b --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpFileHeader.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Stores general information about the Bitmap file. + /// + /// + /// + /// The first two bytes of the Bitmap file format + /// (thus the Bitmap header) are stored in big-endian order. + /// All of the other integer values are stored in little-endian format + /// (i.e. least-significant byte first). + /// + internal class BmpFileHeader + { + /// + /// Defines of the data structure in the bitmap file. + /// + public const int Size = 14; + + /// + /// Gets or sets the Bitmap identifier. + /// The field used to identify the bitmap file: 0x42 0x4D + /// (Hex code points for B and M) + /// + public short Type { get; set; } + + /// + /// Gets or sets the size of the bitmap file in bytes. + /// + public int FileSize { get; set; } + + /// + /// Gets or sets any reserved data; actual value depends on the application + /// that creates the image. + /// + public int Reserved { get; set; } + + /// + /// Gets or sets the offset, i.e. starting address, of the byte where + /// the bitmap data can be found. + /// + public int Offset { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/Bmp/BmpFormat.cs b/src/ImageSharp46/Formats/Bmp/BmpFormat.cs new file mode 100644 index 000000000..b01f3ab32 --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the means to encode and decode bitmap images. + /// + public class BmpFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new BmpDecoder(); + + /// + public IImageEncoder Encoder => new BmpEncoder(); + } +} diff --git a/src/ImageSharp46/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp46/Formats/Bmp/BmpInfoHeader.cs new file mode 100644 index 000000000..e652cb504 --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/BmpInfoHeader.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats +{ + /// + /// This block of bytes tells the application detailed information + /// about the image, which will be used to display the image on + /// the screen. + /// + /// + internal class BmpInfoHeader + { + /// + /// Defines of the data structure in the bitmap file. + /// + public const int Size = 40; + + /// + /// Gets or sets the size of this header (40 bytes) + /// + public int HeaderSize { get; set; } + + /// + /// Gets or sets the bitmap width in pixels (signed integer). + /// + public int Width { get; set; } + + /// + /// Gets or sets the bitmap height in pixels (signed integer). + /// + public int Height { get; set; } + + /// + /// Gets or sets the number of color planes being used. Must be set to 1. + /// + public short Planes { get; set; } + + /// + /// Gets or sets the number of bits per pixel, which is the color depth of the image. + /// Typical values are 1, 4, 8, 16, 24 and 32. + /// + public short BitsPerPixel { get; set; } + + /// + /// Gets or sets the compression method being used. + /// See the next table for a list of possible values. + /// + public BmpCompression Compression { get; set; } + + /// + /// Gets or sets the image size. This is the size of the raw bitmap data (see below), + /// and should not be confused with the file size. + /// + public int ImageSize { get; set; } + + /// + /// Gets or sets the horizontal resolution of the image. + /// (pixel per meter, signed integer) + /// + public int XPelsPerMeter { get; set; } + + /// + /// Gets or sets the vertical resolution of the image. + /// (pixel per meter, signed integer) + /// + public int YPelsPerMeter { get; set; } + + /// + /// Gets or sets the number of colors in the color palette, + /// or 0 to default to 2^n. + /// + public int ClrUsed { get; set; } + + /// + /// Gets or sets the number of important colors used, + /// or 0 when every color is important{ get; set; } generally ignored. + /// + public int ClrImportant { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/Bmp/README.md b/src/ImageSharp46/Formats/Bmp/README.md new file mode 100644 index 000000000..d07283843 --- /dev/null +++ b/src/ImageSharp46/Formats/Bmp/README.md @@ -0,0 +1,8 @@ +Encoder/Decoder adapted from: + +https://github.com/yufeih/Nine.Imaging/ +https://imagetools.codeplex.com/ + +TODO: + +- Add support for all bitmap formats. diff --git a/src/ImageSharp46/Formats/Gif/DisposalMethod.cs b/src/ImageSharp46/Formats/Gif/DisposalMethod.cs new file mode 100644 index 000000000..80b5f3c6e --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/DisposalMethod.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Provides enumeration for instructing the decoder what to do with the last image + /// in an animation sequence. + /// section 23 + /// + public enum DisposalMethod + { + /// + /// No disposal specified. The decoder is not required to take any action. + /// + Unspecified = 0, + + /// + /// Do not dispose. The graphic is to be left in place. + /// + NotDispose = 1, + + /// + /// Restore to background color. The area used by the graphic must be restored to + /// the background color. + /// + RestoreToBackground = 2, + + /// + /// Restore to previous. The decoder is required to restore the area overwritten by the + /// graphic with what was there prior to rendering the graphic. + /// + RestoreToPrevious = 3 + } +} diff --git a/src/ImageSharp46/Formats/Gif/GifConstants.cs b/src/ImageSharp46/Formats/Gif/GifConstants.cs new file mode 100644 index 000000000..5334bcba3 --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/GifConstants.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Constants that define specific points within a gif. + /// + internal sealed class GifConstants + { + /// + /// The file type. + /// + public const string FileType = "GIF"; + + /// + /// The file version. + /// + public const string FileVersion = "89a"; + + /// + /// The extension block introducer !. + /// + public const byte ExtensionIntroducer = 0x21; + + /// + /// The graphic control label. + /// + public const byte GraphicControlLabel = 0xF9; + + /// + /// The application extension label. + /// + public const byte ApplicationExtensionLabel = 0xFF; + + /// + /// The application identification. + /// + public const string ApplicationIdentification = "NETSCAPE2.0"; + + /// + /// The application block size. + /// + public const byte ApplicationBlockSize = 0x0b; + + /// + /// The comment label. + /// + public const byte CommentLabel = 0xFE; + + /// + /// The maximum comment length. + /// + public const int MaxCommentLength = 1024 * 8; + + /// + /// The image descriptor label ,. + /// + public const byte ImageDescriptorLabel = 0x2C; + + /// + /// The plain text label. + /// + public const byte PlainTextLabel = 0x01; + + /// + /// The image label introducer ,. + /// + public const byte ImageLabel = 0x2C; + + /// + /// The terminator. + /// + public const byte Terminator = 0; + + /// + /// The end introducer trailer ;. + /// + public const byte EndIntroducer = 0x3B; + } +} diff --git a/src/ImageSharp46/Formats/Gif/GifDecoder.cs b/src/ImageSharp46/Formats/Gif/GifDecoder.cs new file mode 100644 index 000000000..4afc1cfdb --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/GifDecoder.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Decoder for generating an image out of a gif encoded stream. + /// + public class GifDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 6; + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + return extension.Equals("GIF", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + public bool IsSupportedFileFormat(byte[] header) + { + return header.Length >= 6 && + header[0] == 0x47 && // G + header[1] == 0x49 && // I + header[2] == 0x46 && // F + header[3] == 0x38 && // 8 + (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 + header[5] == 0x61; // a + } + + /// + public void Decode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + new GifDecoderCore().Decode(image, stream); + } + } +} diff --git a/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs new file mode 100644 index 000000000..29b34aa9a --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs @@ -0,0 +1,425 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Performs the gif decoding operation. + /// + /// The pixel format. + /// The packed format. uint, long, float. + internal class GifDecoderCore + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The image to decode the information to. + /// + private Image decodedImage; + + /// + /// The currently loaded stream. + /// + private Stream currentStream; + + /// + /// The global color table. + /// + private byte[] globalColorTable; + + /// + /// The current frame. + /// + private TColor[] currentFrame; + + /// + /// The logical screen descriptor. + /// + private GifLogicalScreenDescriptor logicalScreenDescriptor; + + /// + /// The graphics control extension. + /// + private GifGraphicsControlExtension graphicsControlExtension; + + /// + /// Decodes the stream to the image. + /// + /// The image to decode to. + /// The stream containing image data. + public void Decode(Image image, Stream stream) + { + this.decodedImage = image; + + this.currentStream = stream; + + // Skip the identifier + this.currentStream.Seek(6, SeekOrigin.Current); + this.ReadLogicalScreenDescriptor(); + + if (this.logicalScreenDescriptor.GlobalColorTableFlag) + { + this.globalColorTable = new byte[this.logicalScreenDescriptor.GlobalColorTableSize * 3]; + + // Read the global color table from the stream + stream.Read(this.globalColorTable, 0, this.globalColorTable.Length); + } + + // Loop though the respective gif parts and read the data. + int nextFlag = stream.ReadByte(); + while (nextFlag != GifConstants.Terminator) + { + if (nextFlag == GifConstants.ImageLabel) + { + this.ReadFrame(); + } + else if (nextFlag == GifConstants.ExtensionIntroducer) + { + int label = stream.ReadByte(); + switch (label) + { + case GifConstants.GraphicControlLabel: + this.ReadGraphicalControlExtension(); + break; + case GifConstants.CommentLabel: + this.ReadComments(); + break; + case GifConstants.ApplicationExtensionLabel: + this.Skip(12); // No need to read. + break; + case GifConstants.PlainTextLabel: + this.Skip(13); // Not supported by any known decoder. + break; + } + } + else if (nextFlag == GifConstants.EndIntroducer) + { + break; + } + + nextFlag = stream.ReadByte(); + } + } + + /// + /// Reads the graphic control extension. + /// + private void ReadGraphicalControlExtension() + { + byte[] buffer = new byte[6]; + + this.currentStream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[1]; + + this.graphicsControlExtension = new GifGraphicsControlExtension + { + DelayTime = BitConverter.ToInt16(buffer, 2), + TransparencyIndex = buffer[4], + TransparencyFlag = (packed & 0x01) == 1, + DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2) + }; + } + + /// + /// Reads the image descriptor + /// + /// + private GifImageDescriptor ReadImageDescriptor() + { + byte[] buffer = new byte[9]; + + this.currentStream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[8]; + + GifImageDescriptor imageDescriptor = new GifImageDescriptor + { + Left = BitConverter.ToInt16(buffer, 0), + Top = BitConverter.ToInt16(buffer, 2), + Width = BitConverter.ToInt16(buffer, 4), + Height = BitConverter.ToInt16(buffer, 6), + LocalColorTableFlag = ((packed & 0x80) >> 7) == 1, + LocalColorTableSize = 2 << (packed & 0x07), + InterlaceFlag = ((packed & 0x40) >> 6) == 1 + }; + + return imageDescriptor; + } + + /// + /// Reads the logical screen descriptor. + /// + private void ReadLogicalScreenDescriptor() + { + byte[] buffer = new byte[7]; + + this.currentStream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[4]; + + this.logicalScreenDescriptor = new GifLogicalScreenDescriptor + { + Width = BitConverter.ToInt16(buffer, 0), + Height = BitConverter.ToInt16(buffer, 2), + BackgroundColorIndex = buffer[5], + PixelAspectRatio = buffer[6], + GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1, + GlobalColorTableSize = 2 << (packed & 0x07) + }; + + if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4) + { + throw new ImageFormatException( + $"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'"); + } + + if (this.logicalScreenDescriptor.Width > this.decodedImage.MaxWidth || this.logicalScreenDescriptor.Height > this.decodedImage.MaxHeight) + { + throw new ArgumentOutOfRangeException( + $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{this.decodedImage.MaxWidth}x{this.decodedImage.MaxHeight}'"); + } + } + + /// + /// Skips the designated number of bytes in the stream. + /// + /// The number of bytes to skip. + private void Skip(int length) + { + this.currentStream.Seek(length, SeekOrigin.Current); + + int flag; + + while ((flag = this.currentStream.ReadByte()) != 0) + { + this.currentStream.Seek(flag, SeekOrigin.Current); + } + } + + /// + /// Reads the gif comments. + /// + private void ReadComments() + { + int flag; + + while ((flag = this.currentStream.ReadByte()) != 0) + { + if (flag > GifConstants.MaxCommentLength) + { + throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'"); + } + + byte[] buffer = new byte[flag]; + + this.currentStream.Read(buffer, 0, flag); + + this.decodedImage.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(buffer))); + } + } + + /// + /// Reads an individual gif frame. + /// + private void ReadFrame() + { + GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); + + byte[] localColorTable = this.ReadFrameLocalColorTable(imageDescriptor); + + byte[] indices = this.ReadFrameIndices(imageDescriptor); + + // Determine the color table for this frame. If there is a local one, use it + // otherwise use the global color table. + byte[] colorTable = localColorTable ?? this.globalColorTable; + + this.ReadFrameColors(indices, colorTable, imageDescriptor); + + // Skip any remaining blocks + this.Skip(0); + } + + /// + /// Reads the frame indices marking the color to use for each pixel. + /// + /// The . + /// The + private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor) + { + int dataSize = this.currentStream.ReadByte(); + LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream); + + byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); + + return indices; + } + + /// + /// Reads the local color table from the current frame. + /// + /// The . + /// The + private byte[] ReadFrameLocalColorTable(GifImageDescriptor imageDescriptor) + { + byte[] localColorTable = null; + + if (imageDescriptor.LocalColorTableFlag) + { + localColorTable = new byte[imageDescriptor.LocalColorTableSize * 3]; + + this.currentStream.Read(localColorTable, 0, localColorTable.Length); + } + + return localColorTable; + } + + /// + /// Reads the frames colors, mapping indices to colors. + /// + /// The indexed pixels. + /// The color table containing the available colors. + /// The + private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) + { + int imageWidth = this.logicalScreenDescriptor.Width; + int imageHeight = this.logicalScreenDescriptor.Height; + + if (this.currentFrame == null) + { + this.currentFrame = new TColor[imageWidth * imageHeight]; + } + + TColor[] lastFrame = null; + + if (this.graphicsControlExtension != null && + this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) + { + lastFrame = new TColor[imageWidth * imageHeight]; + + Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); + } + + int offset, i = 0; + int interlacePass = 0; // The interlace pass + int interlaceIncrement = 8; // The interlacing line increment + int interlaceY = 0; // The current interlaced line + + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + { + // Check if this image is interlaced. + int writeY; // the target y offset to write to + if (descriptor.InterlaceFlag) + { + // If so then we read lines at predetermined offsets. + // When an entire image height worth of offset lines has been read we consider this a pass. + // With each pass the number of offset lines changes and the starting line changes. + if (interlaceY >= descriptor.Height) + { + interlacePass++; + switch (interlacePass) + { + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; + } + } + + writeY = interlaceY + descriptor.Top; + + interlaceY += interlaceIncrement; + } + else + { + writeY = y; + } + + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + offset = (writeY * imageWidth) + x; + int index = indices[i]; + + if (this.graphicsControlExtension == null || + this.graphicsControlExtension.TransparencyFlag == false || + this.graphicsControlExtension.TransparencyIndex != index) + { + // Stored in r-> g-> b-> a order. + int indexOffset = index * 3; + TColor pixel = default(TColor); + pixel.PackFromVector4(new Color(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2]).ToVector4()); + this.currentFrame[offset] = pixel; + } + + i++; + } + } + + TColor[] pixels = new TColor[imageWidth * imageHeight]; + + Array.Copy(this.currentFrame, pixels, pixels.Length); + + ImageBase currentImage; + + if (this.decodedImage.Pixels == null) + { + currentImage = this.decodedImage; + currentImage.SetPixels(imageWidth, imageHeight, pixels); + currentImage.Quality = colorTable.Length / 3; + + if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + { + this.decodedImage.FrameDelay = this.graphicsControlExtension.DelayTime; + } + } + else + { + ImageFrame frame = new ImageFrame(); + + currentImage = frame; + currentImage.SetPixels(imageWidth, imageHeight, pixels); + currentImage.Quality = colorTable.Length / 3; + + if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + { + currentImage.FrameDelay = this.graphicsControlExtension.DelayTime; + } + + this.decodedImage.Frames.Add(frame); + } + + if (this.graphicsControlExtension != null) + { + if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) + { + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + { + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + offset = (y * imageWidth) + x; + + // Stored in r-> g-> b-> a order. + this.currentFrame[offset] = default(TColor); + } + } + } + else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) + { + this.currentFrame = lastFrame; + } + } + } + } +} diff --git a/src/ImageSharp46/Formats/Gif/GifEncoder.cs b/src/ImageSharp46/Formats/Gif/GifEncoder.cs new file mode 100644 index 000000000..57db04301 --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/GifEncoder.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + using ImageSharp.Quantizers; + + /// + /// Image encoder for writing image data to a stream in gif format. + /// + public class GifEncoder : IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + public int Quality { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + public string Extension => "gif"; + + /// + public string MimeType => "image/gif"; + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + GifEncoderCore encoder = new GifEncoderCore + { + Quality = this.Quality, + Quantizer = this.Quantizer, + Threshold = this.Threshold + }; + + encoder.Encode(image, stream); + } + } +} diff --git a/src/ImageSharp46/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp46/Formats/Gif/GifEncoderCore.cs new file mode 100644 index 000000000..746e27ff7 --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/GifEncoderCore.cs @@ -0,0 +1,349 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + using IO; + using Quantizers; + + /// + /// Performs the gif encoding operation. + /// + internal sealed class GifEncoderCore + { + /// + /// The number of bits requires to store the image palette. + /// + private int bitDepth; + + /// + /// Gets or sets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + public int Quality { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + if (this.Quantizer == null) + { + this.Quantizer = new OctreeQuantizer(); + } + + // Do not use IDisposable pattern here as we want to preserve the stream. + EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream); + + // Ensure that quality can be set but has a fallback. + int quality = this.Quality > 0 ? this.Quality : image.Quality; + this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; + + // Get the number of bits. + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); + + // Quantize the image returning a palette. + QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + + int index = GetTransparentIndex(quantized); + + // Write the header. + this.WriteHeader(writer); + + // Write the LSD. We'll use local color tables for now. + this.WriteLogicalScreenDescriptor(image, writer, index); + + // Write the first frame. + this.WriteGraphicalControlExtension(image, writer, index); + this.WriteImageDescriptor(image, writer); + this.WriteColorTable(quantized, writer); + this.WriteImageData(quantized, writer); + + // Write additional frames. + if (image.Frames.Any()) + { + this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); + foreach (ImageFrame frame in image.Frames) + { + QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, this.Quality); + + this.WriteGraphicalControlExtension(frame, writer, GetTransparentIndex(quantizedFrame)); + this.WriteImageDescriptor(frame, writer); + this.WriteColorTable(quantizedFrame, writer); + this.WriteImageData(quantizedFrame, writer); + } + } + + // TODO: Write Comments extension etc + writer.Write(GifConstants.EndIntroducer); + } + + /// + /// Returns the index of the most transparent color in the palette. + /// + /// + /// The quantized. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// + /// The . + /// + private static int GetTransparentIndex(QuantizedImage quantized) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // Find the lowest alpha value and make it the transparent index. + int index = 255; + float alpha = 1; + for (int i = 0; i < quantized.Palette.Length; i++) + { + float a = quantized.Palette[i].ToVector4().W; + if (a < alpha) + { + alpha = a; + index = i; + } + } + + return index; + } + + /// + /// Writes the file header signature and version to the stream. + /// + /// The writer to write to the stream with. + private void WriteHeader(EndianBinaryWriter writer) + { + writer.Write((GifConstants.FileType + GifConstants.FileVersion).ToCharArray()); + } + + /// + /// Writes the logical screen descriptor to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to encode. + /// The writer to write to the stream with. + /// The transparency index to set the default background index to. + private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) + where TColor : struct, IPackedPixel + where TPacked : struct + { + GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor + { + Width = (short)image.Width, + Height = (short)image.Height, + GlobalColorTableFlag = false, // Always false for now. + GlobalColorTableSize = this.bitDepth - 1, + BackgroundColorIndex = (byte)(tranparencyIndex > -1 ? tranparencyIndex : 255) + }; + + writer.Write((ushort)descriptor.Width); + writer.Write((ushort)descriptor.Height); + + PackedField field = default(PackedField); + field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) + field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution + field.SetBit(4, false); // 5 : GCT sort flag = 0 + field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1) + + // Reduce the number of writes + byte[] arr = + { + field.Byte, + descriptor.BackgroundColorIndex, // Background Color Index + descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1 + }; + + writer.Write(arr); + } + + /// + /// Writes the application extension to the stream. + /// + /// The writer to write to the stream with. + /// The animated image repeat count. + /// The number of image frames. + private void WriteApplicationExtension(EndianBinaryWriter writer, ushort repeatCount, int frames) + { + // Application Extension Header + if (repeatCount != 1 && frames > 0) + { + byte[] ext = + { + GifConstants.ExtensionIntroducer, + GifConstants.ApplicationExtensionLabel, + GifConstants.ApplicationBlockSize + }; + + writer.Write(ext); + + writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0 + writer.Write((byte)3); // Application block length + writer.Write((byte)1); // Data sub-block index (always 1) + + // 0 means loop indefinitely. Count is set as play n + 1 times. + repeatCount = (ushort)(Math.Max((ushort)0, repeatCount) - 1); + + writer.Write(repeatCount); // Repeat count for images. + + writer.Write(GifConstants.Terminator); // Terminator + } + } + + /// + /// Writes the graphics control extension to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to encode. + /// The stream to write to. + /// The index of the color in the color palette to make transparent. + private void WriteGraphicalControlExtension(ImageBase image, EndianBinaryWriter writer, int transparencyIndex) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // TODO: Check transparency logic. + bool hasTransparent = transparencyIndex > -1; + DisposalMethod disposalMethod = hasTransparent + ? DisposalMethod.RestoreToBackground + : DisposalMethod.Unspecified; + + GifGraphicsControlExtension extension = new GifGraphicsControlExtension() + { + DisposalMethod = disposalMethod, + TransparencyFlag = hasTransparent, + TransparencyIndex = transparencyIndex, + DelayTime = image.FrameDelay + }; + + // Reduce the number of writes. + byte[] intro = + { + GifConstants.ExtensionIntroducer, + GifConstants.GraphicControlLabel, + 4 // Size + }; + + writer.Write(intro); + + PackedField field = default(PackedField); + field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal + + // TODO: Allow this as an option. + field.SetBit(6, false); // 7 : User input - 0 = none + field.SetBit(7, extension.TransparencyFlag); // 8: Has transparent. + + writer.Write(field.Byte); + writer.Write((ushort)extension.DelayTime); + writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); + writer.Write(GifConstants.Terminator); + } + + /// + /// Writes the image descriptor to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to be encoded. + /// The stream to write to. + private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer) + where TColor : struct, IPackedPixel + where TPacked : struct + { + writer.Write(GifConstants.ImageDescriptorLabel); // 2c + // TODO: Can we capture this? + writer.Write((ushort)0); // Left position + writer.Write((ushort)0); // Top position + writer.Write((ushort)image.Width); + writer.Write((ushort)image.Height); + + PackedField field = default(PackedField); + field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) + field.SetBit(1, false); // 2: Interlace flag 0 + field.SetBit(2, false); // 3: Sort flag 0 + field.SetBits(5, 3, this.bitDepth - 1); // 4-5: Reserved, 6-8 : LCT size. 2^(N+1) + + writer.Write(field.Byte); + } + + /// + /// Writes the color table to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to encode. + /// The writer to write to the stream with. + private void WriteColorTable(QuantizedImage image, EndianBinaryWriter writer) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // Grab the palette and write it to the stream. + TColor[] palette = image.Palette; + int pixelCount = palette.Length; + + // Get max colors for bit depth. + int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; + byte[] colorTable = new byte[colorTableLength]; + + Parallel.For( + 0, + pixelCount, + i => + { + int offset = i * 3; + Color color = new Color(palette[i].ToVector4()); + + colorTable[offset] = color.R; + colorTable[offset + 1] = color.G; + colorTable[offset + 2] = color.B; + }); + + writer.Write(colorTable, 0, colorTableLength); + } + + /// + /// Writes the image pixel data to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The containing indexed pixels. + /// The stream to write to. + private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) + where TColor : struct, IPackedPixel + where TPacked : struct + { + byte[] indexedPixels = image.Pixels; + + LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth); + encoder.Encode(writer.BaseStream); + } + } +} diff --git a/src/ImageSharp46/Formats/Gif/GifFormat.cs b/src/ImageSharp46/Formats/Gif/GifFormat.cs new file mode 100644 index 000000000..4c584c7ba --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/GifFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the means to encode and decode gif images. + /// + public class GifFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new GifDecoder(); + + /// + public IImageEncoder Encoder => new GifEncoder(); + } +} diff --git a/src/ImageSharp46/Formats/Gif/LzwDecoder.cs b/src/ImageSharp46/Formats/Gif/LzwDecoder.cs new file mode 100644 index 000000000..b77d01739 --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/LzwDecoder.cs @@ -0,0 +1,228 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.IO; + + /// + /// Decompresses and decodes data using the dynamic LZW algorithms. + /// + internal sealed class LzwDecoder + { + /// + /// The max decoder pixel stack size. + /// + private const int MaxStackSize = 4096; + + /// + /// The null code. + /// + private const int NullCode = -1; + + /// + /// The stream to decode. + /// + private readonly Stream stream; + + /// + /// Initializes a new instance of the class + /// and sets the stream, where the compressed data should be read from. + /// + /// The stream to read from. + /// is null. + public LzwDecoder(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + this.stream = stream; + } + + /// + /// Decodes and decompresses all pixel indices from the stream. + /// + /// The width of the pixel index array. + /// The height of the pixel index array. + /// Size of the data. + /// The decoded and uncompressed array. + public byte[] DecodePixels(int width, int height, int dataSize) + { + Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); + + // The resulting index table. + byte[] pixels = new byte[width * height]; + + // Calculate the clear code. The value of the clear code is 2 ^ dataSize + int clearCode = 1 << dataSize; + + int codeSize = dataSize + 1; + + // Calculate the end code + int endCode = clearCode + 1; + + // Calculate the available code. + int availableCode = clearCode + 2; + + // Jillzhangs Code see: http://giflib.codeplex.com/ + // Adapted from John Cristy's ImageMagick. + int code; + int oldCode = NullCode; + int codeMask = (1 << codeSize) - 1; + int bits = 0; + + int[] prefix = new int[MaxStackSize]; + int[] suffix = new int[MaxStackSize]; + int[] pixelStatck = new int[MaxStackSize + 1]; + + int top = 0; + int count = 0; + int bi = 0; + int xyz = 0; + + int data = 0; + int first = 0; + + for (code = 0; code < clearCode; code++) + { + prefix[code] = 0; + suffix[code] = (byte)code; + } + + byte[] buffer = null; + while (xyz < pixels.Length) + { + if (top == 0) + { + if (bits < codeSize) + { + // Load bytes until there are enough bits for a code. + if (count == 0) + { + // Read a new data block. + buffer = this.ReadBlock(); + count = buffer.Length; + if (count == 0) + { + break; + } + + bi = 0; + } + + if (buffer != null) + { + data += buffer[bi] << bits; + } + + bits += 8; + bi++; + count--; + continue; + } + + // Get the next code + code = data & codeMask; + data >>= codeSize; + bits -= codeSize; + + // Interpret the code + if (code > availableCode || code == endCode) + { + break; + } + + if (code == clearCode) + { + // Reset the decoder + codeSize = dataSize + 1; + codeMask = (1 << codeSize) - 1; + availableCode = clearCode + 2; + oldCode = NullCode; + continue; + } + + if (oldCode == NullCode) + { + pixelStatck[top++] = suffix[code]; + oldCode = code; + first = code; + continue; + } + + int inCode = code; + if (code == availableCode) + { + pixelStatck[top++] = (byte)first; + + code = oldCode; + } + + while (code > clearCode) + { + pixelStatck[top++] = suffix[code]; + code = prefix[code]; + } + + first = suffix[code]; + + pixelStatck[top++] = suffix[code]; + + // Fix for Gifs that have "deferred clear code" as per here : + // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 + if (availableCode < MaxStackSize) + { + prefix[availableCode] = oldCode; + suffix[availableCode] = first; + availableCode++; + if (availableCode == codeMask + 1 && availableCode < MaxStackSize) + { + codeSize++; + codeMask = (1 << codeSize) - 1; + } + } + + oldCode = inCode; + } + + // Pop a pixel off the pixel stack. + top--; + + // Clear missing pixels + pixels[xyz++] = (byte)pixelStatck[top]; + } + + return pixels; + } + + /// + /// Reads the next data block from the stream. A data block begins with a byte, + /// which defines the size of the block, followed by the block itself. + /// + /// + /// The . + /// + private byte[] ReadBlock() + { + int blockSize = this.stream.ReadByte(); + return this.ReadBytes(blockSize); + } + + /// + /// Reads the specified number of bytes from the data stream. + /// + /// + /// The number of bytes to read. + /// + /// + /// The . + /// + private byte[] ReadBytes(int length) + { + byte[] buffer = new byte[length]; + this.stream.Read(buffer, 0, length); + return buffer; + } + } +} diff --git a/src/ImageSharp46/Formats/Gif/LzwEncoder.cs b/src/ImageSharp46/Formats/Gif/LzwEncoder.cs new file mode 100644 index 000000000..003cfa834 --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/LzwEncoder.cs @@ -0,0 +1,419 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. + /// + /// + /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 + /// + /// GIFCOMPR.C - GIF Image compression routines + /// + /// + /// Lempel-Ziv compression based on 'compress'. GIF modifications by + /// David Rowley (mgardi@watdcsu.waterloo.edu) + /// + /// GIF Image compression - modified 'compress' + /// + /// Based on: compress.c - File compression ala IEEE Computer, June 1984. + /// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + /// Jim McKie (decvax!mcvax!jim) + /// Steve Davies (decvax!vax135!petsd!peora!srd) + /// Ken Turkowski (decvax!decwrl!turtlevax!ken) + /// James A. Woods (decvax!ihnp4!ames!jaw) + /// Joe Orost (decvax!vax135!petsd!joe) + /// + /// + internal sealed class LzwEncoder + { + private const int Eof = -1; + + private const int Bits = 12; + + private const int HashSize = 5003; // 80% occupancy + + private readonly byte[] pixelArray; + + private readonly int initialCodeSize; + + private readonly int[] hashTable = new int[HashSize]; + + private readonly int[] codeTable = new int[HashSize]; + + private readonly int[] masks = + { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF + }; + + /// + /// Define the storage for the packet accumulator. + /// + private readonly byte[] accumulators = new byte[256]; + + /// + /// The current pixel + /// + private int currentPixel; + + /// + /// Number of bits/code + /// + private int bitCount; + + /// + /// User settable max # bits/code + /// + private int maxbits = Bits; + + /// + /// maximum code, given bitCount + /// + private int maxcode; + + /// + /// should NEVER generate this code + /// + private int maxmaxcode = 1 << Bits; + + /// + /// For dynamic table sizing + /// + private int hsize = HashSize; + + /// + /// First unused entry + /// + private int freeEntry; + + /// + /// Block compression parameters -- after all codes are used up, + /// and compression rate changes, start over. + /// + private bool clearFlag; + + // Algorithm: use open addressing double hashing (no chaining) on the + // prefix code / next character combination. We do a variant of Knuth's + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + // secondary probe. Here, the modular division first probe is gives way + // to a faster exclusive-or manipulation. Also do block compression with + // an adaptive reset, whereby the code table is cleared when the compression + // ratio decreases, but after the table fills. The variable-length output + // codes are re-sized at this point, and a special CLEAR code is generated + // for the decompressor. Late addition: construct the table according to + // file size for noticeable speed improvement on small files. Please direct + // questions about this implementation to ames!jaw. + private int globalInitialBits; + + private int clearCode; + + private int eofCode; + + // output + // + // Output the given code. + // Inputs: + // code: A bitCount-bit integer. If == -1, then EOF. This assumes + // that bitCount =< wordsize - 1. + // Outputs: + // Outputs code to the file. + // Assumptions: + // Chars are 8 bits long. + // Algorithm: + // Maintain a BITS character long buffer (so that 8 codes will + // fit in it exactly). Use the VAX insv instruction to insert each + // code in turn. When the buffer fills up empty it and start over. + private int currentAccumulator; + + private int currentBits; + + /// + /// Number of characters so far in this 'packet' + /// + private int accumulatorCount; + + /// + /// Initializes a new instance of the class. + /// + /// The array of indexed pixels. + /// The color depth in bits. + public LzwEncoder(byte[] indexedPixels, int colorDepth) + { + this.pixelArray = indexedPixels; + this.initialCodeSize = Math.Max(2, colorDepth); + } + + /// + /// Encodes and compresses the indexed pixels to the stream. + /// + /// The stream to write to. + public void Encode(Stream stream) + { + // Write "initial code size" byte + stream.WriteByte((byte)this.initialCodeSize); + + this.currentPixel = 0; + + // Compress and write the pixel data + this.Compress(this.initialCodeSize + 1, stream); + + // Write block terminator + stream.WriteByte(GifConstants.Terminator); + } + + /// + /// Gets the maximum code value + /// + /// The number of bits + /// See + private static int GetMaxcode(int bitCount) + { + return (1 << bitCount) - 1; + } + + /// + /// Add a character to the end of the current packet, and if it is 254 characters, + /// flush the packet to disk. + /// + /// The character to add. + /// The stream to write to. + private void AddCharacter(byte c, Stream stream) + { + this.accumulators[this.accumulatorCount++] = c; + if (this.accumulatorCount >= 254) + { + this.FlushPacket(stream); + } + } + + /// + /// Table clear for block compress + /// + /// The output stream. + private void ClearBlock(Stream stream) + { + this.ResetCodeTable(this.hsize); + this.freeEntry = this.clearCode + 2; + this.clearFlag = true; + + this.Output(this.clearCode, stream); + } + + /// + /// Reset the code table. + /// + /// The hash size. + private void ResetCodeTable(int size) + { + for (int i = 0; i < size; ++i) + { + this.hashTable[i] = -1; + } + } + + /// + /// Compress the packets to the stream. + /// + /// The initial bits. + /// The stream to write to. + private void Compress(int intialBits, Stream stream) + { + int fcode; + int c; + int ent; + int hsizeReg; + int hshift; + + // Set up the globals: globalInitialBits - initial number of bits + this.globalInitialBits = intialBits; + + // Set up the necessary values + this.clearFlag = false; + this.bitCount = this.globalInitialBits; + this.maxcode = GetMaxcode(this.bitCount); + + this.clearCode = 1 << (intialBits - 1); + this.eofCode = this.clearCode + 1; + this.freeEntry = this.clearCode + 2; + + this.accumulatorCount = 0; // clear packet + + ent = this.NextPixel(); + + hshift = 0; + for (fcode = this.hsize; fcode < 65536; fcode *= 2) + { + ++hshift; + } + + hshift = 8 - hshift; // set hash code range bound + + hsizeReg = this.hsize; + + this.ResetCodeTable(hsizeReg); // clear hash table + + this.Output(this.clearCode, stream); + + while ((c = this.NextPixel()) != Eof) + { + fcode = (c << this.maxbits) + ent; + int i = (c << hshift) ^ ent /* = 0 */; + + if (this.hashTable[i] == fcode) + { + ent = this.codeTable[i]; + continue; + } + + // Non-empty slot + if (this.hashTable[i] >= 0) + { + int disp = hsizeReg - i; + if (i == 0) + { + disp = 1; + } + + do + { + if ((i -= disp) < 0) + { + i += hsizeReg; + } + + if (this.hashTable[i] == fcode) + { + ent = this.codeTable[i]; + break; + } + } + while (this.hashTable[i] >= 0); + + if (this.hashTable[i] == fcode) + { + continue; + } + } + + this.Output(ent, stream); + ent = c; + if (this.freeEntry < this.maxmaxcode) + { + this.codeTable[i] = this.freeEntry++; // code -> hashtable + this.hashTable[i] = fcode; + } + else + { + this.ClearBlock(stream); + } + } + + // Put out the final code. + this.Output(ent, stream); + + this.Output(this.eofCode, stream); + } + + /// + /// Flush the packet to disk, and reset the accumulator. + /// + /// The output stream. + private void FlushPacket(Stream outStream) + { + if (this.accumulatorCount > 0) + { + outStream.WriteByte((byte)this.accumulatorCount); + outStream.Write(this.accumulators, 0, this.accumulatorCount); + this.accumulatorCount = 0; + } + } + + /// + /// Return the next pixel from the image + /// + /// + /// The + /// + private int NextPixel() + { + if (this.currentPixel == this.pixelArray.Length) + { + return Eof; + } + + if (this.currentPixel == this.pixelArray.Length) + { + return Eof; + } + + this.currentPixel++; + return this.pixelArray[this.currentPixel - 1] & 0xff; + } + + /// + /// Output the current code to the stream. + /// + /// The code. + /// The stream to write to. + private void Output(int code, Stream outs) + { + this.currentAccumulator &= this.masks[this.currentBits]; + + if (this.currentBits > 0) + { + this.currentAccumulator |= code << this.currentBits; + } + else + { + this.currentAccumulator = code; + } + + this.currentBits += this.bitCount; + + while (this.currentBits >= 8) + { + this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); + this.currentAccumulator >>= 8; + this.currentBits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if (this.freeEntry > this.maxcode || this.clearFlag) + { + if (this.clearFlag) + { + this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); + this.clearFlag = false; + } + else + { + ++this.bitCount; + this.maxcode = this.bitCount == this.maxbits + ? this.maxmaxcode + : GetMaxcode(this.bitCount); + } + } + + if (code == this.eofCode) + { + // At EOF, write the rest of the buffer. + while (this.currentBits > 0) + { + this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); + this.currentAccumulator >>= 8; + this.currentBits -= 8; + } + + this.FlushPacket(outs); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Gif/PackedField.cs b/src/ImageSharp46/Formats/Gif/PackedField.cs new file mode 100644 index 000000000..cabf4f5ab --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/PackedField.cs @@ -0,0 +1,199 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + + /// + /// Represents a byte of data in a GIF data stream which contains a number + /// of data items. + /// + internal struct PackedField : IEquatable + { + /// + /// The individual bits representing the packed byte. + /// + private static readonly bool[] Bits = new bool[8]; + + /// + /// Gets the byte which represents the data items held in this instance. + /// + public byte Byte + { + get + { + int returnValue = 0; + int bitShift = 7; + foreach (bool bit in Bits) + { + int bitValue; + if (bit) + { + bitValue = 1 << bitShift; + } + else + { + bitValue = 0; + } + + returnValue |= bitValue; + bitShift--; + } + + return Convert.ToByte(returnValue & 0xFF); + } + } + + /// + /// Returns a new with the bits in the packed fields to + /// the corresponding bits from the supplied byte. + /// + /// The value to pack. + /// The + public static PackedField FromInt(byte value) + { + PackedField packed = default(PackedField); + packed.SetBits(0, 8, value); + return packed; + } + + /// + /// Sets the specified bit within the packed fields to the supplied + /// value. + /// + /// + /// The zero-based index within the packed fields of the bit to set. + /// + /// + /// The value to set the bit to. + /// + public void SetBit(int index, bool valueToSet) + { + if (index < 0 || index > 7) + { + string message + = "Index must be between 0 and 7. Supplied index: " + + index; + throw new ArgumentOutOfRangeException(nameof(index), message); + } + + Bits[index] = valueToSet; + } + + /// + /// Sets the specified bits within the packed fields to the supplied + /// value. + /// + /// The zero-based index within the packed fields of the first bit to set. + /// The number of bits to set. + /// The value to set the bits to. + public void SetBits(int startIndex, int length, int valueToSet) + { + if (startIndex < 0 || startIndex > 7) + { + string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(startIndex), message); + } + + if (length < 1 || startIndex + length > 8) + { + string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " + + $"Supplied length: {length}. Supplied start index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(length), message); + } + + int bitShift = length - 1; + for (int i = startIndex; i < startIndex + length; i++) + { + int bitValueIfSet = 1 << bitShift; + int bitValue = valueToSet & bitValueIfSet; + int bitIsSet = bitValue >> bitShift; + Bits[i] = bitIsSet == 1; + bitShift--; + } + } + + /// + /// Gets the value of the specified bit within the byte. + /// + /// The zero-based index of the bit to get. + /// + /// The value of the specified bit within the byte. + /// + public bool GetBit(int index) + { + if (index < 0 || index > 7) + { + string message = $"Index must be between 0 and 7. Supplied index: {index}"; + throw new ArgumentOutOfRangeException(nameof(index), message); + } + + return Bits[index]; + } + + /// + /// Gets the value of the specified bits within the byte. + /// + /// The zero-based index of the first bit to get. + /// The number of bits to get. + /// + /// The value of the specified bits within the byte. + /// + public int GetBits(int startIndex, int length) + { + if (startIndex < 0 || startIndex > 7) + { + string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(startIndex), message); + } + + if (length < 1 || startIndex + length > 8) + { + string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " + + $"Supplied length: {length}. Supplied start index: {startIndex}"; + + throw new ArgumentOutOfRangeException(nameof(length), message); + } + + int returnValue = 0; + int bitShift = length - 1; + for (int i = startIndex; i < startIndex + length; i++) + { + int bitValue = (Bits[i] ? 1 : 0) << bitShift; + returnValue += bitValue; + bitShift--; + } + + return returnValue; + } + + /// + public override bool Equals(object obj) + { + PackedField? field = obj as PackedField?; + + return this.Byte == field?.Byte; + } + + /// + public bool Equals(PackedField other) + { + return this.Byte.Equals(other.Byte); + } + + /// + public override string ToString() + { + return $"PackedField [ Byte={this.Byte} ]"; + } + + /// + public override int GetHashCode() + { + return this.Byte.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Gif/README.md b/src/ImageSharp46/Formats/Gif/README.md new file mode 100644 index 000000000..d47a4c683 --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/README.md @@ -0,0 +1,4 @@ +Encoder/Decoder adapted and extended from: + +https://github.com/yufeih/Nine.Imaging/ +https://imagetools.codeplex.com/ diff --git a/src/ImageSharp46/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageSharp46/Formats/Gif/Sections/GifGraphicsControlExtension.cs new file mode 100644 index 000000000..79d98f5fb --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/Sections/GifGraphicsControlExtension.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// The Graphic Control Extension contains parameters used when + /// processing a graphic rendering block. + /// + internal sealed class GifGraphicsControlExtension + { + /// + /// Gets or sets the disposal method which indicates the way in which the + /// graphic is to be treated after being displayed. + /// + public DisposalMethod DisposalMethod { get; set; } + + /// + /// Gets or sets a value indicating whether transparency flag is to be set. + /// This indicates whether a transparency index is given in the Transparent Index field. + /// (This field is the least significant bit of the byte.) + /// + public bool TransparencyFlag { get; set; } + + /// + /// Gets or sets the transparency index. + /// The Transparency Index is such that when encountered, the corresponding pixel + /// of the display device is not modified and processing goes on to the next pixel. + /// + public int TransparencyIndex { get; set; } + + /// + /// Gets or sets the delay time. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int DelayTime { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageSharp46/Formats/Gif/Sections/GifImageDescriptor.cs new file mode 100644 index 000000000..12c13beaf --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/Sections/GifImageDescriptor.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Each image in the Data Stream is composed of an Image Descriptor, + /// an optional Local Color Table, and the image data. + /// Each image must fit within the boundaries of the + /// Logical Screen, as defined in the Logical Screen Descriptor. + /// + internal sealed class GifImageDescriptor + { + /// + /// Gets or sets the column number, in pixels, of the left edge of the image, + /// with respect to the left edge of the Logical Screen. + /// Leftmost column of the Logical Screen is 0. + /// + public short Left { get; set; } + + /// + /// Gets or sets the row number, in pixels, of the top edge of the image with + /// respect to the top edge of the Logical Screen. + /// Top row of the Logical Screen is 0. + /// + public short Top { get; set; } + + /// + /// Gets or sets the width of the image in pixels. + /// + public short Width { get; set; } + + /// + /// Gets or sets the height of the image in pixels. + /// + public short Height { get; set; } + + /// + /// Gets or sets a value indicating whether the presence of a Local Color Table immediately + /// follows this Image Descriptor. + /// + public bool LocalColorTableFlag { get; set; } + + /// + /// Gets or sets the local color table size. + /// If the Local Color Table Flag is set to 1, the value in this field + /// is used to calculate the number of bytes contained in the Local Color Table. + /// + public int LocalColorTableSize { get; set; } + + /// + /// Gets or sets a value indicating whether the image is to be interlaced. + /// An image is interlaced in a four-pass interlace pattern. + /// + public bool InterlaceFlag { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageSharp46/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs new file mode 100644 index 000000000..fc2cc974a --- /dev/null +++ b/src/ImageSharp46/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// The Logical Screen Descriptor contains the parameters + /// necessary to define the area of the display device + /// within which the images will be rendered + /// + internal sealed class GifLogicalScreenDescriptor + { + /// + /// Gets or sets the width, in pixels, of the Logical Screen where the images will + /// be rendered in the displaying device. + /// + public short Width { get; set; } + + /// + /// Gets or sets the height, in pixels, of the Logical Screen where the images will be + /// rendered in the displaying device. + /// + public short Height { get; set; } + + /// + /// Gets or sets the index at the Global Color Table for the Background Color. + /// The Background Color is the color used for those + /// pixels on the screen that are not covered by an image. + /// + public byte BackgroundColorIndex { get; set; } + + /// + /// Gets or sets the pixel aspect ratio. Default to 0. + /// + public byte PixelAspectRatio { get; set; } + + /// + /// Gets or sets a value indicating whether a flag denoting the presence of a Global Color Table + /// should be set. + /// If the flag is set, the Global Color Table will immediately + /// follow the Logical Screen Descriptor. + /// + public bool GlobalColorTableFlag { get; set; } + + /// + /// Gets or sets the global color table size. + /// If the Global Color Table Flag is set to 1, + /// the value in this field is used to calculate the number of + /// bytes contained in the Global Color Table. + /// + public int GlobalColorTableSize { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/IImageDecoder.cs b/src/ImageSharp46/Formats/IImageDecoder.cs new file mode 100644 index 000000000..36ef23398 --- /dev/null +++ b/src/ImageSharp46/Formats/IImageDecoder.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.IO; + + /// + /// Encapsulates properties and methods required for decoding an image from a stream. + /// + public interface IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + int HeaderSize { get; } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + bool IsSupportedFileExtension(string extension); + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + bool IsSupportedFileFormat(byte[] header); + + /// + /// Decodes the image from the specified stream to the . + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to decode to. + /// The containing image data. + void Decode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct; + } +} diff --git a/src/ImageSharp46/Formats/IImageEncoder.cs b/src/ImageSharp46/Formats/IImageEncoder.cs new file mode 100644 index 000000000..6cb97420c --- /dev/null +++ b/src/ImageSharp46/Formats/IImageEncoder.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.IO; + + /// + /// Encapsulates properties and methods required for encoding an image to a stream. + /// + public interface IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + int Quality { get; set; } + + /// + /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. + /// + string MimeType { get; } + + /// + /// Gets the default file extension for this encoder. + /// + string Extension { get; } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + bool IsSupportedFileExtension(string extension); + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to encode from. + /// The to encode the image data to. + void Encode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct; + } +} diff --git a/src/ImageSharp46/Formats/IImageFormat.cs b/src/ImageSharp46/Formats/IImageFormat.cs new file mode 100644 index 000000000..cffc9861a --- /dev/null +++ b/src/ImageSharp46/Formats/IImageFormat.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates a supported image format, providing means to encode and decode an image. + /// + public interface IImageFormat + { + /// + /// Gets the image encoder for encoding an image from a stream. + /// + IImageEncoder Encoder { get; } + + /// + /// Gets the image decoder for decoding an image from a stream. + /// + IImageDecoder Decoder { get; } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/Components/Bits.cs b/src/ImageSharp46/Formats/Jpg/Components/Bits.cs new file mode 100644 index 000000000..68278e69b --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/Bits.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Holds the unprocessed bits that have been taken from the byte-stream. + /// The n least significant bits of a form the unread bits, to be read in MSB to + /// LSB order. + /// + internal class Bits + { + /// + /// Gets or sets the accumulator. + /// + public uint Accumulator { get; set; } + + /// + /// Gets or sets the mask. + /// 0, with mask==0 when unreadbits==0.]]> + /// + public uint Mask { get; set; } + + /// + /// Gets or sets the number of unread bits in the accumulator. + /// + public int UnreadBits { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Jpg/Components/Block.cs b/src/ImageSharp46/Formats/Jpg/Components/Block.cs new file mode 100644 index 000000000..8bc5a861c --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/Block.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Represents an 8x8 block of coefficients to transform and encode. + /// + internal class Block + { + /// + /// Gets the size of the block. + /// + public const int BlockSize = 64; + + /// + /// The array of block data. + /// + private readonly int[] data; + + /// + /// Initializes a new instance of the class. + /// + public Block() + { + this.data = new int[BlockSize]; + } + + /// + /// Gets the pixel data at the given block index. + /// + /// The index of the data to return. + /// + /// The . + /// + public int this[int index] + { + get { return this.data[index]; } + set { this.data[index] = value; } + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/Components/Bytes.cs b/src/ImageSharp46/Formats/Jpg/Components/Bytes.cs new file mode 100644 index 000000000..d4159e996 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/Bytes.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Bytes is a byte buffer, similar to a stream, except that it + /// has to be able to unread more than 1 byte, due to byte stuffing. + /// Byte stuffing is specified in section F.1.2.3. + /// + internal class Bytes + { + /// + /// Initializes a new instance of the class. + /// + public Bytes() + { + this.Buffer = new byte[4096]; + this.I = 0; + this.J = 0; + this.UnreadableBytes = 0; + } + + /// + /// Gets or sets the buffer. + /// buffer[i:j] are the buffered bytes read from the underlying + /// stream that haven't yet been passed further on. + /// + public byte[] Buffer { get; set; } + + public int I { get; set; } + + public int J { get; set; } + + /// + /// Gets or sets the unreadable bytes. The number of bytes to back up i after + /// overshooting. It can be 0, 1 or 2. + /// + public int UnreadableBytes { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/Components/Component.cs b/src/ImageSharp46/Formats/Jpg/Components/Component.cs new file mode 100644 index 000000000..f56b6d513 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/Component.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Represents a single color component + /// + internal class Component + { + /// + /// Gets or sets the horizontal sampling factor. + /// + public int HorizontalFactor { get; set; } + + /// + /// Gets or sets the vertical sampling factor. + /// + public int VerticalFactor { get; set; } + + /// + /// Gets or sets the identifier + /// + public byte Identifier { get; set; } + + /// + /// Gets or sets the quantization table destination selector. + /// + public byte Selector { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/Components/FDCT.cs b/src/ImageSharp46/Formats/Jpg/Components/FDCT.cs new file mode 100644 index 000000000..cd27b9e73 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/FDCT.cs @@ -0,0 +1,160 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Performs a fast, forward discrete cosine transform against the given block + /// decomposing it into 64 orthogonal basis signals. + /// + internal class FDCT + { + // Trigonometric constants in 13-bit fixed point format. + // TODO: Rename and describe these. + private const int fix_0_298631336 = 2446; + private const int fix_0_390180644 = 3196; + private const int fix_0_541196100 = 4433; + private const int fix_0_765366865 = 6270; + private const int fix_0_899976223 = 7373; + private const int fix_1_175875602 = 9633; + private const int fix_1_501321110 = 12299; + private const int fix_1_847759065 = 15137; + private const int fix_1_961570560 = 16069; + private const int fix_2_053119869 = 16819; + private const int fix_2_562915447 = 20995; + private const int fix_3_072711026 = 25172; + + /// + /// The number of bits + /// + private const int Bits = 13; + + /// + /// The number of bits to shift by on the first pass. + /// + private const int Pass1Bits = 2; + + /// + /// The value to shift by + /// + private const int CenterJSample = 128; + + /// + /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. + /// + /// The block of coefficients. + public static void Transform(Block block) + { + // Pass 1: process rows. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + int x0 = block[y8]; + int x1 = block[y8 + 1]; + int x2 = block[y8 + 2]; + int x3 = block[y8 + 3]; + int x4 = block[y8 + 4]; + int x5 = block[y8 + 5]; + int x6 = block[y8 + 6]; + int x7 = block[y8 + 7]; + + int tmp0 = x0 + x7; + int tmp1 = x1 + x6; + int tmp2 = x2 + x5; + int tmp3 = x3 + x4; + + int tmp10 = tmp0 + tmp3; + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = x0 - x7; + tmp1 = x1 - x6; + tmp2 = x2 - x5; + tmp3 = x3 - x4; + + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits - Pass1Bits - 1); + block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits - Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); + block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); + } + + // Pass 2: process columns. + // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. + for (int x = 0; x < 8; x++) + { + int tmp0 = block[x] + block[56 + x]; + int tmp1 = block[8 + x] + block[48 + x]; + int tmp2 = block[16 + x] + block[40 + x]; + int tmp3 = block[24 + x] + block[32 + x]; + + int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = block[x] - block[56 + x]; + tmp1 = block[8 + x] - block[48 + x]; + tmp2 = block[16 + x] - block[40 + x]; + tmp3 = block[24 + x] - block[32 + x]; + + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; + + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits + Pass1Bits - 1); + block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits + Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); + block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); + block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); + block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); + } + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/Components/GrayImage.cs b/src/ImageSharp46/Formats/Jpg/Components/GrayImage.cs new file mode 100644 index 000000000..5f5f015ad --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/GrayImage.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Represents a grayscale image + /// + internal class GrayImage + { + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The height. + public GrayImage(int width, int height) + { + this.Width = width; + this.Height = height; + this.Pixels = new byte[width * height]; + this.Stride = width; + this.Offset = 0; + } + + /// + /// Prevents a default instance of the class from being created. + /// + private GrayImage() + { + } + + /// + /// Gets or sets the pixels. + /// + public byte[] Pixels { get; set; } + + /// + /// Gets or sets the stride. + /// + public int Stride { get; set; } + + /// + /// Gets or sets the horizontal position. + /// + public int X { get; set; } + + /// + /// Gets or sets the vertical position. + /// + public int Y { get; set; } + + /// + /// Gets or sets the width. + /// + public int Width { get; set; } + + /// + /// Gets or sets the height. + /// + public int Height { get; set; } + + /// + /// Gets or sets the offset + /// + public int Offset { get; set; } + + /// + /// Gets an image made up of a subset of the originals pixels. + /// + /// The x-coordinate of the image. + /// The y-coordinate of the image. + /// The width. + /// The height. + /// + /// The . + /// + public GrayImage Subimage(int x, int y, int width, int height) + { + return new GrayImage + { + Width = width, + Height = height, + Pixels = this.Pixels, + Stride = this.Stride, + Offset = (y * this.Stride) + x + }; + } + + /// + /// Gets the row offset at the given position + /// + /// The y-coordinate of the image. + /// The + public int GetRowOffset(int y) + { + return this.Offset + (y * this.Stride); + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/Components/Huffman.cs b/src/ImageSharp46/Formats/Jpg/Components/Huffman.cs new file mode 100644 index 000000000..2c38cfd38 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/Huffman.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Represents a Huffman tree + /// + internal class Huffman + { + /// + /// Initializes a new instance of the class. + /// + /// The log-2 size of the Huffman decoder's look-up table. + /// The maximum (inclusive) number of codes in a Huffman tree. + /// The maximum (inclusive) number of bits in a Huffman code. + public Huffman(int lutSize, int maxNCodes, int maxCodeLength) + { + this.Lut = new ushort[1 << lutSize]; + this.Values = new byte[maxNCodes]; + this.MinCodes = new int[maxCodeLength]; + this.MaxCodes = new int[maxCodeLength]; + this.Indices = new int[maxCodeLength]; + this.Length = 0; + } + + /// + /// Gets or sets the number of codes in the tree. + /// + public int Length { get; set; } + + /// + /// Gets the look-up table for the next LutSize bits in the bit-stream. + /// The high 8 bits of the uint16 are the encoded value. The low 8 bits + /// are 1 plus the code length, or 0 if the value is too large to fit in + /// lutSize bits. + /// + public ushort[] Lut { get; } + + /// + /// Gets the the decoded values, sorted by their encoding. + /// + public byte[] Values { get; } + + /// + /// Gets the array of minimum codes. + /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. + /// + public int[] MinCodes { get; } + + /// + /// Gets the array of maximum codes. + /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. + /// + public int[] MaxCodes { get; } + + /// + /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. + /// + public int[] Indices { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Jpg/Components/IDCT.cs b/src/ImageSharp46/Formats/Jpg/Components/IDCT.cs new file mode 100644 index 000000000..bc145779a --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/IDCT.cs @@ -0,0 +1,169 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Performs a 2-D Inverse Discrete Cosine Transformation. + /// + internal class IDCT + { + private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) + private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) + private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) + private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) + private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) + private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) + + private const int w1pw7 = w1 + w7; + private const int w1mw7 = w1 - w7; + private const int w2pw6 = w2 + w6; + private const int w2mw6 = w2 - w6; + private const int w3pw5 = w3 + w5; + private const int w3mw5 = w3 - w5; + + private const int r2 = 181; // 256/sqrt(2) + + /// + /// Performs a 2-D Inverse Discrete Cosine Transformation. + /// + /// The input coefficients should already have been multiplied by the + /// appropriate quantization table. We use fixed-point computation, with the + /// number of bits for the fractional component varying over the intermediate + /// stages. + /// + /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the + /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on + /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. + /// + /// The source block of coefficients + public static void Transform(Block src) + { + // Horizontal 1-D IDCT. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + // If all the AC components are zero, then the IDCT is trivial. + if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && + src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) + { + int dc = src[y8 + 0] << 3; + src[y8 + 0] = dc; + src[y8 + 1] = dc; + src[y8 + 2] = dc; + src[y8 + 3] = dc; + src[y8 + 4] = dc; + src[y8 + 5] = dc; + src[y8 + 6] = dc; + src[y8 + 7] = dc; + continue; + } + + // Prescale. + int x0 = (src[y8 + 0] << 11) + 128; + int x1 = src[y8 + 4] << 11; + int x2 = src[y8 + 6]; + int x3 = src[y8 + 2]; + int x4 = src[y8 + 1]; + int x5 = src[y8 + 7]; + int x6 = src[y8 + 5]; + int x7 = src[y8 + 3]; + + // Stage 1. + int x8 = w7 * (x4 + x5); + x4 = x8 + (w1mw7 * x4); + x5 = x8 - (w1pw7 * x5); + x8 = w3 * (x6 + x7); + x6 = x8 - (w3mw5 * x6); + x7 = x8 - (w3pw5 * x7); + + // Stage 2. + x8 = x0 + x1; + x0 -= x1; + x1 = w6 * (x3 + x2); + x2 = x1 - (w2pw6 * x2); + x3 = x1 + (w2mw6 * x3); + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + // Stage 3. + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = ((r2 * (x4 + x5)) + 128) >> 8; + x4 = ((r2 * (x4 - x5)) + 128) >> 8; + + // Stage 4. + src[y8 + 0] = (x7 + x1) >> 8; + src[y8 + 1] = (x3 + x2) >> 8; + src[y8 + 2] = (x0 + x4) >> 8; + src[y8 + 3] = (x8 + x6) >> 8; + src[y8 + 4] = (x8 - x6) >> 8; + src[y8 + 5] = (x0 - x4) >> 8; + src[y8 + 6] = (x3 - x2) >> 8; + src[y8 + 7] = (x7 - x1) >> 8; + } + + // Vertical 1-D IDCT. + for (int x = 0; x < 8; x++) + { + // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. + // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so + // we do not bother to check for the all-zero case. + + // Prescale. + int y0 = (src[x] << 8) + 8192; + int y1 = src[32 + x] << 8; + int y2 = src[48 + x]; + int y3 = src[16 + x]; + int y4 = src[8 + x]; + int y5 = src[56 + x]; + int y6 = src[40 + x]; + int y7 = src[24 + x]; + + // Stage 1. + int y8 = (w7 * (y4 + y5)) + 4; + y4 = (y8 + (w1mw7 * y4)) >> 3; + y5 = (y8 - (w1pw7 * y5)) >> 3; + y8 = (w3 * (y6 + y7)) + 4; + y6 = (y8 - (w3mw5 * y6)) >> 3; + y7 = (y8 - (w3pw5 * y7)) >> 3; + + // Stage 2. + y8 = y0 + y1; + y0 -= y1; + y1 = (w6 * (y3 + y2)) + 4; + y2 = (y1 - (w2pw6 * y2)) >> 3; + y3 = (y1 + (w2mw6 * y3)) >> 3; + y1 = y4 + y6; + y4 -= y6; + y6 = y5 + y7; + y5 -= y7; + + // Stage 3. + y7 = y8 + y3; + y8 -= y3; + y3 = y0 + y2; + y0 -= y2; + y2 = ((r2 * (y4 + y5)) + 128) >> 8; + y4 = ((r2 * (y4 - y5)) + 128) >> 8; + + // Stage 4. + src[x] = (y7 + y1) >> 14; + src[8 + x] = (y3 + y2) >> 14; + src[16 + x] = (y0 + y4) >> 14; + src[24 + x] = (y8 + y6) >> 14; + src[32 + x] = (y8 - y6) >> 14; + src[40 + x] = (y0 - y4) >> 14; + src[48 + x] = (y3 - y2) >> 14; + src[56 + x] = (y7 - y1) >> 14; + } + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/Components/YCbCrImage.cs b/src/ImageSharp46/Formats/Jpg/Components/YCbCrImage.cs new file mode 100644 index 000000000..564da379b --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/Components/YCbCrImage.cs @@ -0,0 +1,228 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Represents an image made up of three color components (luminance, blue chroma, red chroma) + /// + internal class YCbCrImage + { + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The height. + /// The ratio. + public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio) + { + int cw, ch; + YCbCrSize(width, height, ratio, out cw, out ch); + this.YChannel = new byte[width * height]; + this.CbChannel = new byte[cw * ch]; + this.CrChannel = new byte[cw * ch]; + this.Ratio = ratio; + this.YStride = width; + this.CStride = cw; + this.X = 0; + this.Y = 0; + this.Width = width; + this.Height = height; + } + + /// + /// Prevents a default instance of the class from being created. + /// + private YCbCrImage() + { + } + + /// + /// Provides enumeration of the various available subsample ratios. + /// + public enum YCbCrSubsampleRatio + { + YCbCrSubsampleRatio444, + + YCbCrSubsampleRatio422, + + YCbCrSubsampleRatio420, + + YCbCrSubsampleRatio440, + + YCbCrSubsampleRatio411, + + YCbCrSubsampleRatio410, + } + + /// + /// Gets or sets the luminance components channel. + /// + public byte[] YChannel { get; set; } + + /// + /// Gets or sets the blue chroma components channel. + /// + public byte[] CbChannel { get; set; } + + /// + /// Gets or sets the red chroma components channel. + /// + public byte[] CrChannel { get; set; } + + /// + /// Gets or sets the Y slice index delta between vertically adjacent pixels. + /// + public int YStride { get; set; } + + /// + /// Gets or sets the red and blue chroma slice index delta between vertically adjacent pixels + /// that map to separate chroma samples. + /// + public int CStride { get; set; } + + /// + /// Gets or sets the index of the first luminance element. + /// + public int YOffset { get; set; } + + /// + /// Gets or sets the index of the first element of red or blue chroma. + /// + public int COffset { get; set; } + + /// + /// Gets or sets the horizontal position. + /// + public int X { get; set; } + + /// + /// Gets or sets the vertical position. + /// + public int Y { get; set; } + + /// + /// Gets or sets the width. + /// + public int Width { get; set; } + + /// + /// Gets or sets the height. + /// + public int Height { get; set; } + + /// + /// Gets or sets the subsampling ratio. + /// + public YCbCrSubsampleRatio Ratio { get; set; } + + /// + /// Gets an image made up of a subset of the originals pixels. + /// + /// The x-coordinate of the image. + /// The y-coordinate of the image. + /// The width. + /// The height. + /// + /// The . + /// + public YCbCrImage Subimage(int x, int y, int width, int height) + { + YCbCrImage ret = new YCbCrImage + { + Width = width, + Height = height, + YChannel = this.YChannel, + CbChannel = this.CbChannel, + CrChannel = this.CrChannel, + Ratio = this.Ratio, + YStride = this.YStride, + CStride = this.CStride, + YOffset = (y * this.YStride) + x, + COffset = (y * this.CStride) + x + }; + return ret; + } + + /// + /// Returns the offset of the first luminance component at the given row + /// + /// The row number. + /// + /// The . + /// + public int GetRowYOffset(int y) + { + return y * this.YStride; + } + + /// + /// Returns the offset of the first chroma component at the given row + /// + /// The row number. + /// + /// The . + /// + public int GetRowCOffset(int y) + { + switch (this.Ratio) + { + case YCbCrSubsampleRatio.YCbCrSubsampleRatio422: + return y * this.CStride; + case YCbCrSubsampleRatio.YCbCrSubsampleRatio420: + return (y / 2) * this.CStride; + case YCbCrSubsampleRatio.YCbCrSubsampleRatio440: + return (y / 2) * this.CStride; + case YCbCrSubsampleRatio.YCbCrSubsampleRatio411: + return y * this.CStride; + case YCbCrSubsampleRatio.YCbCrSubsampleRatio410: + return (y / 2) * this.CStride; + default: + return y * this.CStride; + } + } + + /// + /// Returns the height and width of the chroma components + /// + /// The width. + /// The height. + /// The subsampling ratio. + /// The chroma width. + /// The chroma height. + private static void YCbCrSize(int width, int height, YCbCrSubsampleRatio ratio, out int chromaWidth, out int chromaHeight) + { + switch (ratio) + { + case YCbCrSubsampleRatio.YCbCrSubsampleRatio422: + chromaWidth = (width + 1) / 2; + chromaHeight = height; + break; + case YCbCrSubsampleRatio.YCbCrSubsampleRatio420: + chromaWidth = (width + 1) / 2; + chromaHeight = (height + 1) / 2; + break; + case YCbCrSubsampleRatio.YCbCrSubsampleRatio440: + chromaWidth = width; + chromaHeight = (height + 1) / 2; + break; + case YCbCrSubsampleRatio.YCbCrSubsampleRatio411: + chromaWidth = (width + 3) / 4; + chromaHeight = height; + break; + case YCbCrSubsampleRatio.YCbCrSubsampleRatio410: + chromaWidth = (width + 3) / 4; + chromaHeight = (height + 1) / 2; + break; + default: + + // Default to 4:4:4 subsampling. + chromaWidth = width; + chromaHeight = height; + break; + } + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/JpegConstants.cs b/src/ImageSharp46/Formats/Jpg/JpegConstants.cs new file mode 100644 index 000000000..19d726e70 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/JpegConstants.cs @@ -0,0 +1,230 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Defines jpeg constants defined in the specification. + /// + internal static class JpegConstants + { + /// + /// The maximum allowable length in each dimension of a jpeg image. + /// + public const ushort MaxLength = 65535; + + /// + /// Represents high detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourFourFourHorizontal = { 0x11, 0x11, 0x11 }; + + /// + /// Represents high detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourFourFourVertical = { 0x11, 0x11, 0x11 }; + + /// + /// Represents medium detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourTwoTwoVertical = { 0x11, 0x11, 0x11 }; + + /// + /// Represents low detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourTwoZeroVertical = { 0x22, 0x11, 0x11 }; + + /// + /// Represents medium detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x22, 0x11, 0x11 }; + + /// + /// Represents low detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x22, 0x11, 0x11 }; + + /// + /// Describes component ids for start of frame components. + /// + internal static class Components + { + /// + /// The YCbCr luminance component id. + /// + public const byte Y = 1; + + /// + /// The YCbCr chroma component id. + /// + public const byte Cb = 2; + + /// + /// The YCbCr chroma component id. + /// + public const byte Cr = 3; + + /// + /// The YIQ x coordinate component id. + /// + public const byte I = 4; + + /// + /// The YIQ y coordinate component id. + /// + public const byte Q = 5; + } + + /// + /// Describes common Jpeg markers + /// + internal static class Markers + { + /// + /// Marker prefix. Next byte is a marker. + /// + public const byte XFF = 0xff; + + /// + /// Start of Image + /// + public const byte SOI = 0xd8; + + /// + /// Start of Frame (baseline DCT) + /// + /// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF0 = 0xc0; + + /// + /// Start Of Frame (Extended Sequential DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF1 = 0xc1; + + /// + /// Start Of Frame (progressive DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF2 = 0xc2; + + /// + /// Define Huffman Table(s) + /// + /// Specifies one or more Huffman tables. + /// + /// + public const byte DHT = 0xc4; + + /// + /// Define Quantization Table(s) + /// + /// Specifies one or more quantization tables. + /// + /// + public const byte DQT = 0xdb; + + /// + /// Define Restart Interval + /// + /// Specifies the interval between RSTn markers, in macroblocks. This marker is followed by two bytes + /// indicating the fixed size so it can be treated like any other variable size segment. + /// + /// + public const byte DRI = 0xdd; + + /// + /// Define First Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST0 = 0xd0; + + /// + /// Define Eigth Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST7 = 0xd7; + + /// + /// Start of Scan + /// + /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. + /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it + /// will contain, and is immediately followed by entropy-coded data. + /// + /// + public const byte SOS = 0xda; + + /// + /// Comment + /// + /// Contains a text comment. + /// + /// + public const byte COM = 0xfe; + + /// + /// End of Image + /// + public const byte EOI = 0xd9; + + /// + /// Application specific marker for marking the jpeg format. + /// + /// + public const byte APP0 = 0xe0; + + /// + /// Application specific marker for marking where to store metadata. + /// + public const byte APP1 = 0xe1; + + /// + /// Application specific marker used by Adobe for storing encoding information for DCT filters. + /// + public const byte APP14 = 0xee; + + /// + /// Application specific marker used by GraphicConverter to store JPEG quality. + /// + public const byte APP15 = 0xef; + } + + /// + /// Describes Adobe specific markers + /// + internal static class Adobe + { + /// + /// The color transform is unknown.(RGB or CMYK) + /// + public const int ColorTransformUnknown = 0; + + /// + /// The color transform is YCbCr (luminance, red chroma, blue chroma) + /// + public const int ColorTransformYCbCr = 1; + + /// + /// The color transform is YCCK (luminance, red chroma, blue chroma, keyline) + /// + public const int ColorTransformYcck = 2; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Jpg/JpegDecoder.cs b/src/ImageSharp46/Formats/Jpg/JpegDecoder.cs new file mode 100644 index 000000000..666f1b35a --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/JpegDecoder.cs @@ -0,0 +1,139 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Image decoder for generating an image out of a jpg stream. + /// + public class JpegDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 11; + + /// + /// Indicates if the image decoder supports the specified + /// file extension. + /// + /// The file extension. + /// + /// true, if the decoder supports the specified + /// extensions; otherwise false. + /// + /// + /// is null (Nothing in Visual Basic). + /// is a string + /// of length zero or contains only blanks. + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + if (extension.StartsWith(".")) + { + extension = extension.Substring(1); + } + + return extension.Equals("JPG", StringComparison.OrdinalIgnoreCase) || + extension.Equals("JPEG", StringComparison.OrdinalIgnoreCase) || + extension.Equals("JFIF", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Indicates if the image decoder supports the specified + /// file header. + /// + /// The file header. + /// + /// true, if the decoder supports the specified + /// file header; otherwise false. + /// + /// + /// is null (Nothing in Visual Basic). + public bool IsSupportedFileFormat(byte[] header) + { + Guard.NotNull(header, "header"); + + bool isSupported = false; + + if (header.Length >= 11) + { + bool isJfif = IsJfif(header); + bool isExif = IsExif(header); + bool isJpeg = IsJpeg(header); + + isSupported = isJfif || isExif || isJpeg; + } + + return isSupported; + } + + /// + public void Decode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Guard.NotNull(image, "image"); + Guard.NotNull(stream, "stream"); + + JpegDecoderCore decoder = new JpegDecoderCore(); + decoder.Decode(image, stream, false); + } + + /// + /// Returns a value indicating whether the given bytes identify Jfif data. + /// + /// The bytes representing the file header. + /// The + private static bool IsJfif(byte[] header) + { + bool isJfif = + header[6] == 0x4A && // J + header[7] == 0x46 && // F + header[8] == 0x49 && // I + header[9] == 0x46 && // F + header[10] == 0x00; + + return isJfif; + } + + /// + /// Returns a value indicating whether the given bytes identify EXIF data. + /// + /// The bytes representing the file header. + /// The + private static bool IsExif(byte[] header) + { + bool isExif = + header[6] == 0x45 && // E + header[7] == 0x78 && // X + header[8] == 0x69 && // I + header[9] == 0x66 && // F + header[10] == 0x00; + + return isExif; + } + + /// + /// Returns a value indicating whether the given bytes identify Jpeg data. + /// This is a last chance resort for jpegs that contain ICC information. + /// + /// The bytes representing the file header. + /// The + private static bool IsJpeg(byte[] header) + { + bool isJpg = + header[0] == 0xFF && // 255 + header[1] == 0xD8; // 216 + + return isJpg; + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs new file mode 100644 index 000000000..332dcec48 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs @@ -0,0 +1,2203 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + using System.Threading.Tasks; + + /// + /// Performs the jpeg decoding operation. + /// + internal class JpegDecoderCore + { + /// + /// The maximum (inclusive) number of bits in a Huffman code. + /// + private const int MaxCodeLength = 16; + + /// + /// The maximum (inclusive) number of codes in a Huffman tree. + /// + private const int MaxNCodes = 256; + + /// + /// The log-2 size of the Huffman decoder's look-up table. + /// + private const int LutSize = 8; + + /// + /// The maximum number of color components + /// + private const int MaxComponents = 4; + + /// + /// The maximum number of Huffman table classes + /// + private const int MaxTc = 1; + + /// + /// The maximum number of Huffman table identifiers + /// + private const int MaxTh = 3; + + /// + /// The maximum number of quantization tables + /// + private const int MaxTq = 3; + + /// + /// The DC table index + /// + private const int DcTable = 0; + + /// + /// The AC table index + /// + private const int AcTable = 1; + + /// + /// Unzig maps from the zigzag ordering to the natural ordering. For example, + /// unzig[3] is the column and row of the fourth element in zigzag order. The + /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// + private static readonly int[] Unzig = + { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, + 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, + 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, + 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, + }; + + /// + /// The component array + /// + private readonly Component[] componentArray; + + /// + /// Saved state between progressive-mode scans. + /// + private readonly Block[][] progCoeffs; + + /// + /// The huffman trees + /// + private readonly Huffman[,] huffmanTrees; + + /// + /// Quantization tables, in zigzag order. + /// + private readonly Block[] quantizationTables; + + /// + /// A temporary buffer for holding pixels + /// + private readonly byte[] temp; + + /// + /// The byte buffer. + /// + private readonly Bytes bytes; + + /// + /// The image width + /// + private int imageWidth; + + /// + /// The image height + /// + private int imageHeight; + + /// + /// The number of color components within the image. + /// + private int componentCount; + + /// + /// A grayscale image to decode to. + /// + private GrayImage grayImage; + + /// + /// The full color image to decode to. + /// + private YCbCrImage ycbcrImage; + + /// + /// The input stream. + /// + private Stream inputStream; + + /// + /// Holds the unprocessed bits that have been taken from the byte-stream. + /// + private Bits bits; + + /// + /// The array of keyline pixels in a CMYK image + /// + private byte[] blackPixels; + + /// + /// The width in bytes or a single row of keyline pixels in a CMYK image + /// + private int blackStride; + + /// + /// The restart interval + /// + private int restartInterval; + + /// + /// Whether the image is interlaced (progressive) + /// + private bool isProgressive; + + /// + /// Whether the image has a JFIF header + /// + private bool isJfif; + + /// + /// Whether the image is in CMYK format with an App14 marker + /// + private bool adobeTransformValid; + + /// + /// The App14 marker color-space + /// + private byte adobeTransform; + + /// + /// End-of-Band run, specified in section G.1.2.2. + /// + private ushort eobRun; + + /// + /// The horizontal resolution. Calculated if the image has a JFIF header. + /// + private short horizontalResolution; + + /// + /// The vertical resolution. Calculated if the image has a JFIF header. + /// + private short verticalResolution; + + /// + /// Initializes a new instance of the class. + /// + public JpegDecoderCore() + { + this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1]; + this.quantizationTables = new Block[MaxTq + 1]; + this.temp = new byte[2 * Block.BlockSize]; + this.componentArray = new Component[MaxComponents]; + this.progCoeffs = new Block[MaxComponents][]; + this.bits = new Bits(); + this.bytes = new Bytes(); + + // TODO: This looks like it could be static. + for (int i = 0; i < MaxTc + 1; i++) + { + for (int j = 0; j < MaxTh + 1; j++) + { + this.huffmanTrees[i, j] = new Huffman(LutSize, MaxNCodes, MaxCodeLength); + } + } + + for (int i = 0; i < this.quantizationTables.Length; i++) + { + this.quantizationTables[i] = new Block(); + } + + for (int i = 0; i < this.componentArray.Length; i++) + { + this.componentArray[i] = new Component(); + } + } + + /// + /// Decodes the image from the specified this._stream and sets + /// the data to image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image, where the data should be set to. + /// The stream, where the image should be. + /// Whether to decode metadata only. + public void Decode(Image image, Stream stream, bool configOnly) + where TColor : struct, IPackedPixel + where TPacked : struct + { + this.inputStream = stream; + + // Check for the Start Of Image marker. + this.ReadFull(this.temp, 0, 2); + if (this.temp[0] != JpegConstants.Markers.XFF || this.temp[1] != JpegConstants.Markers.SOI) + { + throw new ImageFormatException("Missing SOI marker."); + } + + // Process the remaining segments until the End Of Image marker. + while (true) + { + this.ReadFull(this.temp, 0, 2); + while (this.temp[0] != 0xff) + { + // Strictly speaking, this is a format error. However, libjpeg is + // liberal in what it accepts. As of version 9, next_marker in + // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and + // continues to decode the stream. Even before next_marker sees + // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many + // bytes as it can, possibly past the end of a scan's data. It + // effectively puts back any markers that it overscanned (e.g. an + // "\xff\xd9" EOI marker), but it does not put back non-marker data, + // and thus it can silently ignore a small number of extraneous + // non-marker bytes before next_marker has a chance to see them (and + // print a warning). + // We are therefore also liberal in what we accept. Extraneous data + // is silently ignore + // This is similar to, but not exactly the same as, the restart + // mechanism within a scan (the RST[0-7] markers). + // Note that extraneous 0xff bytes in e.g. SOS data are escaped as + // "\xff\x00", and so are detected a little further down below. + this.temp[0] = this.temp[1]; + this.temp[1] = this.ReadByte(); + } + + byte marker = this.temp[1]; + if (marker == 0) + { + // Treat "\xff\x00" as extraneous data. + continue; + } + + while (marker == 0xff) + { + // Section B.1.1.2 says, "Any marker may optionally be preceded by any + // number of fill bytes, which are bytes assigned code X'FF'". + marker = this.ReadByte(); + } + + // End Of Image. + if (marker == JpegConstants.Markers.EOI) + { + break; + } + + if (JpegConstants.Markers.RST0 <= marker && marker <= JpegConstants.Markers.RST7) + { + // Figures B.2 and B.16 of the specification suggest that restart markers should + // only occur between Entropy Coded Segments and not after the final ECS. + // However, some encoders may generate incorrect JPEGs with a final restart + // marker. That restart marker will be seen here instead of inside the ProcessSOS + // method, and is ignored as a harmless error. Restart markers have no extra data, + // so we check for this before we read the 16-bit length of the segment. + continue; + } + + // Read the 16-bit length of the segment. The value includes the 2 bytes for the + // length itself, so we subtract 2 to get the number of remaining bytes. + this.ReadFull(this.temp, 0, 2); + int remaining = (this.temp[0] << 8) + this.temp[1] - 2; + if (remaining < 0) + { + throw new ImageFormatException("Short segment length."); + } + + switch (marker) + { + case JpegConstants.Markers.SOF0: + case JpegConstants.Markers.SOF1: + case JpegConstants.Markers.SOF2: + this.isProgressive = marker == JpegConstants.Markers.SOF2; + this.ProcessStartOfFrameMarker(remaining); + if (configOnly && this.isJfif) + { + return; + } + + break; + case JpegConstants.Markers.DHT: + if (configOnly) + { + this.Skip(remaining); + } + else + { + this.ProcessDefineHuffmanTablesMarker(remaining); + } + + break; + case JpegConstants.Markers.DQT: + if (configOnly) + { + this.Skip(remaining); + } + else this.ProcessDqt(remaining); + break; + case JpegConstants.Markers.SOS: + if (configOnly) + { + return; + } + + this.ProcessStartOfScan(remaining); + break; + case JpegConstants.Markers.DRI: + if (configOnly) + { + this.Skip(remaining); + } + else + { + this.ProcessDefineRestartIntervalMarker(remaining); + } + + break; + case JpegConstants.Markers.APP0: + this.ProcessApplicationHeader(remaining); + break; + case JpegConstants.Markers.APP1: + this.ProcessApp1Marker(remaining, image); + break; + case JpegConstants.Markers.APP14: + this.ProcessApp14Marker(remaining); + break; + default: + if ((JpegConstants.Markers.APP0 <= marker && marker <= JpegConstants.Markers.APP15) || marker == JpegConstants.Markers.COM) + { + this.Skip(remaining); + } + else if (marker < JpegConstants.Markers.SOF0) + { + // See Table B.1 "Marker code assignments". + throw new ImageFormatException("Unknown marker"); + } + else + { + throw new ImageFormatException("Unknown marker"); + } + + break; + } + } + + if (this.grayImage != null) + { + this.ConvertFromGrayScale(this.imageWidth, this.imageHeight, image); + } + else if (this.ycbcrImage != null) + { + if (this.componentCount == 4) + { + this.ConvertFromCmyk(this.imageWidth, this.imageHeight, image); + return; + } + + if (this.componentCount == 3) + { + if (this.IsRGB()) + { + this.ConvertFromRGB(this.imageWidth, this.imageHeight, image); + return; + } + + this.ConvertFromYCbCr(this.imageWidth, this.imageHeight, image); + return; + } + + throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); + } + else + { + throw new ImageFormatException("Missing SOS marker."); + } + } + + /// + /// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at + /// least n. For best performance (avoiding function calls inside hot loops), + /// the caller is the one responsible for first checking that bits.UnreadBits < n. + /// + /// The number of bits to ensure. + private void EnsureNBits(int n) + { + while (true) + { + byte c = this.ReadByteStuffedByte(); + this.bits.Accumulator = (this.bits.Accumulator << 8) | c; + this.bits.UnreadBits += 8; + if (this.bits.Mask == 0) + { + this.bits.Mask = 1 << 7; + } + else + { + this.bits.Mask <<= 8; + } + + if (this.bits.UnreadBits >= n) + { + break; + } + } + } + + /// + /// The composition of RECEIVE and EXTEND, specified in section F.2.2.1. + /// + /// The byte + /// The + private int ReceiveExtend(byte t) + { + if (this.bits.UnreadBits < t) + { + this.EnsureNBits(t); + } + + this.bits.UnreadBits -= t; + this.bits.Mask >>= t; + int s = 1 << t; + int x = (int)((this.bits.Accumulator >> this.bits.UnreadBits) & (s - 1)); + + if (x < (s >> 1)) + { + x += ((-1) << t) + 1; + } + + return x; + } + + /// + /// Processes a Define Huffman Table marker, and initializes a huffman + /// struct from its contents. Specified in section B.2.4.2. + /// + /// The remaining bytes in the segment block. + private void ProcessDefineHuffmanTablesMarker(int remaining) + { + while (remaining > 0) + { + if (remaining < 17) + { + throw new ImageFormatException("DHT has wrong length"); + } + + this.ReadFull(this.temp, 0, 17); + + int tc = this.temp[0] >> 4; + if (tc > MaxTc) + { + throw new ImageFormatException("Bad Tc value"); + } + + int th = this.temp[0] & 0x0f; + if (th > MaxTh || (!this.isProgressive && (th > 1))) + { + throw new ImageFormatException("Bad Th value"); + } + + Huffman huffman = this.huffmanTrees[tc, th]; + + // Read nCodes and huffman.Valuess (and derive h.Length). + // nCodes[i] is the number of codes with code length i. + // h.Length is the total number of codes. + huffman.Length = 0; + + int[] ncodes = new int[MaxCodeLength]; + for (int i = 0; i < ncodes.Length; i++) + { + ncodes[i] = this.temp[i + 1]; + huffman.Length += ncodes[i]; + } + + if (huffman.Length == 0) + { + throw new ImageFormatException("Huffman table has zero length"); + } + + if (huffman.Length > MaxNCodes) + { + throw new ImageFormatException("Huffman table has excessive length"); + } + + remaining -= huffman.Length + 17; + if (remaining < 0) + { + throw new ImageFormatException("DHT has wrong length"); + } + + this.ReadFull(huffman.Values, 0, huffman.Length); + + // Derive the look-up table. + for (int i = 0; i < huffman.Lut.Length; i++) + { + huffman.Lut[i] = 0; + } + + uint x = 0, code = 0; + + for (int i = 0; i < LutSize; i++) + { + code <<= 1; + + for (int j = 0; j < ncodes[i]; j++) + { + // The codeLength is 1+i, so shift code by 8-(1+i) to + // calculate the high bits for every 8-bit sequence + // whose codeLength's high bits matches code. + // The high 8 bits of lutValue are the encoded value. + // The low 8 bits are 1 plus the codeLength. + byte base2 = (byte)(code << (7 - i)); + ushort lutValue = (ushort)((huffman.Values[x] << 8) | (2 + i)); + + for (int k = 0; k < 1 << (7 - i); k++) + { + huffman.Lut[base2 | k] = lutValue; + } + + code++; + x++; + } + } + + // Derive minCodes, maxCodes, and indices. + int c = 0, index = 0; + for (int i = 0; i < ncodes.Length; i++) + { + int nc = ncodes[i]; + if (nc == 0) + { + huffman.MinCodes[i] = -1; + huffman.MaxCodes[i] = -1; + huffman.Indices[i] = -1; + } + else + { + huffman.MinCodes[i] = c; + huffman.MaxCodes[i] = c + nc - 1; + huffman.Indices[i] = index; + c += nc; + index += nc; + } + + c <<= 1; + } + } + } + + /// + /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. + /// + /// The huffman value + /// The + private byte DecodeHuffman(Huffman huffman) + { + if (huffman.Length == 0) + { + throw new ImageFormatException("Uninitialized Huffman table"); + } + + if (this.bits.UnreadBits < 8) + { + try + { + this.EnsureNBits(8); + + ushort v = huffman.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff]; + + if (v != 0) + { + byte n = (byte)((v & 0xff) - 1); + this.bits.UnreadBits -= n; + this.bits.Mask >>= n; + return (byte)(v >> 8); + } + } + catch (MissingFF00Exception) + { + if (this.bytes.UnreadableBytes != 0) + { + this.UnreadByteStuffedByte(); + } + } + catch (ShortHuffmanDataException) + { + if (this.bytes.UnreadableBytes != 0) + { + this.UnreadByteStuffedByte(); + } + } + } + + int code = 0; + for (int i = 0; i < MaxCodeLength; i++) + { + if (this.bits.UnreadBits == 0) + { + this.EnsureNBits(1); + } + + if ((this.bits.Accumulator & this.bits.Mask) != 0) + { + code |= 1; + } + + this.bits.UnreadBits--; + this.bits.Mask >>= 1; + + if (code <= huffman.MaxCodes[i]) + { + return huffman.Values[huffman.Indices[i] + code - huffman.MinCodes[i]]; + } + + code <<= 1; + } + + throw new ImageFormatException("Bad Huffman code"); + } + + /// + /// Decodes a single bit + /// + /// The + private bool DecodeBit() + { + if (this.bits.UnreadBits == 0) + { + this.EnsureNBits(1); + } + + bool ret = (this.bits.Accumulator & this.bits.Mask) != 0; + this.bits.UnreadBits--; + this.bits.Mask >>= 1; + return ret; + } + + /// + /// Decodes the given number of bits + /// + /// The number of bits to decode. + /// The + private uint DecodeBits(int count) + { + if (this.bits.UnreadBits < count) + { + this.EnsureNBits(count); + } + + uint ret = this.bits.Accumulator >> (this.bits.UnreadBits - count); + ret = (uint)(ret & ((1 << count) - 1)); + this.bits.UnreadBits -= count; + this.bits.Mask >>= count; + return ret; + } + + /// + /// Fills up the bytes buffer from the underlying stream. + /// It should only be called when there are no unread bytes in bytes. + /// + private void Fill() + { + if (this.bytes.I != this.bytes.J) + { + throw new ImageFormatException("Fill called when unread bytes exist."); + } + + // Move the last 2 bytes to the start of the buffer, in case we need + // to call UnreadByteStuffedByte. + if (this.bytes.J > 2) + { + this.bytes.Buffer[0] = this.bytes.Buffer[this.bytes.J - 2]; + this.bytes.Buffer[1] = this.bytes.Buffer[this.bytes.J - 1]; + this.bytes.I = 2; + this.bytes.J = 2; + } + + // Fill in the rest of the buffer. + int n = this.inputStream.Read(this.bytes.Buffer, this.bytes.J, this.bytes.Buffer.Length - this.bytes.J); + if (n == 0) + { + throw new EOFException(); + } + + this.bytes.J += n; + } + + /// + /// Undoes the most recent ReadByteStuffedByte call, + /// giving a byte of data back from bits to bytes. The Huffman look-up table + /// requires at least 8 bits for look-up, which means that Huffman decoding can + /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot + /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte. + /// + private void UnreadByteStuffedByte() + { + this.bytes.I -= this.bytes.UnreadableBytes; + this.bytes.UnreadableBytes = 0; + if (this.bits.UnreadBits >= 8) + { + this.bits.Accumulator >>= 8; + this.bits.UnreadBits -= 8; + this.bits.Mask >>= 8; + } + } + + /// + /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing. + /// + /// The + private byte ReadByte() + { + while (this.bytes.I == this.bytes.J) + { + this.Fill(); + } + + byte x = this.bytes.Buffer[this.bytes.I]; + this.bytes.I++; + this.bytes.UnreadableBytes = 0; + return x; + } + + /// + /// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data. + /// + /// The + private byte ReadByteStuffedByte() + { + byte x; + + // Take the fast path if bytes.buf contains at least two bytes. + if (this.bytes.I + 2 <= this.bytes.J) + { + x = this.bytes.Buffer[this.bytes.I]; + this.bytes.I++; + this.bytes.UnreadableBytes = 1; + if (x != JpegConstants.Markers.XFF) + { + return x; + } + + if (this.bytes.Buffer[this.bytes.I] != 0x00) + { + throw new MissingFF00Exception(); + } + + this.bytes.I++; + this.bytes.UnreadableBytes = 2; + return JpegConstants.Markers.XFF; + } + + this.bytes.UnreadableBytes = 0; + + x = this.ReadByte(); + this.bytes.UnreadableBytes = 1; + if (x != JpegConstants.Markers.XFF) + { + return x; + } + + x = this.ReadByte(); + this.bytes.UnreadableBytes = 2; + if (x != 0x00) + { + throw new MissingFF00Exception(); + } + + return JpegConstants.Markers.XFF; + } + + /// + /// Reads exactly length bytes into data. It does not care about byte stuffing. + /// + /// The data to write to. + /// The offset in the source buffer + /// The number of bytes to read + private void ReadFull(byte[] data, int offset, int length) + { + // Unread the overshot bytes, if any. + if (this.bytes.UnreadableBytes != 0) + { + if (this.bits.UnreadBits >= 8) + { + this.UnreadByteStuffedByte(); + } + + this.bytes.UnreadableBytes = 0; + } + + while (length > 0) + { + if (this.bytes.J - this.bytes.I >= length) + { + Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, length); + this.bytes.I += length; + length -= length; + } + else + { + Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, this.bytes.J - this.bytes.I); + offset += this.bytes.J - this.bytes.I; + length -= this.bytes.J - this.bytes.I; + this.bytes.I += this.bytes.J - this.bytes.I; + + this.Fill(); + } + } + } + + /// + /// Skips the next n bytes. + /// + /// The number of bytes to ignore. + private void Skip(int count) + { + // Unread the overshot bytes, if any. + if (this.bytes.UnreadableBytes != 0) + { + if (this.bits.UnreadBits >= 8) + { + this.UnreadByteStuffedByte(); + } + + this.bytes.UnreadableBytes = 0; + } + + while (true) + { + int m = this.bytes.J - this.bytes.I; + if (m > count) + { + m = count; + } + + this.bytes.I += m; + count -= m; + if (count == 0) + { + break; + } + + this.Fill(); + } + } + + /// + /// Processes the Start of Frame marker. Specified in section B.2.2. + /// + /// The remaining bytes in the segment block. + private void ProcessStartOfFrameMarker(int remaining) + { + if (this.componentCount != 0) + { + throw new ImageFormatException("Multiple SOF markers"); + } + + switch (remaining) + { + case 6 + (3 * 1): // Grayscale image. + this.componentCount = 1; + break; + case 6 + (3 * 3): // YCbCr or RGB image. + this.componentCount = 3; + break; + case 6 + (3 * 4): // YCbCrK or CMYK image. + this.componentCount = 4; + break; + default: + throw new ImageFormatException("Incorrect number of components"); + } + + this.ReadFull(this.temp, 0, remaining); + + // We only support 8-bit precision. + if (this.temp[0] != 8) + { + throw new ImageFormatException("Only 8-Bit precision supported."); + } + + this.imageHeight = (this.temp[1] << 8) + this.temp[2]; + this.imageWidth = (this.temp[3] << 8) + this.temp[4]; + if (this.temp[5] != this.componentCount) + { + throw new ImageFormatException("SOF has wrong length"); + } + + for (int i = 0; i < this.componentCount; i++) + { + this.componentArray[i].Identifier = this.temp[6 + (3 * i)]; + + // Section B.2.2 states that "the value of C_i shall be different from + // the values of C_1 through C_(i-1)". + for (int j = 0; j < i; j++) + { + if (this.componentArray[i].Identifier == this.componentArray[j].Identifier) + { + throw new ImageFormatException("Repeated component identifier"); + } + } + + this.componentArray[i].Selector = this.temp[8 + (3 * i)]; + if (this.componentArray[i].Selector > MaxTq) + { + throw new ImageFormatException("Bad Tq value"); + } + + byte hv = this.temp[7 + (3 * i)]; + int h = hv >> 4; + int v = hv & 0x0f; + if (h < 1 || h > 4 || v < 1 || v > 4) + { + throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio"); + } + + if (h == 3 || v == 3) + { + throw new ImageFormatException("Lnsupported subsampling ratio"); + } + + switch (this.componentCount) + { + case 1: + + // If a JPEG image has only one component, section A.2 says "this data + // is non-interleaved by definition" and section A.2.2 says "[in this + // case...] the order of data units within a scan shall be left-to-right + // and top-to-bottom... regardless of the values of H_1 and V_1". Section + // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be + // one data unit". Similarly, section A.1.1 explains that it is the ratio + // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale + // images, H_1 is the maximum H_j for all components j, so that ratio is + // always 1. The component's (h, v) is effectively always (1, 1): even if + // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8 + // MCUs, not two 16x8 MCUs. + h = 1; + v = 1; + break; + + case 3: + + // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0, + // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the + // (h, v) values for the Y component are either (1, 1), (1, 2), + // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values + // must be a multiple of the Cb and Cr component's values. We also + // assume that the two chroma components have the same subsampling + // ratio. + switch (i) + { + case 0: + { + // Y. + // We have already verified, above, that h and v are both + // either 1, 2 or 4, so invalid (h, v) combinations are those + // with v == 4. + if (v == 4) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + } + + case 1: + { + // Cb. + if (this.componentArray[0].HorizontalFactor % h != 0 || this.componentArray[0].VerticalFactor % v != 0) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + } + + case 2: + { + // Cr. + if (this.componentArray[1].HorizontalFactor != h || this.componentArray[1].VerticalFactor != v) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + } + } + + break; + + case 4: + + // For 4-component images (either CMYK or YCbCrK), we only support two + // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. + // Theoretically, 4-component JPEG images could mix and match hv values + // but in practice, those two combinations are the only ones in use, + // and it simplifies the applyBlack code below if we can assume that: + // - for CMYK, the C and K channels have full samples, and if the M + // and Y channels subsample, they subsample both horizontally and + // vertically. + // - for YCbCrK, the Y and K channels have full samples. + switch (i) + { + case 0: + if (hv != 0x11 && hv != 0x22) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + case 1: + case 2: + if (hv != 0x11) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + case 3: + if (this.componentArray[0].HorizontalFactor != h || this.componentArray[0].VerticalFactor != v) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + } + + break; + } + + this.componentArray[i].HorizontalFactor = h; + this.componentArray[i].VerticalFactor = v; + } + } + + /// + /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. + /// + /// The remaining bytes in the segment block. + /// + /// Thrown if the tables do not match the header + /// + private void ProcessDqt(int remaining) + { + while (remaining > 0) + { + bool done = false; + + remaining--; + byte x = this.ReadByte(); + byte tq = (byte)(x & 0x0f); + if (tq > MaxTq) + { + throw new ImageFormatException("Bad Tq value"); + } + + switch (x >> 4) + { + case 0: + if (remaining < Block.BlockSize) + { + done = true; + break; + } + + remaining -= Block.BlockSize; + this.ReadFull(this.temp, 0, Block.BlockSize); + + for (int i = 0; i < Block.BlockSize; i++) + { + this.quantizationTables[tq][i] = this.temp[i]; + } + + break; + case 1: + if (remaining < 2 * Block.BlockSize) + { + done = true; + break; + } + + remaining -= 2 * Block.BlockSize; + this.ReadFull(this.temp, 0, 2 * Block.BlockSize); + + for (int i = 0; i < Block.BlockSize; i++) + { + this.quantizationTables[tq][i] = (this.temp[2 * i] << 8) | this.temp[(2 * i) + 1]; + } + + break; + default: + throw new ImageFormatException("Bad Pq value"); + } + + if (done) + { + break; + } + } + + if (remaining != 0) + { + throw new ImageFormatException("DQT has wrong length"); + } + } + + /// + /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in macroblocks + /// + /// The remaining bytes in the segment block. + private void ProcessDefineRestartIntervalMarker(int remaining) + { + if (remaining != 2) + { + throw new ImageFormatException("DRI has wrong length"); + } + + this.ReadFull(this.temp, 0, 2); + this.restartInterval = ((int)this.temp[0] << 8) + (int)this.temp[1]; + } + + /// + /// Processes the application header containing the JFIF identifier plus extra data. + /// + /// The remaining bytes in the segment block. + private void ProcessApplicationHeader(int remaining) + { + if (remaining < 5) + { + this.Skip(remaining); + return; + } + + this.ReadFull(this.temp, 0, 13); + remaining -= 13; + + // TODO: We should be using constants for this. + this.isJfif = this.temp[0] == 'J' && + this.temp[1] == 'F' && + this.temp[2] == 'I' && + this.temp[3] == 'F' && + this.temp[4] == '\x00'; + + if (this.isJfif) + { + this.horizontalResolution = (short)(this.temp[9] + (this.temp[10] << 8)); + this.verticalResolution = (short)(this.temp[11] + (this.temp[12] << 8)); + } + + if (remaining > 0) + { + this.Skip(remaining); + } + } + + /// + /// Processes the App1 marker retrieving any stored metadata + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The remaining bytes in the segment block. + /// The image. + private void ProcessApp1Marker(int remaining, Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + if (remaining < 6) + { + this.Skip(remaining); + return; + } + + byte[] profile = new byte[remaining]; + this.ReadFull(profile, 0, remaining); + + if (profile[0] == 'E' && + profile[1] == 'x' && + profile[2] == 'i' && + profile[3] == 'f' && + profile[4] == '\0' && + profile[5] == '\0') + { + image.ExifProfile = new ExifProfile(profile); + } + } + + /// + /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters. + /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not + /// deleted by default when deleting all metadata because it may affect the appearance of the image. + /// + /// The remaining number of bytes in the stream. + private void ProcessApp14Marker(int remaining) + { + if (remaining < 12) + { + this.Skip(remaining); + return; + } + + this.ReadFull(this.temp, 0, 12); + remaining -= 12; + + if (this.temp[0] == 'A' && + this.temp[1] == 'd' && + this.temp[2] == 'o' && + this.temp[3] == 'b' && + this.temp[4] == 'e') + { + this.adobeTransformValid = true; + this.adobeTransform = this.temp[11]; + } + + if (remaining > 0) + { + this.Skip(remaining); + } + } + + /// + /// Converts the image from the original CMYK image pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image width. + /// The image height. + /// The image. + private void ConvertFromCmyk(int width, int height, Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + if (!this.adobeTransformValid) + { + throw new ImageFormatException("Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); + } + + // If the 4-component JPEG image isn't explicitly marked as "Unknown (RGB + // or CMYK)" as per http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + if (this.adobeTransform != JpegConstants.Adobe.ColorTransformUnknown) + { + int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + + image.InitPixels(width, height); + + using (PixelAccessor pixels = image.Lock()) + { + // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get + // CMY, and patch in the original K. The RGB to CMY inversion cancels + // out the 'Adobe inversion' described in the applyBlack doc comment + // above, so in practice, only the fourth channel (black) is inverted. + Parallel.For( + 0, + height, + y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) + { + byte yy = this.ycbcrImage.YChannel[yo + x]; + byte cb = this.ycbcrImage.CbChannel[co + (x / scale)]; + byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; + + TColor packed = default(TColor); + this.PackCmyk(ref packed, yy, cb, cr, x, y); + pixels[x, y] = packed; + } + }); + } + + this.AssignResolution(image); + } + } + + /// + /// Converts the image from the original grayscale image pixels. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image width. + /// The image height. + /// The image. + private void ConvertFromGrayScale(int width, int height, Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + image.InitPixels(width, height); + + using (PixelAccessor pixels = image.Lock()) + { + Parallel.For( + 0, + height, + Bootstrapper.Instance.ParallelOptions, + y => + { + int yoff = this.grayImage.GetRowOffset(y); + for (int x = 0; x < width; x++) + { + byte rgb = this.grayImage.Pixels[yoff + x]; + + TColor packed = default(TColor); + packed.PackFromBytes(rgb, rgb, rgb, 255); + pixels[x, y] = packed; + } + }); + } + + this.AssignResolution(image); + } + + /// + /// Converts the image from the original YCbCr image pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image width. + /// The image height. + /// The image. + private void ConvertFromYCbCr(int width, int height, Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + image.InitPixels(width, height); + + using (PixelAccessor pixels = image.Lock()) + { + Parallel.For( + 0, + height, + Bootstrapper.Instance.ParallelOptions, + y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) + { + byte yy = this.ycbcrImage.YChannel[yo + x]; + byte cb = this.ycbcrImage.CbChannel[co + (x / scale)]; + byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; + + TColor packed = default(TColor); + this.PackYcbCr(ref packed, yy, cb, cr); + pixels[x, y] = packed; + } + }); + } + + this.AssignResolution(image); + } + + /// + /// Converts the image from the original RBG image pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image width. + /// The height. + /// The image. + private void ConvertFromRGB(int width, int height, Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + image.InitPixels(width, height); + + using (PixelAccessor pixels = image.Lock()) + { + Parallel.For( + 0, + height, + Bootstrapper.Instance.ParallelOptions, + y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) + { + byte red = this.ycbcrImage.YChannel[yo + x]; + byte green = this.ycbcrImage.CbChannel[co + (x / scale)]; + byte blue = this.ycbcrImage.CrChannel[co + (x / scale)]; + + TColor packed = default(TColor); + packed.PackFromBytes(red, green, blue, 255); + pixels[x, y] = packed; + } + }); + } + + this.AssignResolution(image); + } + + /// + /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to assign the resolution to. + private void AssignResolution(Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) + { + image.HorizontalResolution = this.horizontalResolution; + image.VerticalResolution = this.verticalResolution; + } + } + + /// + /// Processes the SOS (Start of scan marker). + /// + /// + /// TODO: This also needs some significant refactoring to follow a more OO format. + /// + /// The remaining bytes in the segment block. + /// + /// Missing SOF Marker + /// SOS has wrong length + /// + private void ProcessStartOfScan(int remaining) + { + if (this.componentCount == 0) + { + throw new ImageFormatException("Missing SOF marker"); + } + + if (remaining < 6 || 4 + (2 * this.componentCount) < remaining || remaining % 2 != 0) + { + throw new ImageFormatException("SOS has wrong length"); + } + + this.ReadFull(this.temp, 0, remaining); + byte scanComponentCount = this.temp[0]; + + if (remaining != 4 + (2 * scanComponentCount)) + { + throw new ImageFormatException("SOS length inconsistent with number of components"); + } + + Scan[] scan = new Scan[MaxComponents]; + int totalHv = 0; + + for (int i = 0; i < scanComponentCount; i++) + { + // Component selector. + int cs = this.temp[1 + (2 * i)]; + int compIndex = -1; + for (int j = 0; j < this.componentCount; j++) + { + Component compv = this.componentArray[j]; + if (cs == compv.Identifier) + { + compIndex = j; + } + } + + if (compIndex < 0) + { + throw new ImageFormatException("Unknown component selector"); + } + + scan[i].Index = (byte)compIndex; + + // Section B.2.3 states that "the value of Cs_j shall be different from + // the values of Cs_1 through Cs_(j-1)". Since we have previously + // verified that a frame's component identifiers (C_i values in section + // B.2.2) are unique, it suffices to check that the implicit indexes + // into comp are unique. + for (int j = 0; j < i; j++) + { + if (scan[i].Index == scan[j].Index) + { + throw new ImageFormatException("Repeated component selector"); + } + } + + totalHv += this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor; + + scan[i].DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4); + if (scan[i].DcTableSelector > MaxTh) + { + throw new ImageFormatException("Bad DC table selector value"); + } + + scan[i].AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f); + if (scan[i].AcTableSelector > MaxTh) + { + throw new ImageFormatException("Bad AC table selector value"); + } + } + + // Section B.2.3 states that if there is more than one component then the + // total H*V values in a scan must be <= 10. + if (this.componentCount > 1 && totalHv > 10) + { + throw new ImageFormatException("Total sampling factors too large."); + } + + // zigStart and zigEnd are the spectral selection bounds. + // ah and al are the successive approximation high and low values. + // The spec calls these values Ss, Se, Ah and Al. + // For progressive JPEGs, these are the two more-or-less independent + // aspects of progression. Spectral selection progression is when not + // all of a block's 64 DCT coefficients are transmitted in one pass. + // For example, three passes could transmit coefficient 0 (the DC + // component), coefficients 1-5, and coefficients 6-63, in zig-zag + // order. Successive approximation is when not all of the bits of a + // band of coefficients are transmitted in one pass. For example, + // three passes could transmit the 6 most significant bits, followed + // by the second-least significant bit, followed by the least + // significant bit. + // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. + int zigStart = 0; + int zigEnd = Block.BlockSize - 1; + int ah = 0; + int al = 0; + + if (this.isProgressive) + { + zigStart = this.temp[1 + (2 * scanComponentCount)]; + zigEnd = this.temp[2 + (2 * scanComponentCount)]; + ah = this.temp[3 + (2 * scanComponentCount)] >> 4; + al = this.temp[3 + (2 * scanComponentCount)] & 0x0f; + + if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd) + { + throw new ImageFormatException("Bad spectral selection bounds"); + } + + if (zigStart != 0 && scanComponentCount != 1) + { + throw new ImageFormatException("Progressive AC coefficients for more than one component"); + } + + if (ah != 0 && ah != al + 1) + { + throw new ImageFormatException("Bad successive approximation values"); + } + } + + // mxx and myy are the number of MCUs (Minimum Coded Units) in the image. + int h0 = this.componentArray[0].HorizontalFactor; + int v0 = this.componentArray[0].VerticalFactor; + int mxx = (this.imageWidth + (8 * h0) - 1) / (8 * h0); + int myy = (this.imageHeight + (8 * v0) - 1) / (8 * v0); + + if (this.grayImage == null && this.ycbcrImage == null) + { + this.MakeImage(mxx, myy); + } + + if (this.isProgressive) + { + for (int i = 0; i < scanComponentCount; i++) + { + int compIndex = scan[i].Index; + if (this.progCoeffs[compIndex] == null) + { + this.progCoeffs[compIndex] = new Block[mxx * myy * this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor]; + + for (int j = 0; j < this.progCoeffs[compIndex].Length; j++) + { + this.progCoeffs[compIndex][j] = new Block(); + } + } + } + } + + this.bits = new Bits(); + + int mcu = 0; + byte expectedRst = JpegConstants.Markers.RST0; + + // b is the decoded coefficients block, in natural (not zig-zag) order. + Block b; + int[] dc = new int[MaxComponents]; + + // bx and by are the location of the current block, in units of 8x8 + // blocks: the third block in the first row has (bx, by) = (2, 0). + int bx, by, blockCount = 0; + + for (int my = 0; my < myy; my++) + { + for (int mx = 0; mx < mxx; mx++) + { + for (int i = 0; i < scanComponentCount; i++) + { + int compIndex = scan[i].Index; + int hi = this.componentArray[compIndex].HorizontalFactor; + int vi = this.componentArray[compIndex].VerticalFactor; + Block qt = this.quantizationTables[this.componentArray[compIndex].Selector]; + + for (int j = 0; j < hi * vi; j++) + { + // The blocks are traversed one MCU at a time. For 4:2:0 chroma + // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. + // For a baseline 32x16 pixel image, the Y blocks visiting order is: + // 0 1 4 5 + // 2 3 6 7 + // For progressive images, the interleaved scans (those with component count > 1) + // are traversed as above, but non-interleaved scans are traversed left + // to right, top to bottom: + // 0 1 2 3 + // 4 5 6 7 + // Only DC scans (zigStart == 0) can be interleave AC scans must have + // only one component. + // To further complicate matters, for non-interleaved scans, there is no + // data for any blocks that are inside the image at the MCU level but + // outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 + // progressive image consists of two 16x16 MCUs. The interleaved scans + // will process 8 Y blocks: + // 0 1 4 5 + // 2 3 6 7 + // The non-interleaved scans will process only 6 Y blocks: + // 0 1 2 + // 3 4 5 + if (scanComponentCount != 1) + { + bx = (hi * mx) + (j % hi); + by = (vi * my) + (j / hi); + } + else + { + int q = mxx * hi; + bx = blockCount % q; + by = blockCount / q; + blockCount++; + if (bx * 8 >= this.imageWidth || by * 8 >= this.imageHeight) + { + continue; + } + } + + // Load the previous partially decoded coefficients, if applicable. + b = this.isProgressive ? this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] : new Block(); + + if (ah != 0) + { + this.Refine(b, this.huffmanTrees[AcTable, scan[i].AcTableSelector], zigStart, zigEnd, 1 << al); + } + else + { + int zig = zigStart; + if (zig == 0) + { + zig++; + + // Decode the DC coefficient, as specified in section F.2.2.1. + byte value = this.DecodeHuffman(this.huffmanTrees[DcTable, scan[i].DcTableSelector]); + if (value > 16) + { + throw new ImageFormatException("Excessive DC component"); + } + + int deltaDC = this.ReceiveExtend(value); + dc[compIndex] += deltaDC; + b[0] = dc[compIndex] << al; + } + + if (zig <= zigEnd && this.eobRun > 0) + { + this.eobRun--; + } + else + { + // Decode the AC coefficients, as specified in section F.2.2.2. + Huffman huffv = this.huffmanTrees[AcTable, scan[i].AcTableSelector]; + for (; zig <= zigEnd; zig++) + { + byte value = this.DecodeHuffman(huffv); + byte val0 = (byte)(value >> 4); + byte val1 = (byte)(value & 0x0f); + if (val1 != 0) + { + zig += val0; + if (zig > zigEnd) + { + break; + } + + int ac = this.ReceiveExtend(val1); + b[Unzig[zig]] = ac << al; + } + else + { + if (val0 != 0x0f) + { + this.eobRun = (ushort)(1 << val0); + if (val0 != 0) + { + this.eobRun |= (ushort)this.DecodeBits(val0); + } + + this.eobRun--; + break; + } + + zig += 0x0f; + } + } + } + } + + if (this.isProgressive) + { + if (zigEnd != Block.BlockSize - 1 || al != 0) + { + // We haven't completely decoded this 8x8 block. Save the coefficients. + this.progCoeffs[compIndex][((by * mxx) * hi) + bx] = b; + + // At this point, we could execute the rest of the loop body to dequantize and + // perform the inverse DCT, to save early stages of a progressive image to the + // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, + // the jpeg.Decode function does not return until the entire image is decoded, + // so we "continue" here to avoid wasted computation. + continue; + } + } + + // Dequantize, perform the inverse DCT and store the block to the image. + for (int zig = 0; zig < Block.BlockSize; zig++) + { + b[Unzig[zig]] *= qt[zig]; + } + + IDCT.Transform(b); + + byte[] dst; + int offset; + int stride; + + if (this.componentCount == 1) + { + dst = this.grayImage.Pixels; + stride = this.grayImage.Stride; + offset = this.grayImage.Offset + (8 * ((by * this.grayImage.Stride) + bx)); + } + else + { + switch (compIndex) + { + case 0: + dst = this.ycbcrImage.YChannel; + stride = this.ycbcrImage.YStride; + offset = this.ycbcrImage.YOffset + (8 * ((by * this.ycbcrImage.YStride) + bx)); + break; + + case 1: + dst = this.ycbcrImage.CbChannel; + stride = this.ycbcrImage.CStride; + offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx)); + break; + + case 2: + dst = this.ycbcrImage.CrChannel; + stride = this.ycbcrImage.CStride; + offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx)); + break; + + case 3: + + dst = this.blackPixels; + stride = this.blackStride; + offset = 8 * ((by * this.blackStride) + bx); + break; + + default: + throw new ImageFormatException("Too many components"); + } + } + + // Level shift by +128, clip to [0, 255], and write to dst. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + int yStride = y * stride; + + for (int x = 0; x < 8; x++) + { + int c = b[y8 + x]; + if (c < -128) + { + c = 0; + } + else if (c > 127) + { + c = 255; + } + else + { + c += 128; + } + + dst[yStride + x + offset] = (byte)c; + } + } + } + + // for j + } + + // for i + mcu++; + + if (this.restartInterval > 0 && mcu % this.restartInterval == 0 && mcu < mxx * myy) + { + // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, + // but this one assumes well-formed input, and hence the restart marker follows immediately. + this.ReadFull(this.temp, 0, 2); + if (this.temp[0] != 0xff || this.temp[1] != expectedRst) + { + throw new ImageFormatException("Bad RST marker"); + } + + expectedRst++; + if (expectedRst == JpegConstants.Markers.RST7 + 1) + { + expectedRst = JpegConstants.Markers.RST0; + } + + // Reset the Huffman decoder. + this.bits = new Bits(); + + // Reset the DC components, as per section F.2.1.3.1. + dc = new int[MaxComponents]; + + // Reset the progressive decoder state, as per section G.1.2.2. + this.eobRun = 0; + } + } + + // for mx + } + + // for my + } + + /// + /// Decodes a successive approximation refinement block, as specified in section G.1.2. + /// + /// The block of coefficients + /// The Huffman tree + /// The zig-zag start index + /// The zig-zag end index + /// The low transform offset + private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta) + { + // Refining a DC component is trivial. + if (zigStart == 0) + { + if (zigEnd != 0) + { + throw new ImageFormatException("Invalid state for zig DC component"); + } + + bool bit = this.DecodeBit(); + if (bit) + { + b[0] |= delta; + } + + return; + } + + // Refining AC components is more complicated; see sections G.1.2.2 and G.1.2.3. + int zig = zigStart; + if (this.eobRun == 0) + { + for (; zig <= zigEnd; zig++) + { + bool done = false; + int z = 0; + byte val = this.DecodeHuffman(h); + int val0 = val >> 4; + int val1 = val & 0x0f; + + switch (val1) + { + case 0: + if (val0 != 0x0f) + { + this.eobRun = (ushort)(1 << val0); + if (val0 != 0) + { + this.eobRun |= (ushort)this.DecodeBits(val0); + } + + done = true; + } + + break; + case 1: + z = delta; + bool bit = this.DecodeBit(); + if (!bit) + { + z = -z; + } + + break; + default: + throw new ImageFormatException("Unexpected Huffman code"); + } + + if (done) + { + break; + } + + zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta); + if (zig > zigEnd) + { + throw new ImageFormatException($"Too many coefficients {zig} > {zigEnd}"); + } + + if (z != 0) + { + b[Unzig[zig]] = z; + } + } + } + + if (this.eobRun > 0) + { + this.eobRun--; + this.RefineNonZeroes(b, zig, zigEnd, -1, delta); + } + } + + /// + /// Refines non-zero entries of b in zig-zag order. + /// If >= 0, the first zero entries are skipped over. + /// + /// The block of coefficients + /// The zig-zag start index + /// The zig-zag end index + /// The non-zero entry + /// The low transform offset + /// The + private int RefineNonZeroes(Block b, int zig, int zigEnd, int nz, int delta) + { + for (; zig <= zigEnd; zig++) + { + int u = Unzig[zig]; + if (b[u] == 0) + { + if (nz == 0) + { + break; + } + + nz--; + continue; + } + + bool bit = this.DecodeBit(); + if (!bit) + { + continue; + } + + if (b[u] >= 0) + { + b[u] += delta; + } + else + { + b[u] -= delta; + } + } + + return zig; + } + + /// + /// Makes the image from the buffer. + /// + /// The horizontal MCU count + /// The vertical MCU count + private void MakeImage(int mxx, int myy) + { + if (this.componentCount == 1) + { + GrayImage gray = new GrayImage(8 * mxx, 8 * myy); + this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight); + } + else + { + int h0 = this.componentArray[0].HorizontalFactor; + int v0 = this.componentArray[0].VerticalFactor; + int horizontalRatio = h0 / this.componentArray[1].HorizontalFactor; + int verticalRatio = v0 / this.componentArray[1].VerticalFactor; + + YCbCrImage.YCbCrSubsampleRatio ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; + switch ((horizontalRatio << 4) | verticalRatio) + { + case 0x11: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; + break; + case 0x12: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440; + break; + case 0x21: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422; + break; + case 0x22: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420; + break; + case 0x41: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411; + break; + case 0x42: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410; + break; + } + + YCbCrImage ycbcr = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio); + this.ycbcrImage = ycbcr.Subimage(0, 0, this.imageWidth, this.imageHeight); + + if (this.componentCount == 4) + { + int h3 = this.componentArray[3].HorizontalFactor; + int v3 = this.componentArray[3].VerticalFactor; + this.blackPixels = new byte[8 * h3 * mxx * 8 * v3 * myy]; + this.blackStride = 8 * h3 * mxx; + } + } + } + + /// + /// Returns a value indicating whether the image in an RGB image. + /// + /// + /// The . + /// + private bool IsRGB() + { + if (this.isJfif) + { + return false; + } + + if (this.adobeTransformValid && this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) + { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + // says that 0 means Unknown (and in practice RGB) and 1 means YCbCr. + return true; + } + + return this.componentArray[0].Identifier == 'R' && this.componentArray[1].Identifier == 'G' && this.componentArray[2].Identifier == 'B'; + } + + /// + /// Optimized method to pack bytes to the image from the YCbCr color space. + /// This is faster than implicit casting as it avoids double packing. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The packed pixel. + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + private void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int ccb = cb - 128; + int ccr = cr - 128; + + byte r = (byte)(y + (1.402F * ccr)).Clamp(0, 255); + byte g = (byte)(y - (0.34414F * ccb) - (0.71414F * ccr)).Clamp(0, 255); + byte b = (byte)(y + (1.772F * ccb)).Clamp(0, 255); + + packed.PackFromBytes(r, g, b, 255); + } + + /// + /// Optimized method to pack bytes to the image from the CMYK color space. + /// This is faster than implicit casting as it avoids double packing. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The packed pixel. + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + /// The x-position within the image. + /// The y-position within the image. + private void PackCmyk(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // TODO: We can speed this up further with Vector4 + int ccb = cb - 128; + int ccr = cr - 128; + + // First convert from YCbCr to CMY + float cyan = (y + (1.402F * ccr)).Clamp(0, 255) / 255F; + float magenta = (y - (0.34414F * ccb) - (0.71414F * ccr)).Clamp(0, 255) / 255F; + float yellow = (y + (1.772F * ccb)).Clamp(0, 255) / 255F; + + // Get keyline + float keyline = (255 - this.blackPixels[(yy * this.blackStride) + xx]) / 255F; + + // Convert back to RGB + byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255); + byte g = (byte)(((1 - magenta) * (1 - keyline)).Clamp(0, 1) * 255); + byte b = (byte)(((1 - yellow) * (1 - keyline)).Clamp(0, 1) * 255); + + packed.PackFromBytes(r, g, b, 255); + } + + /// + /// Represents a component scan + /// + private struct Scan + { + /// + /// Gets or sets the component index. + /// + public byte Index { get; set; } + + /// + /// Gets or sets the DC table selector + /// + public byte DcTableSelector { get; set; } + + /// + /// Gets or sets the AC table selector + /// + public byte AcTableSelector { get; set; } + } + + /// + /// The missing ff00 exception. + /// + private class MissingFF00Exception : Exception + { + } + + /// + /// The short huffman data exception. + /// + private class ShortHuffmanDataException : Exception + { + } + + /// + /// The EOF (End of File exception). + /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker + /// + private class EOFException : Exception + { + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/JpegEncoder.cs b/src/ImageSharp46/Formats/Jpg/JpegEncoder.cs new file mode 100644 index 000000000..0fc1f3d35 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/JpegEncoder.cs @@ -0,0 +1,100 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Encoder for writing the data image to a stream in jpeg format. + /// + public class JpegEncoder : IImageEncoder + { + /// + /// The quality used to encode the image. + /// + private int quality = 75; + + /// + /// The subsamples scheme used to encode the image. + /// + private JpegSubsample subsample = JpegSubsample.Ratio420; + + /// + /// Whether subsampling has been specifically set. + /// + private bool subsampleSet; + + /// + /// Gets or sets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// + /// If the quality is less than or equal to 80, the subsampling ratio will switch to + /// + /// The quality of the jpg image from 0 to 100. + public int Quality + { + get { return this.quality; } + set { this.quality = value.Clamp(1, 100); } + } + + /// + /// Gets or sets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + public JpegSubsample Subsample + { + get + { + return this.subsample; + } + + set + { + this.subsample = value; + this.subsampleSet = true; + } + } + + /// + public string MimeType => "image/jpeg"; + + /// + public string Extension => "jpg"; + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + if (extension.StartsWith(".")) + { + extension = extension.Substring(1); + } + + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase) || + extension.Equals("jpeg", StringComparison.OrdinalIgnoreCase) || + extension.Equals("jfif", StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + JpegEncoderCore encode = new JpegEncoderCore(); + if (this.subsampleSet) + { + encode.Encode(image, stream, this.Quality, this.Subsample); + } + else + { + encode.Encode(image, stream, this.Quality, this.Quality >= 80 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); + } + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs new file mode 100644 index 000000000..8d9b48a20 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs @@ -0,0 +1,1011 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Image encoder for writing an image to a stream as a jpeg. + /// + internal unsafe class JpegEncoderCore + { + /// + /// The number of quantization tables. + /// + private const int QuantizationTableCount = 2; + + /// + /// Maps from the zig-zag ordering to the natural ordering. For example, + /// unzig[3] is the column and row of the fourth element in zig-zag order. The + /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// + private static readonly int[] Unzig = + { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, + 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, + 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, + 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, + }; + + /// + /// The Huffman encoding specifications. + /// This encoder uses the same Huffman encoding for all images. + /// + private static readonly HuffmanSpec[] TheHuffmanSpecs = + { + // Luminance DC. + new HuffmanSpec( + new byte[] + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + }, + new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }), + new HuffmanSpec( + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + }, + new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + + // Chrominance AC. + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, + 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }) + }; + + /// + /// The compiled representations of theHuffmanSpec. + /// + private static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; + + /// + /// Counts the number of bits needed to hold an integer. + /// + private readonly byte[] bitCountLut = + { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, + }; + + /// + /// The unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + private readonly byte[,] unscaledQuant = + { + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, + 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, + 101, 103, 99, + }, + { + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + } + }; + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough. + /// + private readonly byte[] emitBuffer = new byte[64]; + + /// + /// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths + identifier. + /// + private readonly byte[] huffmanBuffer = new byte[179]; + + /// + /// The scaled quantization tables, in zig-zag order. + /// + private readonly byte[][] quant = new byte[QuantizationTableCount][]; + + /// + /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: + /// - the marker length "\x00\x0c", + /// - the number of components "\x03", + /// - component 1 uses DC table 0 and AC table 0 "\x01\x00", + /// - component 2 uses DC table 1 and AC table 1 "\x02\x11", + /// - component 3 uses DC table 1 and AC table 1 "\x03\x11", + /// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + /// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + /// should be 0x00, 0x3f, 0x00<<4 | 0x00. + /// + private readonly byte[] sosHeaderYCbCr = + { + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, // Marker + 0x00, 0x0c, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) + 0x03, // Number of components in a scan, 3 + 0x01, // Component Id Y + 0x00, // DC/AC Huffman table + 0x02, // Component Id Cb + 0x11, // DC/AC Huffman table + 0x03, // Component Id Cr + 0x11, // DC/AC Huffman table + 0x00, // Ss - Start of spectral selection. + 0x3f, // Se - End of spectral selection. + 0x00 // Ah + Ah (Successive approximation bit position high + low) + }; + + /// + /// The accumulated bits to write to the stream. + /// + private uint accumulatedBits; + + /// + /// The accumulated bit count. + /// + private uint bitCount; + + /// + /// The output stream. All attempted writes after the first error become no-ops. + /// + private Stream outputStream; + + /// + /// The subsampling method to use. + /// + private JpegSubsample subsample; + + /// + /// Initializes static members of the class. + /// + static JpegEncoderCore() + { + // Initialize the Huffman tables + for (int i = 0; i < TheHuffmanSpecs.Length; i++) + { + TheHuffmanLut[i] = new HuffmanLut(TheHuffmanSpecs[i]); + } + } + + /// + /// Enumerates the Huffman tables + /// + private enum HuffIndex + { + /// + /// The DC luminance huffman table index + /// + LuminanceDC = 0, + + /// + /// The AC luminance huffman table index + /// + + LuminanceAC = 1, + // ReSharper restore UnusedMember.Local + + /// + /// The DC chrominance huffman table index + /// + ChrominanceDC = 2, + + /// + /// The AC chrominance huffman table index + /// + ChrominanceAC = 3, + } + + /// + /// Enumerates the quantization tables + /// + private enum QuantIndex + { + /// + /// The luminance quantization table index + /// + Luminance = 0, + + /// + /// The chrominance quantization table index + /// + Chrominance = 1, + } + + /// + /// Encode writes the image to the jpeg baseline format with the given options. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to write from. + /// The stream to write to. + /// The quality. + /// The subsampling mode. + public void Encode(Image image, Stream stream, int quality, JpegSubsample sample) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + ushort max = JpegConstants.MaxLength; + if (image.Width >= max || image.Height >= max) + { + throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); + } + + this.outputStream = stream; + this.subsample = sample; + + for (int i = 0; i < QuantizationTableCount; i++) + { + this.quant[i] = new byte[Block.BlockSize]; + } + + if (quality < 1) + { + quality = 1; + } + + if (quality > 100) + { + quality = 100; + } + + // Convert from a quality rating to a scaling factor. + int scale; + if (quality < 50) + { + scale = 5000 / quality; + } + else + { + scale = 200 - (quality * 2); + } + + // Initialize the quantization tables. + for (int i = 0; i < QuantizationTableCount; i++) + { + for (int j = 0; j < Block.BlockSize; j++) + { + int x = this.unscaledQuant[i, j]; + x = ((x * scale) + 50) / 100; + if (x < 1) + { + x = 1; + } + + if (x > 255) + { + x = 255; + } + + this.quant[i][j] = (byte)x; + } + } + + // Compute number of components based on input image type. + int componentCount = 3; + + // Write the Start Of Image marker. + this.WriteApplicationHeader((short)image.HorizontalResolution, (short)image.VerticalResolution); + + this.WriteProfiles(image); + + // Write the quantization tables. + this.WriteDefineQuantizationTables(); + + // Write the image dimensions. + this.WriteStartOfFrame(image.Width, image.Height, componentCount); + + // Write the Huffman tables. + this.WriteDefineHuffmanTables(componentCount); + + // Write the image data. + using (PixelAccessor pixels = image.Lock()) + { + this.WriteStartOfScan(pixels); + } + + // Write the End Of Image marker. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.EOI; + stream.Write(this.buffer, 0, 2); + stream.Flush(); + } + + /// + /// Gets the quotient of the two numbers rounded to the nearest integer, instead of rounded to zero. + /// + /// The value to divide. + /// The value to divide by. + /// The + private static int Round(int dividend, int divisor) + { + if (dividend >= 0) + { + return (dividend + (divisor >> 1)) / divisor; + } + + return -((-dividend + (divisor >> 1)) / divisor); + } + + /// + /// Emits the least significant count of bits of bits to the bit-stream. + /// The precondition is bits < 1<<nBits && nBits <= 16. + /// + /// The packed bits. + /// The number of bits + private void Emit(uint bits, uint count) + { + count += this.bitCount; + bits <<= (int)(32 - count); + bits |= this.accumulatedBits; + + // Only write if more than 8 bits. + if (count >= 8) + { + // Track length + int len = 0; + while (count >= 8) + { + byte b = (byte)(bits >> 24); + this.emitBuffer[len++] = b; + if (b == 0xff) + { + this.emitBuffer[len++] = 0x00; + } + + bits <<= 8; + count -= 8; + } + + if (len > 0) + { + this.outputStream.Write(this.emitBuffer, 0, len); + } + } + + this.accumulatedBits = bits; + this.bitCount = count; + } + + /// + /// Emits the given value with the given Huffman encoder. + /// + /// The index of the Huffman encoder + /// The value to encode. + private void EmitHuff(HuffIndex index, int value) + { + uint x = TheHuffmanLut[(int)index].Values[value]; + this.Emit(x & ((1 << 24) - 1), x >> 24); + } + + /// + /// Emits a run of runLength copies of value encoded with the given Huffman encoder. + /// + /// The index of the Huffman encoder + /// The number of copies to encode. + /// The value to encode. + private void EmitHuffRLE(HuffIndex index, int runLength, int value) + { + int a = value; + int b = value; + if (a < 0) + { + a = -value; + b = value - 1; + } + + uint bt; + if (a < 0x100) + { + bt = this.bitCountLut[a]; + } + else + { + bt = 8 + (uint)this.bitCountLut[a >> 8]; + } + + this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); + if (bt > 0) + { + this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt); + } + } + + /// + /// Writes a block of pixel data using the given quantization table, + /// returning the post-quantized DC value of the DCT-transformed block. + /// The block is in natural (not zig-zag) order. + /// + /// The block to write. + /// The quantization table index. + /// The previous DC value. + /// The + private int WriteBlock(Block block, QuantIndex index, int prevDC) + { + FDCT.Transform(block); + + // Emit the DC delta. + int dc = Round(block[0], 8 * this.quant[(int)index][0]); + this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC); + + // Emit the AC components. + HuffIndex h = (HuffIndex)((2 * (int)index) + 1); + int runLength = 0; + + for (int zig = 1; zig < Block.BlockSize; zig++) + { + int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]); + + if (ac == 0) + { + runLength++; + } + else + { + while (runLength > 15) + { + this.EmitHuff(h, 0xf0); + runLength -= 16; + } + + this.EmitHuffRLE(h, runLength, ac); + runLength = 0; + } + } + + if (runLength > 0) + { + this.EmitHuff(h, 0x00); + } + + return dc; + } + + /// + /// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The pixel accessor. + /// The x-position within the image. + /// The y-position within the image. + /// The luminance block. + /// The red chroma block. + /// The blue chroma block. + // ReSharper disable StyleCop.SA1305 + private void ToYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) + // ReSharper restore StyleCop.SA1305 + where TColor : struct, IPackedPixel + where TPacked : struct + { + int xmax = pixels.Width - 1; + int ymax = pixels.Height - 1; + byte[] color = new byte[3]; + for (int j = 0; j < 8; j++) + { + for (int i = 0; i < 8; i++) + { + pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)].ToBytes(color, 0, ComponentOrder.XYZ); + + byte r = color[0]; + byte g = color[1]; + byte b = color[2]; + + // Convert returned bytes into the YCbCr color space. Assume RGBA + byte yy = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b)); + byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))); + byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))); + + int index = (8 * j) + i; + yBlock[index] = yy; + cbBlock[index] = cb; + crBlock[index] = cr; + } + } + } + + /// + /// Scales the 16x16 region represented by the 4 source blocks to the 8x8 + /// DST block. + /// + /// The destination block array + /// The source block array. + private void Scale16X16To8X8(Block destination, Block[] source) + { + for (int i = 0; i < 4; i++) + { + int dstOff = ((i & 2) << 4) | ((i & 1) << 2); + for (int y = 0; y < 4; y++) + { + for (int x = 0; x < 4; x++) + { + int j = (16 * y) + (2 * x); + int sum = source[i][j] + source[i][j + 1] + source[i][j + 8] + source[i][j + 9]; + destination[(8 * y) + x + dstOff] = (sum + 2) / 4; + } + } + } + } + + /// + /// Writes the application header containing the JFIF identifier plus extra data. + /// + /// The resolution of the image in the x- direction. + /// The resolution of the image in the y- direction. + private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) + { + // Write the start of image marker. Markers are always prefixed with with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.SOI; + + // Write the JFIF headers + this.buffer[2] = JpegConstants.Markers.XFF; + this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker + this.buffer[4] = 0x00; + this.buffer[5] = 0x10; + this.buffer[6] = 0x4a; // J + this.buffer[7] = 0x46; // F + this.buffer[8] = 0x49; // I + this.buffer[9] = 0x46; // F + this.buffer[10] = 0x00; // = "JFIF",'\0' + this.buffer[11] = 0x01; // versionhi + this.buffer[12] = 0x01; // versionlo + this.buffer[13] = 0x01; // xyunits as dpi + + // No thumbnail + this.buffer[14] = 0x00; // Thumbnail width + this.buffer[15] = 0x00; // Thumbnail height + + this.outputStream.Write(this.buffer, 0, 16); + + // Resolution. Big Endian + this.buffer[0] = (byte)(horizontalResolution >> 8); + this.buffer[1] = (byte)horizontalResolution; + this.buffer[2] = (byte)(verticalResolution >> 8); + this.buffer[3] = (byte)verticalResolution; + + this.outputStream.Write(this.buffer, 0, 4); + } + + /// + /// Writes the metadata profiles to the image. + /// + /// The image. + /// The pixel format. + /// The packed format. uint, long, float. + private void WriteProfiles(Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + this.WriteProfile(image.ExifProfile); + } + + /// + /// Writes the EXIF profile. + /// + /// The exif profile. + /// + /// Thrown if the EXIF profile size exceeds the limit + /// + private void WriteProfile(ExifProfile exifProfile) + { + const int Max = 65533; + byte[] data = exifProfile?.ToByteArray(); + if (data == null || data.Length == 0) + { + return; + } + + if (data.Length > Max) + { + throw new ImageFormatException($"Exif profile size exceeds limit. nameof{Max}"); + } + + int length = data.Length + 2; + + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker + this.buffer[2] = (byte)((length >> 8) & 0xFF); + this.buffer[3] = (byte)(length & 0xFF); + + this.outputStream.Write(this.buffer, 0, 4); + this.outputStream.Write(data, 0, data.Length); + } + + /// + /// Writes the Define Quantization Marker and tables. + /// + private void WriteDefineQuantizationTables() + { + // Marker + quantization table lengths + int markerlen = 2 + (QuantizationTableCount * (1 + Block.BlockSize)); + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); + + // Loop through and collect the tables as one array. + // This allows us to reduce the number of writes to the stream. + byte[] dqt = new byte[(QuantizationTableCount * Block.BlockSize) + QuantizationTableCount]; + int offset = 0; + for (int i = 0; i < QuantizationTableCount; i++) + { + dqt[offset++] = (byte)i; + int len = this.quant[i].Length; + for (int j = 0; j < len; j++) + { + dqt[offset++] = this.quant[i][j]; + } + } + + this.outputStream.Write(dqt, 0, dqt.Length); + } + + /// + /// Writes the Start Of Frame (Baseline) marker + /// + /// The width of the image + /// The height of the image + /// The number of components in a pixel + private void WriteStartOfFrame(int width, int height, int componentCount) + { + // "default" to 4:2:0 + byte[] subsamples = { 0x22, 0x11, 0x11 }; + byte[] chroma = { 0x00, 0x01, 0x01 }; + + switch (this.subsample) + { + case JpegSubsample.Ratio444: + subsamples = new byte[] { 0x11, 0x11, 0x11 }; + break; + case JpegSubsample.Ratio420: + subsamples = new byte[] { 0x22, 0x11, 0x11 }; + break; + } + + // Length (high byte, low byte), 8 + components * 3. + int markerlen = 8 + (3 * componentCount); + this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); + this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported + this.buffer[1] = (byte)(height >> 8); + this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[3] = (byte)(width >> 8); + this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[5] = (byte)componentCount; // Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK) + if (componentCount == 1) + { + this.buffer[6] = 1; + + // No subsampling for grayscale images. + this.buffer[7] = 0x11; + this.buffer[8] = 0x00; + } + else + { + for (int i = 0; i < componentCount; i++) + { + this.buffer[(3 * i) + 6] = (byte)(i + 1); + + // We use 4:2:0 chroma subsampling by default. + this.buffer[(3 * i) + 7] = subsamples[i]; + this.buffer[(3 * i) + 8] = chroma[i]; + } + } + + this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); + } + + /// + /// Writes the Define Huffman Table marker and tables. + /// + /// The number of components to write. + private void WriteDefineHuffmanTables(int componentCount) + { + // Table identifiers. + byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; + int markerlen = 2; + HuffmanSpec[] specs = TheHuffmanSpecs; + + if (componentCount == 1) + { + // Drop the Chrominance tables. + specs = new[] { TheHuffmanSpecs[0], TheHuffmanSpecs[1] }; + } + + foreach (HuffmanSpec s in specs) + { + markerlen += 1 + 16 + s.Values.Length; + } + + this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); + for (int i = 0; i < specs.Length; i++) + { + HuffmanSpec spec = specs[i]; + int len = 0; + fixed (byte* huffman = this.huffmanBuffer) + { + fixed (byte* count = spec.Count) + { + fixed (byte* values = spec.Values) + { + huffman[len++] = headers[i]; + + for (int c = 0; c < spec.Count.Length; c++) + { + huffman[len++] = count[c]; + } + + for (int v = 0; v < spec.Values.Length; v++) + { + huffman[len++] = values[v]; + } + } + } + } + + this.outputStream.Write(this.huffmanBuffer, 0, len); + } + } + + /// + /// Writes the StartOfScan marker. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// + /// The pixel accessor providing access to the image pixels. + /// + private void WriteStartOfScan(PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // TODO: We should allow grayscale writing. + this.outputStream.Write(this.sosHeaderYCbCr, 0, this.sosHeaderYCbCr.Length); + + switch (this.subsample) + { + case JpegSubsample.Ratio444: + this.Encode444(pixels); + break; + case JpegSubsample.Ratio420: + this.Encode420(pixels); + break; + } + + // Pad the last byte with 1's. + this.Emit(0x7f, 7); + } + + /// + /// Encodes the image with no subsampling. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The pixel accessor providing access to the image pixels. + private void Encode444(PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Block b = new Block(); + Block cb = new Block(); + Block cr = new Block(); + // ReSharper disable once InconsistentNaming + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + for (int y = 0; y < pixels.Height; y += 8) + { + for (int x = 0; x < pixels.Width; x += 8) + { + this.ToYCbCr(pixels, x, y, b, cb, cr); + prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); + prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb); + prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr); + } + } + } + + /// + /// Encodes the image with subsampling. The Cb and Cr components are each subsampled + /// at a factor of 2 both horizontally and vertically. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The pixel accessor providing access to the image pixels. + private void Encode420(PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Block b = new Block(); + Block[] cb = new Block[4]; + Block[] cr = new Block[4]; + // ReSharper disable once InconsistentNaming + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + for (int i = 0; i < 4; i++) + { + cb[i] = new Block(); + } + + for (int i = 0; i < 4; i++) + { + cr[i] = new Block(); + } + + for (int y = 0; y < pixels.Height; y += 16) + { + for (int x = 0; x < pixels.Width; x += 16) + { + for (int i = 0; i < 4; i++) + { + int xOff = (i & 1) * 8; + int yOff = (i & 2) * 4; + + this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); + prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); + } + + this.Scale16X16To8X8(b, cb); + prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb); + this.Scale16X16To8X8(b, cr); + prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr); + } + } + } + + /// + /// Writes the header for a marker with the given length. + /// + /// The marker to write. + /// The marker length. + private void WriteMarkerHeader(byte marker, int length) + { + // Markers are always prefixed with with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = marker; + this.buffer[2] = (byte)(length >> 8); + this.buffer[3] = (byte)(length & 0xff); + this.outputStream.Write(this.buffer, 0, 4); + } + + /// + /// The Huffman encoding specifications. + /// + private struct HuffmanSpec + { + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; + + /// + /// Gets value[i] - The decoded value of the codeword at the given index. + /// + public readonly byte[] Values; + + /// + /// Initializes a new instance of the struct. + /// + /// The number of codes. + /// The decoded values. + public HuffmanSpec(byte[] count, byte[] values) + { + this.Count = count; + this.Values = values; + } + } + + /// + /// A compiled look-up table representation of a huffmanSpec. + /// Each value maps to a uint32 of which the 8 most significant bits hold the + /// codeword size in bits and the 24 least significant bits hold the codeword. + /// The maximum codeword size is 16 bits. + /// + private class HuffmanLut + { + /// + /// The collection of huffman values. + /// + public readonly uint[] Values; + + /// + /// Initializes a new instance of the class. + /// + /// The encoding specifications. + public HuffmanLut(HuffmanSpec spec) + { + int maxValue = 0; + + foreach (byte v in spec.Values) + { + if (v > maxValue) + { + maxValue = v; + } + } + + this.Values = new uint[maxValue + 1]; + + int code = 0; + int k = 0; + + for (int i = 0; i < spec.Count.Length; i++) + { + int bits = (i + 1) << 24; + for (int j = 0; j < spec.Count[i]; j++) + { + this.Values[spec.Values[k]] = (uint)(bits | code); + code++; + k++; + } + + code <<= 1; + } + } + } + } +} diff --git a/src/ImageSharp46/Formats/Jpg/JpegFormat.cs b/src/ImageSharp46/Formats/Jpg/JpegFormat.cs new file mode 100644 index 000000000..0e2ff7949 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/JpegFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the means to encode and decode jpeg images. + /// + public class JpegFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new JpegDecoder(); + + /// + public IImageEncoder Encoder => new JpegEncoder(); + } +} diff --git a/src/ImageSharp46/Formats/Jpg/JpegSubsample.cs b/src/ImageSharp46/Formats/Jpg/JpegSubsample.cs new file mode 100644 index 000000000..287323bdd --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/JpegSubsample.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Enumerates the chroma subsampling method applied to the image. + /// + public enum JpegSubsample + { + /// + /// High Quality - Each of the three Y'CbCr components have the same sample rate, + /// thus there is no chroma subsampling. + /// + Ratio444, + + /// + /// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only + /// sampled on each alternate line. + /// + Ratio420 + } +} diff --git a/src/ImageSharp46/Formats/Jpg/README.md b/src/ImageSharp46/Formats/Jpg/README.md new file mode 100644 index 000000000..54bc14847 --- /dev/null +++ b/src/ImageSharp46/Formats/Jpg/README.md @@ -0,0 +1,3 @@ +Encoder/Decoder adapted and extended from: + +https://golang.org/src/image/jpeg/ \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs new file mode 100644 index 000000000..97ded289c --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + + /// + /// The Average filter uses the average of the two neighboring pixels (left and above) to predict + /// the value of a pixel. + /// + /// + internal static class AverageFilter + { + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The previous scanline. + /// The bytes per pixel. + /// + /// The + /// + public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel) + { + // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) + byte[] result = new byte[scanline.Length]; + + for (int x = 1; x < scanline.Length; x++) + { + byte left = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel]; + byte above = previousScanline[x]; + + result[x] = (byte)((scanline[x] + Average(left, above)) % 256); + } + + return result; + } + + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The previous scanline. + /// The bytes per pixel. + /// The + public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel) + { + // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) + byte[] encodedScanline = new byte[scanline.Length + 1]; + + encodedScanline[0] = (byte)FilterType.Average; + + for (int x = 0; x < scanline.Length; x++) + { + byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; + byte above = previousScanline[x]; + + encodedScanline[x + 1] = (byte)((scanline[x] - Average(left, above)) % 256); + } + + return encodedScanline; + } + + /// + /// Calculates the average value of two bytes + /// + /// The left byte + /// The above byte + /// The + private static int Average(byte left, byte above) + { + return Convert.ToInt32(Math.Floor((left + above) / 2.0D)); + } + } +} diff --git a/src/ImageSharp46/Formats/Png/Filters/FilterType.cs b/src/ImageSharp46/Formats/Png/Filters/FilterType.cs new file mode 100644 index 000000000..0eddf08f2 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Filters/FilterType.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Provides enumeration of the various PNG filter types. + /// + /// + internal enum FilterType + { + /// + /// With the None filter, the scanline is transmitted unmodified; it is only necessary to + /// insert a filter type byte before the data. + /// + None = 0, + + /// + /// The Sub filter transmits the difference between each byte and the value of the corresponding + /// byte of the prior pixel. + /// + Sub = 1, + + /// + /// The Up filter is just like the Sub filter except that the pixel immediately above the current + /// pixel, rather than just to its left, is used as the predictor. + /// + Up = 2, + + /// + /// The Average filter uses the average of the two neighboring pixels (left and above) to + /// predict the value of a pixel. + /// + Average = 3, + + /// + /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), + /// then chooses as predictor the neighboring pixel closest to the computed value. + /// This technique is due to Alan W. Paeth + /// + Paeth = 4 + } +} diff --git a/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs new file mode 100644 index 000000000..2ac590648 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// The None filter, the scanline is transmitted unmodified; it is only necessary to + /// insert a filter type byte before the data. + /// + /// + internal static class NoneFilter + { + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The + public static byte[] Decode(byte[] scanline) + { + // No change required. + return scanline; + } + + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The + public static byte[] Encode(byte[] scanline) + { + // Insert a byte before the data. + byte[] encodedScanline = new byte[scanline.Length + 1]; + encodedScanline[0] = (byte)FilterType.None; + scanline.CopyTo(encodedScanline, 1); + + return encodedScanline; + } + } +} diff --git a/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs new file mode 100644 index 000000000..1234cb6ef --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs @@ -0,0 +1,97 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + + /// + /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), + /// then chooses as predictor the neighboring pixel closest to the computed value. + /// This technique is due to Alan W. Paeth. + /// + /// + internal static class PaethFilter + { + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The previous scanline. + /// The bytes per pixel. + /// The + public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel) + { + // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) + byte[] result = new byte[scanline.Length]; + + for (int x = 1; x < scanline.Length; x++) + { + byte left = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel]; + byte above = previousScanline[x]; + byte upperLeft = (x - bytesPerPixel < 1) ? (byte)0 : previousScanline[x - bytesPerPixel]; + + result[x] = (byte)((scanline[x] + PaethPredicator(left, above, upperLeft)) % 256); + } + + return result; + } + + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The previous scanline. + /// The bytes per pixel. + /// The + public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel) + { + // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) + byte[] encodedScanline = new byte[scanline.Length + 1]; + encodedScanline[0] = (byte)FilterType.Paeth; + + for (int x = 0; x < scanline.Length; x++) + { + byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; + byte above = previousScanline[x]; + byte upperLeft = (x - bytesPerPixel < 0) ? (byte)0 : previousScanline[x - bytesPerPixel]; + + encodedScanline[x + 1] = (byte)((scanline[x] - PaethPredicator(left, above, upperLeft)) % 256); + } + + return encodedScanline; + } + + /// + /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses + /// as predictor the neighboring pixel closest to the computed value. + /// + /// The left neighbor pixel. + /// The above neighbor pixel. + /// The upper left neighbor pixel. + /// + /// The . + /// + private static byte PaethPredicator(byte left, byte above, byte upperLeft) + { + int p = left + above - upperLeft; + int pa = Math.Abs(p - left); + int pb = Math.Abs(p - above); + int pc = Math.Abs(p - upperLeft); + + if (pa <= pb && pa <= pc) + { + return left; + } + + if (pb <= pc) + { + return above; + } + + return upperLeft; + } + } +} diff --git a/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs new file mode 100644 index 000000000..eb5bd9bfd --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// The Sub filter transmits the difference between each byte and the value of the corresponding byte + /// of the prior pixel. + /// + /// + internal static class SubFilter + { + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The bytes per pixel. + /// The + public static byte[] Decode(byte[] scanline, int bytesPerPixel) + { + // Sub(x) + Raw(x-bpp) + byte[] result = new byte[scanline.Length]; + + for (int x = 1; x < scanline.Length; x++) + { + byte priorRawByte = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel]; + + result[x] = (byte)((scanline[x] + priorRawByte) % 256); + } + + return result; + } + + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The bytes per pixel. + /// The + public static byte[] Encode(byte[] scanline, int bytesPerPixel) + { + // Sub(x) = Raw(x) - Raw(x-bpp) + byte[] encodedScanline = new byte[scanline.Length + 1]; + encodedScanline[0] = (byte)FilterType.Sub; + + for (int x = 0; x < scanline.Length; x++) + { + byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; + + encodedScanline[x + 1] = (byte)((scanline[x] - priorRawByte) % 256); + } + + return encodedScanline; + } + } +} diff --git a/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs new file mode 100644 index 000000000..ebb5e3b54 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, + /// rather than just to its left, is used as the predictor. + /// + /// + internal static class UpFilter + { + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The previous scanline. + /// The + public static byte[] Decode(byte[] scanline, byte[] previousScanline) + { + // Up(x) + Prior(x) + byte[] result = new byte[scanline.Length]; + + for (int x = 1; x < scanline.Length; x++) + { + byte above = previousScanline[x]; + + result[x] = (byte)((scanline[x] + above) % 256); + } + + return result; + } + + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The previous scanline. + /// The + public static byte[] Encode(byte[] scanline, byte[] previousScanline) + { + // Up(x) = Raw(x) - Prior(x) + byte[] encodedScanline = new byte[scanline.Length + 1]; + encodedScanline[0] = (byte)FilterType.Up; + + for (int x = 0; x < scanline.Length; x++) + { + byte above = previousScanline[x]; + + encodedScanline[x + 1] = (byte)((scanline[x] - above) % 256); + } + + return encodedScanline; + } + } +} diff --git a/src/ImageSharp46/Formats/Png/PngChunk.cs b/src/ImageSharp46/Formats/Png/PngChunk.cs new file mode 100644 index 000000000..3d769057c --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngChunk.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Stores header information about a chunk. + /// + internal sealed class PngChunk + { + /// + /// Gets or sets the length. + /// An unsigned integer giving the number of bytes in the chunk's + /// data field. The length counts only the data field, not itself, + /// the chunk type code, or the CRC. Zero is a valid length + /// + public int Length { get; set; } + + /// + /// Gets or sets the chunk type as string with 4 chars. + /// + public string Type { get; set; } + + /// + /// Gets or sets the data bytes appropriate to the chunk type, if any. + /// This field can be of zero length. + /// + public byte[] Data { get; set; } + + /// + /// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, + /// including the chunk type code and chunk data fields, but not including the length field. + /// The CRC is always present, even for chunks containing no data + /// + public uint Crc { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/Png/PngChunkTypes.cs b/src/ImageSharp46/Formats/Png/PngChunkTypes.cs new file mode 100644 index 000000000..72486b50d --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngChunkTypes.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Contains a list of possible chunk type identifiers. + /// + internal static class PngChunkTypes + { + /// + /// The first chunk in a png file. Can only exists once. Contains + /// common information like the width and the height of the image or + /// the used compression method. + /// + public const string Header = "IHDR"; + + /// + /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte + /// series in the RGB format. + /// + public const string Palette = "PLTE"; + + /// + /// The IDAT chunk contains the actual image data. The image can contains more + /// than one chunk of this type. All chunks together are the whole image. + /// + public const string Data = "IDAT"; + + /// + /// This chunk must appear last. It marks the end of the PNG data stream. + /// The chunk's data field is empty. + /// + public const string End = "IEND"; + + /// + /// This chunk specifies that the image uses simple transparency: + /// either alpha values associated with palette entries (for indexed-color images) + /// or a single transparent color (for grayscale and true color images). + /// + public const string PaletteAlpha = "tRNS"; + + /// + /// Textual information that the encoder wishes to record with the image can be stored in + /// tEXt chunks. Each tEXt chunk contains a keyword and a text string. + /// + public const string Text = "tEXt"; + + /// + /// This chunk specifies the relationship between the image samples and the desired + /// display output intensity. + /// + public const string Gamma = "gAMA"; + + /// + /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. + /// + public const string Physical = "pHYs"; + } +} diff --git a/src/ImageSharp46/Formats/Png/PngColorType.cs b/src/ImageSharp46/Formats/Png/PngColorType.cs new file mode 100644 index 000000000..421e27ba3 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngColorType.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Provides enumeration of available PNG color types. + /// + public enum PngColorType : byte + { + /// + /// Each pixel is a grayscale sample. + /// + Grayscale = 0, + + /// + /// Each pixel is an R,G,B triple. + /// + Rgb = 2, + + /// + /// Each pixel is a palette index; a PLTE chunk must appear. + /// + Palette = 3, + + /// + /// Each pixel is a grayscale sample, followed by an alpha sample. + /// + GrayscaleWithAlpha = 4, + + /// + /// Each pixel is an R,G,B triple, followed by an alpha sample. + /// + RgbWithAlpha = 6 + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Png/PngDecoder.cs b/src/ImageSharp46/Formats/Png/PngDecoder.cs new file mode 100644 index 000000000..08e086a43 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngDecoder.cs @@ -0,0 +1,91 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + /// + /// Encoder for generating an image out of a png encoded stream. + /// + /// + /// At the moment the following features are supported: + /// + /// Filters: all filters are supported. + /// + /// + /// Pixel formats: + /// + /// RGBA (True color) with alpha (8 bit). + /// RGB (True color) without alpha (8 bit). + /// Grayscale with alpha (8 bit). + /// Grayscale without alpha (8 bit). + /// Palette Index with alpha (8 bit). + /// Palette Index without alpha (8 bit). + /// + /// + /// + public class PngDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 8; + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals("PNG", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + public bool IsSupportedFileFormat(byte[] header) + { + return header.Length >= 8 && + header[0] == 0x89 && + header[1] == 0x50 && // P + header[2] == 0x4E && // N + header[3] == 0x47 && // G + header[4] == 0x0D && // CR + header[5] == 0x0A && // LF + header[6] == 0x1A && // EOF + header[7] == 0x0A; // LF + } + + /// + /// Decodes the image from the specified stream to the . + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to decode to. + /// The containing image data. + public void Decode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + new PngDecoderCore().Decode(image, stream); + } + } +} diff --git a/src/ImageSharp46/Formats/Png/PngDecoderCore.cs b/src/ImageSharp46/Formats/Png/PngDecoderCore.cs new file mode 100644 index 000000000..d57ba8d4e --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngDecoderCore.cs @@ -0,0 +1,688 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + + /// + /// Performs the png decoding operation. + /// + internal class PngDecoderCore + { + /// + /// The dictionary of available color types. + /// + private static readonly Dictionary ColorTypes = new Dictionary(); + + /// + /// Reusable buffer for reading chunk types. + /// + private readonly byte[] chunkTypeBuffer = new byte[4]; + + /// + /// Reusable buffer for reading chunk lengths. + /// + private readonly byte[] chunkLengthBuffer = new byte[4]; + + /// + /// Reusable buffer for reading crc values. + /// + private readonly byte[] crcBuffer = new byte[4]; + + /// + /// Reusable buffer for reading char arrays. + /// + private readonly char[] chars = new char[4]; + + /// + /// The stream to decode from. + /// + private Stream currentStream; + + /// + /// The png header. + /// + private PngHeader header; + + /// + /// The number of bytes per pixel. + /// + private int bytesPerPixel; + + /// + /// The number of bytes per sample + /// + private int bytesPerSample; + + /// + /// The number of bytes per scanline + /// + private int bytesPerScanline; + + /// + /// The palette containing color information for indexed png's + /// + private byte[] palette; + + /// + /// The palette containing alpha channel color information for indexed png's + /// + private byte[] paletteAlpha; + + /// + /// Initializes static members of the class. + /// + static PngDecoderCore() + { + ColorTypes.Add((int)PngColorType.Grayscale, new byte[] { 1, 2, 4, 8 }); + + ColorTypes.Add((int)PngColorType.Rgb, new byte[] { 8 }); + + ColorTypes.Add((int)PngColorType.Palette, new byte[] { 1, 2, 4, 8 }); + + ColorTypes.Add((int)PngColorType.GrayscaleWithAlpha, new byte[] { 8 }); + + ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 }); + } + + /// + /// Gets or sets the png color type + /// + public PngColorType PngColorType { get; set; } + + /// + /// Decodes the stream to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to decode to. + /// The stream containing image data. + /// + /// Thrown if the stream does not contain and end chunk. + /// + /// + /// Thrown if the image is larger than the maximum allowable size. + /// + public void Decode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Image currentImage = image; + this.currentStream = stream; + this.currentStream.Seek(8, SeekOrigin.Current); + + bool isEndChunkReached = false; + + using (MemoryStream dataStream = new MemoryStream()) + { + PngChunk currentChunk; + while ((currentChunk = this.ReadChunk()) != null) + { + if (isEndChunkReached) + { + throw new ImageFormatException("Image does not end with end chunk."); + } + + if (currentChunk.Type == PngChunkTypes.Header) + { + this.ReadHeaderChunk(currentChunk.Data); + this.ValidateHeader(); + } + else if (currentChunk.Type == PngChunkTypes.Physical) + { + this.ReadPhysicalChunk(currentImage, currentChunk.Data); + } + else if (currentChunk.Type == PngChunkTypes.Data) + { + dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length); + } + else if (currentChunk.Type == PngChunkTypes.Palette) + { + this.palette = currentChunk.Data; + image.Quality = this.palette.Length / 3; + } + else if (currentChunk.Type == PngChunkTypes.PaletteAlpha) + { + this.paletteAlpha = currentChunk.Data; + } + else if (currentChunk.Type == PngChunkTypes.Text) + { + this.ReadTextChunk(currentImage, currentChunk.Data); + } + else if (currentChunk.Type == PngChunkTypes.End) + { + isEndChunkReached = true; + } + } + + if (this.header.Width > image.MaxWidth || this.header.Height > image.MaxHeight) + { + throw new ArgumentOutOfRangeException( + $"The input png '{this.header.Width}x{this.header.Height}' is bigger than the " + + $"max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); + } + + image.InitPixels(this.header.Width, this.header.Height); + + using (PixelAccessor pixels = image.Lock()) + { + this.ReadScanlines(dataStream, pixels); + } + } + } + + /// + /// Reads the data chunk containing physical dimension data. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to read to. + /// The data containing physical data. + private void ReadPhysicalChunk(Image image, byte[] data) + where TColor : struct, IPackedPixel + where TPacked : struct + { + this.ReverseBytes(data, 0, 4); + this.ReverseBytes(data, 4, 4); + + // 39.3700787 = inches in a meter. + image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; + image.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; + } + + /// + /// Calculates the correct number of bytes per pixel for the given color type. + /// + /// The + private int CalculateBytesPerPixel() + { + switch (this.PngColorType) + { + case PngColorType.Grayscale: + return 1; + + case PngColorType.GrayscaleWithAlpha: + return 2; + + case PngColorType.Palette: + return 1; + + case PngColorType.Rgb: + return 3; + + // PngColorType.RgbWithAlpha + // TODO: Maybe figure out a way to detect if there are any transparent pixels and encode RGB if none. + default: + return 4; + } + } + + /// + /// Calculates the scanline length. + /// + /// The representing the length. + private int CalculateScanlineLength() + { + int scanlineLength = this.header.Width * this.header.BitDepth * this.bytesPerPixel; + + int amount = scanlineLength % 8; + if (amount != 0) + { + scanlineLength += 8 - amount; + } + + return scanlineLength / 8; + } + + /// + /// Reads the scanlines within the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The containing data. + /// The pixel data. + private void ReadScanlines(MemoryStream dataStream, PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + this.bytesPerPixel = this.CalculateBytesPerPixel(); + this.bytesPerScanline = this.CalculateScanlineLength() + 1; + this.bytesPerSample = 1; + if (this.header.BitDepth >= 8) + { + this.bytesPerSample = this.header.BitDepth / 8; + } + + dataStream.Position = 0; + using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) + { + using (MemoryStream decompressedStream = new MemoryStream()) + { + compressedStream.CopyTo(decompressedStream); + decompressedStream.Flush(); + decompressedStream.Position = 0; + this.DecodePixelData(decompressedStream, pixels); + //byte[] decompressedBytes = decompressedStream.ToArray(); + //this.DecodePixelData(decompressedBytes, pixels); + } + + //byte[] decompressedBytes = compressedStream.ToArray(); + //this.DecodePixelData(decompressedBytes, pixels); + } + } + + /// + /// Decodes the raw pixel data row by row + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The pixel data. + /// The image pixels. + private void DecodePixelData(Stream pixelData, PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // TODO: ArrayPool.Shared.Rent(this.bytesPerScanline) + byte[] previousScanline = new byte[this.bytesPerScanline]; + byte[] scanline = new byte[this.bytesPerScanline]; + for (int y = 0; y < this.header.Height; y++) + { + pixelData.Read(scanline, 0, this.bytesPerScanline); + + FilterType filterType = (FilterType)scanline[0]; + byte[] defilteredScanline; + + switch (filterType) + { + case FilterType.None: + + defilteredScanline = NoneFilter.Decode(scanline); + + break; + + case FilterType.Sub: + + defilteredScanline = SubFilter.Decode(scanline, this.bytesPerPixel); + + break; + + case FilterType.Up: + + defilteredScanline = UpFilter.Decode(scanline, previousScanline); + + break; + + case FilterType.Average: + + defilteredScanline = AverageFilter.Decode(scanline, previousScanline, this.bytesPerPixel); + + break; + + case FilterType.Paeth: + + defilteredScanline = PaethFilter.Decode(scanline, previousScanline, this.bytesPerPixel); + + break; + + default: + throw new ImageFormatException("Unknown filter type."); + } + + previousScanline = defilteredScanline; + this.ProcessDefilteredScanline(defilteredScanline, y, pixels); + } + } + + /// + /// Processes the de-filtered scanline filling the image pixel data + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The de-filtered scanline + /// The current image row. + /// The image pixels + private void ProcessDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + TColor color = default(TColor); + switch (this.PngColorType) + { + case PngColorType.Grayscale: + + for (int x = 0; x < this.header.Width; x++) + { + int offset = 1 + (x * this.bytesPerPixel); + + byte intensity = defilteredScanline[offset]; + color.PackFromBytes(intensity, intensity, intensity, 255); + pixels[x, row] = color; + } + + break; + + case PngColorType.GrayscaleWithAlpha: + + for (int x = 0; x < this.header.Width; x++) + { + int offset = 1 + (x * this.bytesPerPixel); + + byte intensity = defilteredScanline[offset]; + byte alpha = defilteredScanline[offset + this.bytesPerSample]; + + color.PackFromBytes(intensity, intensity, intensity, alpha); + pixels[x, row] = color; + } + + break; + + case PngColorType.Palette: + + byte[] newScanline = defilteredScanline.ToArrayByBitsLength(this.header.BitDepth); + + if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) + { + // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha + // channel and we should try to read it. + for (int x = 0; x < this.header.Width; x++) + { + int index = newScanline[x]; + int pixelOffset = index * 3; + + byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; + + if (a > 0) + { + byte r = this.palette[pixelOffset]; + byte g = this.palette[pixelOffset + 1]; + byte b = this.palette[pixelOffset + 2]; + color.PackFromBytes(r, g, b, a); + } + + pixels[x, row] = color; + } + } + else + { + for (int x = 0; x < this.header.Width; x++) + { + int index = newScanline[x]; + int pixelOffset = index * 3; + + byte r = this.palette[pixelOffset]; + byte g = this.palette[pixelOffset + 1]; + byte b = this.palette[pixelOffset + 2]; + + color.PackFromBytes(r, g, b, 255); + pixels[x, row] = color; + } + } + + break; + + case PngColorType.Rgb: + + for (int x = 0; x < this.header.Width; x++) + { + int offset = 1 + (x * this.bytesPerPixel); + + byte r = defilteredScanline[offset]; + byte g = defilteredScanline[offset + this.bytesPerSample]; + byte b = defilteredScanline[offset + (2 * this.bytesPerSample)]; + + color.PackFromBytes(r, g, b, 255); + pixels[x, row] = color; + } + + break; + + case PngColorType.RgbWithAlpha: + + for (int x = 0; x < this.header.Width; x++) + { + int offset = 1 + (x * this.bytesPerPixel); + + byte r = defilteredScanline[offset]; + byte g = defilteredScanline[offset + this.bytesPerSample]; + byte b = defilteredScanline[offset + (2 * this.bytesPerSample)]; + byte a = defilteredScanline[offset + (3 * this.bytesPerSample)]; + + color.PackFromBytes(r, g, b, a); + pixels[x, row] = color; + } + + break; + } + } + + /// + /// Reads a text chunk containing image properties from the data. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to decode to. + /// The containing data. + private void ReadTextChunk(Image image, byte[] data) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int zeroIndex = 0; + + for (int i = 0; i < data.Length; i++) + { + if (data[i] == 0) + { + zeroIndex = i; + break; + } + } + + string name = Encoding.Unicode.GetString(data, 0, zeroIndex); + string value = Encoding.Unicode.GetString(data, zeroIndex + 1, data.Length - zeroIndex - 1); + + image.Properties.Add(new ImageProperty(name, value)); + } + + /// + /// Reads a header chunk from the data. + /// + /// The containing data. + private void ReadHeaderChunk(byte[] data) + { + this.header = new PngHeader(); + + this.ReverseBytes(data, 0, 4); + this.ReverseBytes(data, 4, 4); + + this.header.Width = BitConverter.ToInt32(data, 0); + this.header.Height = BitConverter.ToInt32(data, 4); + + this.header.BitDepth = data[8]; + this.header.ColorType = data[9]; + this.header.CompressionMethod = data[10]; + this.header.FilterMethod = data[11]; + this.header.InterlaceMethod = data[12]; + } + + /// + /// Validates the png header. + /// + /// + /// Thrown if the image does pass validation. + /// + private void ValidateHeader() + { + if (!ColorTypes.ContainsKey(this.header.ColorType)) + { + throw new NotSupportedException("Color type is not supported or not valid."); + } + + if (!ColorTypes[this.header.ColorType].Contains(this.header.BitDepth)) + { + throw new NotSupportedException("Bit depth is not supported or not valid."); + } + + if (this.header.FilterMethod != 0) + { + throw new NotSupportedException("The png specification only defines 0 as filter method."); + } + + if (this.header.InterlaceMethod != 0) + { + // TODO: Support interlacing + throw new NotSupportedException("Interlacing is not supported."); + } + + this.PngColorType = (PngColorType)this.header.ColorType; + } + + /// + /// Reads a chunk from the stream. + /// + /// + /// The . + /// + private PngChunk ReadChunk() + { + PngChunk chunk = new PngChunk(); + + if (this.ReadChunkLength(chunk) == 0) + { + return null; + } + + if (chunk.Length <= 0) + { + return null; + } + + this.ReadChunkType(chunk); + this.ReadChunkData(chunk); + this.ReadChunkCrc(chunk); + + return chunk; + } + + /// + /// Reads the cycle redundancy chunk from the data. + /// + /// The chunk. + /// + /// Thrown if the input stream is not valid or corrupt. + /// + private void ReadChunkCrc(PngChunk chunk) + { + int numBytes = this.currentStream.Read(this.crcBuffer, 0, 4); + if (numBytes >= 1 && numBytes <= 3) + { + throw new ImageFormatException("Image stream is not valid!"); + } + + this.ReverseBytes(this.crcBuffer); + + chunk.Crc = BitConverter.ToUInt32(this.crcBuffer, 0); + + Crc32 crc = new Crc32(); + crc.Update(this.chunkTypeBuffer); + crc.Update(chunk.Data); + + if (crc.Value != chunk.Crc) + { + throw new ImageFormatException("CRC Error. PNG Image chunk is corrupt!"); + } + } + + /// + /// Reads the chunk data from the stream. + /// + /// The chunk. + private void ReadChunkData(PngChunk chunk) + { + chunk.Data = new byte[chunk.Length]; + this.currentStream.Read(chunk.Data, 0, chunk.Length); + } + + /// + /// Identifies the chunk type from the chunk. + /// + /// The chunk. + /// + /// Thrown if the input stream is not valid. + /// + private void ReadChunkType(PngChunk chunk) + { + int numBytes = this.currentStream.Read(this.chunkTypeBuffer, 0, 4); + if (numBytes >= 1 && numBytes <= 3) + { + throw new ImageFormatException("Image stream is not valid!"); + } + + this.chars[0] = (char)this.chunkTypeBuffer[0]; + this.chars[1] = (char)this.chunkTypeBuffer[1]; + this.chars[2] = (char)this.chunkTypeBuffer[2]; + this.chars[3] = (char)this.chunkTypeBuffer[3]; + + chunk.Type = new string(this.chars); + } + + /// + /// Calculates the length of the given chunk. + /// + /// he chunk. + /// + /// The representing the chunk length. + /// + /// + /// Thrown if the input stream is not valid. + /// + private int ReadChunkLength(PngChunk chunk) + { + int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4); + if (numBytes >= 1 && numBytes <= 3) + { + throw new ImageFormatException("Image stream is not valid!"); + } + + this.ReverseBytes(this.chunkLengthBuffer); + + chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0); + + return numBytes; + } + + /// + /// Optimized reversal algorithm. + /// + /// The byte array. + private void ReverseBytes(byte[] source) + { + this.ReverseBytes(source, 0, source.Length); + } + + /// + /// Optimized reversal algorithm. + /// + /// The byte array. + /// The index. + /// The length. + private void ReverseBytes(byte[] source, int index, int length) + { + int i = index; + int j = index + length - 1; + while (i < j) + { + byte temp = source[i]; + source[i] = source[j]; + source[j] = temp; + i++; + j--; + } + } + } +} diff --git a/src/ImageSharp46/Formats/Png/PngEncoder.cs b/src/ImageSharp46/Formats/Png/PngEncoder.cs new file mode 100644 index 000000000..af4fe95d6 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngEncoder.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + + using ImageSharp.Quantizers; + + /// + /// Image encoder for writing image data to a stream in png format. + /// + public class PngEncoder : IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the png color type + /// + public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; + + /// + public string MimeType => "image/png"; + + /// + public string Extension => "png"; + + /// + /// Gets or sets the compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; + + /// + /// Gets or sets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + public float Gamma { get; set; } = 2.2F; + + /// + /// Gets or sets quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 0; + + /// + /// Gets or sets a value indicating whether this instance should write + /// gamma information to the stream. The default value is false. + /// + public bool WriteGamma { get; set; } + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(Image image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + PngEncoderCore encoder = new PngEncoderCore + { + CompressionLevel = this.CompressionLevel, + Gamma = this.Gamma, + Quality = this.Quality, + PngColorType = this.PngColorType, + Quantizer = this.Quantizer, + WriteGamma = this.WriteGamma, + Threshold = this.Threshold + }; + + encoder.Encode(image, stream); + } + } +} diff --git a/src/ImageSharp46/Formats/Png/PngEncoderCore.cs b/src/ImageSharp46/Formats/Png/PngEncoderCore.cs new file mode 100644 index 000000000..03f74d5a7 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngEncoderCore.cs @@ -0,0 +1,707 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + using Quantizers; + + /// + /// Performs the png encoding operation. + /// TODO: Perf. There's lots of array parsing and copying going on here. This should be unmanaged. + /// + internal sealed class PngEncoderCore + { + /// + /// The maximum block size, defaults at 64k for uncompressed blocks. + /// + private const int MaxBlockSize = 65535; + + /// + /// Contains the raw pixel data from the image. + /// + private byte[] pixelData; + + /// + /// The image width. + /// + private int width; + + /// + /// The image height. + /// + private int height; + + /// + /// The number of bits required to encode the colors in the png. + /// + private byte bitDepth; + + /// + /// The number of bytes per pixel. + /// + private int bytesPerPixel; + + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the png color type + /// + public PngColorType PngColorType { get; set; } + + /// + /// Gets or sets the compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; + + /// + /// Gets or sets a value indicating whether this instance should write + /// gamma information to the stream. The default value is false. + /// + public bool WriteGamma { get; set; } + + /// + /// Gets or sets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + public float Gamma { get; set; } = 2.2F; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The to encode from. + /// The to encode the image data to. + public void Encode(ImageBase image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.width = image.Width; + this.height = image.Height; + + // Write the png header. + stream.Write( + new byte[] + { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }, + 0, + 8); + + // Ensure that quality can be set but has a fallback. + int quality = this.Quality > 0 ? this.Quality : image.Quality; + this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; + + // Set correct color type if the color count is 256 or less. + if (this.Quality <= 256) + { + this.PngColorType = PngColorType.Palette; + } + + // Set correct bit depth. + this.bitDepth = this.Quality <= 256 + ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) + : (byte)8; + + // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk + if (this.bitDepth == 3) + { + this.bitDepth = 4; + } + else if (this.bitDepth >= 5 || this.bitDepth <= 7) + { + this.bitDepth = 8; + } + + this.bytesPerPixel = this.CalculateBytesPerPixel(); + + PngHeader header = new PngHeader + { + Width = image.Width, + Height = image.Height, + ColorType = (byte)this.PngColorType, + BitDepth = this.bitDepth, + FilterMethod = 0, // None + CompressionMethod = 0, + InterlaceMethod = 0 + }; + + this.WriteHeaderChunk(stream, header); + + // Collect the pixel data + if (this.PngColorType == PngColorType.Palette) + { + this.CollectIndexedBytes(image, stream, header); + } + else if (this.PngColorType == PngColorType.Grayscale || this.PngColorType == PngColorType.GrayscaleWithAlpha) + { + this.CollectGrayscaleBytes(image); + } + else + { + this.CollectColorBytes(image); + } + + this.WritePhysicalChunk(stream, image); + this.WriteGammaChunk(stream); + this.WriteDataChunks(stream); + this.WriteEndChunk(stream); + stream.Flush(); + } + + /// + /// Writes an integer to the byte array. + /// + /// The containing image data. + /// The amount to offset by. + /// The value to write. + private static void WriteInteger(byte[] data, int offset, int value) + { + byte[] buffer = BitConverter.GetBytes(value); + + Array.Reverse(buffer); + Array.Copy(buffer, 0, data, offset, 4); + } + + /// + /// Writes an integer to the stream. + /// + /// The containing image data. + /// The value to write. + private static void WriteInteger(Stream stream, int value) + { + byte[] buffer = BitConverter.GetBytes(value); + + Array.Reverse(buffer); + + stream.Write(buffer, 0, 4); + } + + /// + /// Writes an unsigned integer to the stream. + /// + /// The containing image data. + /// The value to write. + private static void WriteInteger(Stream stream, uint value) + { + byte[] buffer = BitConverter.GetBytes(value); + + Array.Reverse(buffer); + + stream.Write(buffer, 0, 4); + } + + /// + /// Collects the indexed pixel data. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to encode. + /// The containing image data. + /// The . + private void CollectIndexedBytes(ImageBase image, Stream stream, PngHeader header) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // Quatize the image and get the pixels + QuantizedImage quantized = this.WritePaletteChunk(stream, header, image); + this.pixelData = quantized.Pixels; + } + + /// + /// Collects the grayscale pixel data. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to encode. + private void CollectGrayscaleBytes(ImageBase image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // Copy the pixels across from the image. + this.pixelData = new byte[this.width * this.height * this.bytesPerPixel]; + int stride = this.width * this.bytesPerPixel; + byte[] bytes = new byte[4]; + using (PixelAccessor pixels = image.Lock()) + { + for (int y = 0; y < this.height; y++) + { + for (int x = 0; x < this.width; x++) + { + // Convert the color to YCbCr and store the luminance + // Optionally store the original color alpha. + int dataOffset = (y * stride) + (x * this.bytesPerPixel); + pixels[x, y].ToBytes(bytes, 0, ComponentOrder.XYZW); + YCbCr luminance = new Color(bytes[0], bytes[1], bytes[2], bytes[3]); + for (int i = 0; i < this.bytesPerPixel; i++) + { + if (i == 0) + { + this.pixelData[dataOffset] = (byte)luminance.Y; + } + else + { + this.pixelData[dataOffset + i] = bytes[3]; + } + } + } + } + } + } + + /// + /// Collects the true color pixel data. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to encode. + private void CollectColorBytes(ImageBase image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // Copy the pixels across from the image. + // TODO: This could be sped up more if we add a method to PixelAccessor that does this by row directly to a byte array. + this.pixelData = new byte[this.width * this.height * this.bytesPerPixel]; + int stride = this.width * this.bytesPerPixel; + using (PixelAccessor pixels = image.Lock()) + { + int bpp = this.bytesPerPixel; + Parallel.For( + 0, + this.height, + Bootstrapper.Instance.ParallelOptions, + y => + { + for (int x = 0; x < this.width; x++) + { + int dataOffset = (y * stride) + (x * this.bytesPerPixel); + pixels[x, y].ToBytes(this.pixelData, dataOffset, bpp == 4 ? ComponentOrder.XYZW : ComponentOrder.XYZ); + } + }); + } + } + + /// + /// Encodes the pixel data line by line. + /// Each scanline is encoded in the most optimal manner to improve compression. + /// + /// The + private byte[] EncodePixelData() + { + // TODO: Use pointers + List filteredScanlines = new List(); + + byte[] previousScanline = new byte[this.width * this.bytesPerPixel]; + + for (int y = 0; y < this.height; y++) + { + byte[] rawScanline = this.GetRawScanline(y); + byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, this.bytesPerPixel); + + filteredScanlines.Add(filteredScanline); + + previousScanline = rawScanline; + } + + // TODO: We should be able to use a byte array when not using interlaced encoding. + List result = new List(); + + foreach (byte[] encodedScanline in filteredScanlines) + { + result.AddRange(encodedScanline); + } + + return result.ToArray(); + } + + /// + /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed + /// to be most compressible, using lowest total variation as proxy for compressibility. + /// + /// The raw scanline + /// The previous scanline + /// The number of bytes per pixel + /// The + private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int byteCount) + { + List> candidates = new List>(); + + if (this.PngColorType == PngColorType.Palette) + { + byte[] none = NoneFilter.Encode(rawScanline); + candidates.Add(new Tuple(none, this.CalculateTotalVariation(none))); + } + else + { + byte[] sub = SubFilter.Encode(rawScanline, byteCount); + candidates.Add(new Tuple(sub, this.CalculateTotalVariation(sub))); + + byte[] up = UpFilter.Encode(rawScanline, previousScanline); + candidates.Add(new Tuple(up, this.CalculateTotalVariation(up))); + + byte[] average = AverageFilter.Encode(rawScanline, previousScanline, byteCount); + candidates.Add(new Tuple(average, this.CalculateTotalVariation(average))); + + byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, byteCount); + candidates.Add(new Tuple(paeth, this.CalculateTotalVariation(paeth))); + } + + int lowestTotalVariation = int.MaxValue; + int lowestTotalVariationIndex = 0; + + for (int i = 0; i < candidates.Count; i++) + { + if (candidates[i].Item2 < lowestTotalVariation) + { + lowestTotalVariationIndex = i; + lowestTotalVariation = candidates[i].Item2; + } + } + + return candidates[lowestTotalVariationIndex].Item1; + } + + /// + /// Calculates the total variation of given byte array. Total variation is the sum of the absolute values of + /// neighbor differences. + /// + /// The scanline bytes + /// The + private int CalculateTotalVariation(byte[] input) + { + int totalVariation = 0; + + for (int i = 1; i < input.Length; i++) + { + totalVariation += Math.Abs(input[i] - input[i - 1]); + } + + return totalVariation; + } + + /// + /// Get the raw scanline data from the pixel data + /// + /// The row number + /// The + private byte[] GetRawScanline(int y) + { + int stride = this.bytesPerPixel * this.width; + byte[] rawScanline = new byte[stride]; + Array.Copy(this.pixelData, y * stride, rawScanline, 0, stride); + return rawScanline; + } + + /// + /// Calculates the correct number of bytes per pixel for the given color type. + /// + /// The + private int CalculateBytesPerPixel() + { + switch (this.PngColorType) + { + case PngColorType.Grayscale: + return 1; + + case PngColorType.GrayscaleWithAlpha: + return 2; + + case PngColorType.Palette: + return 1; + + case PngColorType.Rgb: + return 3; + + // PngColorType.RgbWithAlpha + // TODO: Maybe figure out a way to detect if there are any transparent + // pixels and encode RGB if none. + default: + return 4; + } + } + + /// + /// Writes the header chunk to the stream. + /// + /// The containing image data. + /// The . + private void WriteHeaderChunk(Stream stream, PngHeader header) + { + byte[] chunkData = new byte[13]; + + WriteInteger(chunkData, 0, header.Width); + WriteInteger(chunkData, 4, header.Height); + + chunkData[8] = header.BitDepth; + chunkData[9] = header.ColorType; + chunkData[10] = header.CompressionMethod; + chunkData[11] = header.FilterMethod; + chunkData[12] = header.InterlaceMethod; + + this.WriteChunk(stream, PngChunkTypes.Header, chunkData); + } + + /// + /// Writes the palette chunk to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The containing image data. + /// The . + /// The image to encode. + /// The + private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + if (this.Quality > 256) + { + return null; + } + + if (this.Quantizer == null) + { + this.Quantizer = new WuQuantizer(); + } + + // Quantize the image returning a palette. This boxing is icky. + QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + + // Grab the palette and write it to the stream. + TColor[] palette = quantized.Palette; + int pixelCount = palette.Length; + List transparentPixels = new List(); + + // Get max colors for bit depth. + int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; + byte[] colorTable = new byte[colorTableLength]; + + // TODO: Optimize this. + Parallel.For( + 0, + pixelCount, + Bootstrapper.Instance.ParallelOptions, + i => + { + int offset = i * 3; + Color color = new Color(palette[i].ToVector4()); + int alpha = color.A; + + // Premultiply the color. This helps prevent banding. + if (alpha < 255 && alpha > this.Threshold) + { + color = Color.Multiply(color, new Color(alpha, alpha, alpha, 255)); + } + + colorTable[offset] = color.R; + colorTable[offset + 1] = color.G; + colorTable[offset + 2] = color.B; + + if (alpha <= this.Threshold) + { + transparentPixels.Add((byte)offset); + } + }); + + this.WriteChunk(stream, PngChunkTypes.Palette, colorTable); + + // Write the transparency data + if (transparentPixels.Any()) + { + this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray()); + } + + return quantized; + } + + /// + /// Writes the physical dimension information to the stream. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The containing image data. + /// The image base. + private void WritePhysicalChunk(Stream stream, ImageBase imageBase) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Image image = imageBase as Image; + if (image != null && image.HorizontalResolution > 0 && image.VerticalResolution > 0) + { + // 39.3700787 = inches in a meter. + int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787D); + int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787D); + + byte[] chunkData = new byte[9]; + + WriteInteger(chunkData, 0, dpmX); + WriteInteger(chunkData, 4, dpmY); + + chunkData[8] = 1; + + this.WriteChunk(stream, PngChunkTypes.Physical, chunkData); + } + } + + /// + /// Writes the gamma information to the stream. + /// + /// The containing image data. + private void WriteGammaChunk(Stream stream) + { + if (this.WriteGamma) + { + int gammaValue = (int)(this.Gamma * 100000f); + + byte[] fourByteData = new byte[4]; + + byte[] size = BitConverter.GetBytes(gammaValue); + + fourByteData[0] = size[3]; + fourByteData[1] = size[2]; + fourByteData[2] = size[1]; + fourByteData[3] = size[0]; + + this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData); + } + } + + /// + /// Writes the pixel information to the stream. + /// + /// The stream. + private void WriteDataChunks(Stream stream) + { + byte[] data = this.EncodePixelData(); + + byte[] buffer; + int bufferLength; + + MemoryStream memoryStream = null; + try + { + memoryStream = new MemoryStream(); + + using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) + { + deflateStream.Write(data, 0, data.Length); + } + + bufferLength = (int)memoryStream.Length; + buffer = memoryStream.ToArray(); + } + finally + { + memoryStream?.Dispose(); + } + + int numChunks = bufferLength / MaxBlockSize; + + if (bufferLength % MaxBlockSize != 0) + { + numChunks++; + } + + for (int i = 0; i < numChunks; i++) + { + int length = bufferLength - (i * MaxBlockSize); + + if (length > MaxBlockSize) + { + length = MaxBlockSize; + } + + this.WriteChunk(stream, PngChunkTypes.Data, buffer, i * MaxBlockSize, length); + } + } + + /// + /// Writes the chunk end to the stream. + /// + /// The containing image data. + private void WriteEndChunk(Stream stream) + { + this.WriteChunk(stream, PngChunkTypes.End, null); + } + + /// + /// Writes a chunk to the stream. + /// + /// The to write to. + /// The type of chunk to write. + /// The containing data. + private void WriteChunk(Stream stream, string type, byte[] data) + { + this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); + } + + /// + /// Writes a chunk of a specified length to the stream at the given offset. + /// + /// The to write to. + /// The type of chunk to write. + /// The containing data. + /// The position to offset the data at. + /// The of the data to write. + private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length) + { + WriteInteger(stream, length); + + byte[] typeArray = new byte[4]; + typeArray[0] = (byte)type[0]; + typeArray[1] = (byte)type[1]; + typeArray[2] = (byte)type[2]; + typeArray[3] = (byte)type[3]; + + stream.Write(typeArray, 0, 4); + + if (data != null) + { + stream.Write(data, offset, length); + } + + Crc32 crc32 = new Crc32(); + crc32.Update(typeArray); + + if (data != null) + { + crc32.Update(data, offset, length); + } + + WriteInteger(stream, (uint)crc32.Value); + } + } +} diff --git a/src/ImageSharp46/Formats/Png/PngFormat.cs b/src/ImageSharp46/Formats/Png/PngFormat.cs new file mode 100644 index 000000000..230ba8b40 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the means to encode and decode png images. + /// + public class PngFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new PngDecoder(); + + /// + public IImageEncoder Encoder => new PngEncoder(); + } +} diff --git a/src/ImageSharp46/Formats/Png/PngHeader.cs b/src/ImageSharp46/Formats/Png/PngHeader.cs new file mode 100644 index 000000000..3121969b6 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/PngHeader.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Represents the png header chunk. + /// + public sealed class PngHeader + { + /// + /// Gets or sets the dimension in x-direction of the image in pixels. + /// + public int Width { get; set; } + + /// + /// Gets or sets the dimension in y-direction of the image in pixels. + /// + public int Height { get; set; } + + /// + /// Gets or sets the bit depth. + /// Bit depth is a single-byte integer giving the number of bits per sample + /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, + /// although not all values are allowed for all color types. + /// + public byte BitDepth { get; set; } + + /// + /// Gets or sets the color type. + /// Color type is a integer that describes the interpretation of the + /// image data. Color type codes represent sums of the following values: + /// 1 (palette used), 2 (color used), and 4 (alpha channel used). + /// + public byte ColorType { get; set; } + + /// + /// Gets or sets the compression method. + /// Indicates the method used to compress the image data. At present, + /// only compression method 0 (deflate/inflate compression with a sliding + /// window of at most 32768 bytes) is defined. + /// + public byte CompressionMethod { get; set; } + + /// + /// Gets or sets the preprocessing method. + /// Indicates the preprocessing method applied to the image + /// data before compression. At present, only filter method 0 + /// (adaptive filtering with five basic filter types) is defined. + /// + public byte FilterMethod { get; set; } + + /// + /// Gets or sets the transmission order. + /// Indicates the transmission order of the image data. + /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). + /// + public byte InterlaceMethod { get; set; } + } +} diff --git a/src/ImageSharp46/Formats/Png/README.md b/src/ImageSharp46/Formats/Png/README.md new file mode 100644 index 000000000..8ade37956 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/README.md @@ -0,0 +1,6 @@ +Encoder/Decoder adapted from: + +https://github.com/yufeih/Nine.Imaging/ +https://imagetools.codeplex.com/ +https://github.com/leonbloy/pngcs + diff --git a/src/ImageSharp46/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp46/Formats/Png/Zlib/Adler32.cs new file mode 100644 index 000000000..5f92cc9e0 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Zlib/Adler32.cs @@ -0,0 +1,174 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + + /// + /// Computes Adler32 checksum for a stream of data. An Adler32 + /// checksum is not as reliable as a CRC32 checksum, but a lot faster to + /// compute. + /// + /// + /// The specification for Adler32 may be found in RFC 1950. + /// ZLIB Compressed Data Format Specification version 3.3) + /// + /// + /// From that document: + /// + /// "ADLER32 (Adler-32 checksum) + /// This contains a checksum value of the uncompressed data + /// (excluding any dictionary data) computed according to Adler-32 + /// algorithm. This algorithm is a 32-bit extension and improvement + /// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 + /// standard. + /// + /// Adler-32 is composed of two sums accumulated per byte: s1 is + /// the sum of all bytes, s2 is the sum of all s1 values. Both sums + /// are done modulo 65521. s1 is initialized to 1, s2 to zero. The + /// Adler-32 checksum is stored as s2*65536 + s1 in most- + /// significant-byte first (network) order." + /// + /// "8.2. The Adler-32 algorithm + /// + /// The Adler-32 algorithm is much faster than the CRC32 algorithm yet + /// still provides an extremely low probability of undetected errors. + /// + /// The modulo on unsigned long accumulators can be delayed for 5552 + /// bytes, so the modulo operation time is negligible. If the bytes + /// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position + /// and order sensitive, unlike the first sum, which is just a + /// checksum. That 65521 is prime is important to avoid a possible + /// large class of two-byte errors that leave the check unchanged. + /// (The Fletcher checksum uses 255, which is not prime and which also + /// makes the Fletcher check insensitive to single byte changes 0 - + /// 255.) + /// + /// The sum s1 is initialized to 1 instead of zero to make the length + /// of the sequence part of s2, so that the length does not have to be + /// checked separately. (Any sequence of zeroes has a Fletcher + /// checksum of zero.)" + /// + /// + /// + internal sealed class Adler32 : IChecksum + { + /// + /// largest prime smaller than 65536 + /// + private const uint Base = 65521; + + /// + /// The checksum calculated to far. + /// + private uint checksum; + + /// + /// Initializes a new instance of the class. + /// The checksum starts off with a value of 1. + /// + public Adler32() + { + this.Reset(); + } + + /// + public long Value => this.checksum; + + /// + public void Reset() + { + this.checksum = 1; + } + + /// + /// Updates the checksum with a byte value. + /// + /// + /// The data value to add. The high byte of the int is ignored. + /// + public void Update(int value) + { + // We could make a length 1 byte array and call update again, but I + // would rather not have that overhead + uint s1 = this.checksum & 0xFFFF; + uint s2 = this.checksum >> 16; + + s1 = (s1 + ((uint)value & 0xFF)) % Base; + s2 = (s1 + s2) % Base; + + this.checksum = (s2 << 16) + s1; + } + + /// + public void Update(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + this.Update(buffer, 0, buffer.Length); + } + + /// + public void Update(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "cannot be negative"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "cannot be negative"); + } + + if (offset >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset), "not a valid index into buffer"); + } + + if (offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); + } + + // (By Per Bothner) + uint s1 = this.checksum & 0xFFFF; + uint s2 = this.checksum >> 16; + + while (count > 0) + { + // We can defer the modulo operation: + // s1 maximally grows from 65521 to 65521 + 255 * 3800 + // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 + int n = 3800; + if (n > count) + { + n = count; + } + + count -= n; + while (--n >= 0) + { + s1 = s1 + (uint)(buffer[offset++] & 0xff); + s2 = s2 + s1; + } + + s1 %= Base; + s2 %= Base; + } + + this.checksum = (s2 << 16) | s1; + } + } +} diff --git a/src/ImageSharp46/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp46/Formats/Png/Zlib/Crc32.cs new file mode 100644 index 000000000..d940112a8 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Zlib/Crc32.cs @@ -0,0 +1,180 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + + /// + /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + /// + /// + /// + /// Polynomials over GF(2) are represented in binary, one bit per coefficient, + /// with the lowest powers in the most significant bit. Then adding polynomials + /// is just exclusive-or, and multiplying a polynomial by x is a right shift by + /// one. If we call the above polynomial p, and represent a byte as the + /// polynomial q, also with the lowest power in the most significant bit (so the + /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + /// where a mod b means the remainder after dividing a by b. + /// + /// + /// This calculation is done using the shift-register method of multiplying and + /// taking the remainder. The register is initialized to zero, and for each + /// incoming bit, x^32 is added mod p to the register if the bit is a one (where + /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + /// x (which is shifting right by one and adding x^32 mod p if the bit shifted + /// out is a one). We start with the highest power (least significant bit) of + /// q and repeat for all eight bits of q. + /// + /// + /// The table is simply the CRC of all possible eight bit values. This is all + /// the information needed to generate CRC's on data a byte at a time for all + /// combinations of CRC register values and incoming bytes. + /// + /// + internal sealed class Crc32 : IChecksum + { + /// + /// The cycle redundancy check seed + /// + private const uint CrcSeed = 0xFFFFFFFF; + + /// + /// The table of all possible eight bit values for fast lookup. + /// + private static readonly uint[] CrcTable = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, + 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, + 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, + 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, + 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, + 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, + 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, + 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, + 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, + 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, + 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, + 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, + 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, + 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, + 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, + 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, + 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, + 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, + 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, + 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, + 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, + 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, + 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, + 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, + 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, + 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, + 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, + 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, + 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, + 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, + 0x2D02EF8D + }; + + /// + /// The data checksum so far. + /// + private uint crc; + + /// + public long Value + { + get + { + return this.crc; + } + + set + { + this.crc = (uint)value; + } + } + + /// + public void Reset() + { + this.crc = 0; + } + + /// + /// Updates the checksum with the given value. + /// + /// The byte is taken as the lower 8 bits of value. + public void Update(int value) + { + this.crc ^= CrcSeed; + this.crc = CrcTable[(this.crc ^ value) & 0xFF] ^ (this.crc >> 8); + this.crc ^= CrcSeed; + } + + /// + public void Update(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + this.Update(buffer, 0, buffer.Length); + } + + /// + public void Update(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be less than zero"); + } + + if (offset < 0 || offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + this.crc ^= CrcSeed; + + while (--count >= 0) + { + this.crc = CrcTable[(this.crc ^ buffer[offset++]) & 0xFF] ^ (this.crc >> 8); + } + + this.crc ^= CrcSeed; + } + } +} diff --git a/src/ImageSharp46/Formats/Png/Zlib/IChecksum.cs b/src/ImageSharp46/Formats/Png/Zlib/IChecksum.cs new file mode 100644 index 000000000..935cdf953 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Zlib/IChecksum.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Interface to compute a data checksum used by checked input/output streams. + /// A data checksum can be updated by one byte or with a byte array. After each + /// update the value of the current checksum can be returned by calling + /// Value. The complete checksum object can also be reset + /// so it can be used again with new data. + /// + public interface IChecksum + { + /// + /// Gets the data checksum computed so far. + /// + long Value + { + get; + } + + /// + /// Resets the data checksum as if no update was ever called. + /// + void Reset(); + + /// + /// Adds one byte to the data checksum. + /// + /// + /// The data value to add. The high byte of the integer is ignored. + /// + void Update(int value); + + /// + /// Updates the data checksum with the bytes taken from the array. + /// + /// + /// buffer an array of bytes + /// + void Update(byte[] buffer); + + /// + /// Adds the byte array to the data checksum. + /// + /// + /// The buffer which contains the data + /// + /// + /// The offset in the buffer where the data starts + /// + /// + /// the number of data bytes to add. + /// + void Update(byte[] buffer, int offset, int count); + } +} diff --git a/src/ImageSharp46/Formats/Png/Zlib/README.md b/src/ImageSharp46/Formats/Png/Zlib/README.md new file mode 100644 index 000000000..c297a91d5 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Zlib/README.md @@ -0,0 +1,2 @@ +Adler32.cs and Crc32.cs have been copied from +https://github.com/ygrenier/SharpZipLib.Portable diff --git a/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs new file mode 100644 index 000000000..3fa61ff56 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -0,0 +1,208 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + using System.IO.Compression; + + /// + /// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. + /// + internal sealed class ZlibDeflateStream : Stream + { + /// + /// The raw stream containing the uncompressed image data. + /// + private readonly Stream rawStream; + + /// + /// Computes the checksum for the data stream. + /// + private readonly Adler32 adler32 = new Adler32(); + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + // The stream responsible for decompressing the input stream. + private DeflateStream deflateStream; + + /// + /// Initializes a new instance of the class. + /// + /// The stream to compress. + /// The compression level. + public ZlibDeflateStream(Stream stream, int compressionLevel) + { + this.rawStream = stream; + + // Write the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + int cmf = 0x78; + int flg = 218; + + // http://stackoverflow.com/a/2331025/277304 + if (compressionLevel >= 5 && compressionLevel <= 6) + { + flg = 156; + } + else if (compressionLevel >= 3 && compressionLevel <= 4) + { + flg = 94; + } + else if (compressionLevel <= 2) + { + flg = 1; + } + + // Just in case + flg -= ((cmf * 256) + flg) % 31; + + if (flg < 0) + { + flg += 31; + } + + this.rawStream.WriteByte((byte)cmf); + this.rawStream.WriteByte((byte)flg); + + // Initialize the deflate Stream. + CompressionLevel level = CompressionLevel.Optimal; + + if (compressionLevel >= 1 && compressionLevel <= 5) + { + level = CompressionLevel.Fastest; + } + else if (compressionLevel == 0) + { + level = CompressionLevel.NoCompression; + } + + this.deflateStream = new DeflateStream(this.rawStream, level, true); + } + + /// + public override bool CanRead => false; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => true; + + /// + public override long Length + { + get + { + throw new NotSupportedException(); + } + } + + /// + public override long Position + { + get + { + throw new NotSupportedException(); + } + + set + { + throw new NotSupportedException(); + } + } + + /// + public override void Flush() + { + this.deflateStream?.Flush(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + this.deflateStream.Write(buffer, offset, count); + this.adler32.Update(buffer, offset, count); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // dispose managed resources + if (this.deflateStream != null) + { + this.deflateStream.Dispose(); + this.deflateStream = null; + } + else + { + // Hack: empty input? + this.rawStream.WriteByte(3); + this.rawStream.WriteByte(0); + } + + // Add the crc + uint crc = (uint)this.adler32.Value; + this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); + this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); + this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); + this.rawStream.WriteByte((byte)(crc & 0xFF)); + } + + base.Dispose(disposing); + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } + } +} diff --git a/src/ImageSharp46/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp46/Formats/Png/Zlib/ZlibInflateStream.cs new file mode 100644 index 000000000..977a4a167 --- /dev/null +++ b/src/ImageSharp46/Formats/Png/Zlib/ZlibInflateStream.cs @@ -0,0 +1,214 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.IO; + using System.IO.Compression; + + /// + /// Provides methods and properties for decompressing streams by using the Zlib Deflate algorithm. + /// + internal sealed class ZlibInflateStream : Stream + { + /// + /// The raw stream containing the uncompressed image data. + /// + private readonly Stream rawStream; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// The read crc data. + /// + private byte[] crcread; + + /// + /// The stream responsible for decompressing the input stream. + /// + private DeflateStream deflateStream; + + /// + /// Initializes a new instance of the class. + /// + /// The stream. + /// + /// Thrown if the compression method is incorrect. + /// + public ZlibInflateStream(Stream stream) + { + // The DICT dictionary identifier identifying the used dictionary. + + // The preset dictionary. + bool fdict; + this.rawStream = stream; + + // Read the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + int cmf = this.rawStream.ReadByte(); + int flag = this.rawStream.ReadByte(); + if (cmf == -1 || flag == -1) + { + return; + } + + if ((cmf & 0x0f) != 8) + { + throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}"); + } + + // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. + // int cinfo = ((cmf & (0xf0)) >> 8); + fdict = (flag & 32) != 0; + + if (fdict) + { + // The DICT dictionary identifier identifying the used dictionary. + byte[] dictId = new byte[4]; + + for (int i = 0; i < 4; i++) + { + // We consume but don't use this. + dictId[i] = (byte)this.rawStream.ReadByte(); + } + } + + // Initialize the deflate Stream. + this.deflateStream = new DeflateStream(this.rawStream, CompressionMode.Decompress, true); + } + + /// + public override bool CanRead => true; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => false; + + /// + public override long Length + { + get + { + throw new NotSupportedException(); + } + } + + /// + public override long Position + { + get + { + throw new NotSupportedException(); + } + + set + { + throw new NotSupportedException(); + } + } + + /// + public override void Flush() + { + this.deflateStream?.Flush(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + // We dont't check CRC on reading + int read = this.deflateStream.Read(buffer, offset, count); + if (read < 1 && this.crcread == null) + { + // The deflater has ended. We try to read the next 4 bytes from raw stream (crc) + this.crcread = new byte[4]; + for (int i = 0; i < 4; i++) + { + // we dont really check/use this + this.crcread[i] = (byte)this.rawStream.ReadByte(); + } + } + + return read; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // dispose managed resources + if (this.deflateStream != null) + { + this.deflateStream.Dispose(); + this.deflateStream = null; + + if (this.crcread == null) + { + // Consume the trailing 4 bytes + this.crcread = new byte[4]; + for (int i = 0; i < 4; i++) + { + this.crcread[i] = (byte)this.rawStream.ReadByte(); + } + } + } + } + + base.Dispose(disposing); + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } + } +} diff --git a/src/ImageSharp46/IO/BigEndianBitConverter.cs b/src/ImageSharp46/IO/BigEndianBitConverter.cs new file mode 100644 index 000000000..084102728 --- /dev/null +++ b/src/ImageSharp46/IO/BigEndianBitConverter.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + /// + /// Implementation of EndianBitConverter which converts to/from big-endian + /// byte arrays. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + internal sealed class BigEndianBitConverter : EndianBitConverter + { + /// + public override Endianness Endianness => Endianness.BigEndian; + + /// + public override bool IsLittleEndian() => false; + + /// + protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + { + int endOffset = index + bytes - 1; + for (int i = 0; i < bytes; i++) + { + buffer[endOffset - i] = unchecked((byte)(value & 0xff)); + value = value >> 8; + } + } + + /// + protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i = 0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex + i]); + } + + return ret; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/IO/EndianBinaryReader.cs b/src/ImageSharp46/IO/EndianBinaryReader.cs new file mode 100644 index 000000000..3b12401b6 --- /dev/null +++ b/src/ImageSharp46/IO/EndianBinaryReader.cs @@ -0,0 +1,615 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + using System.IO; + using System.Text; + + /// + /// Equivalent of , but with either endianness, depending on + /// the EndianBitConverter it is constructed with. No data is buffered in the + /// reader; the client may seek within the stream at will. + /// + internal class EndianBinaryReader : IDisposable + { + /// + /// Decoder to use for string conversions. + /// + private readonly Decoder decoder; + + /// + /// Buffer used for temporary storage before conversion into primitives + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// Buffer used for temporary storage when reading a single character + /// + private readonly char[] charBuffer = new char[1]; + + /// + /// Minimum number of bytes used to encode a character + /// + private readonly int minBytesPerChar; + + /// + /// Whether or not this reader has been disposed yet. + /// + private bool disposed; + + /// + /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on + /// the EndianBitConverter it is constructed with. + /// + /// Converter to use when reading data + /// Stream to read data from + public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream) + : this(bitConverter, stream, Encoding.UTF8) + { + } + + /// + /// Constructs a new binary reader with the given bit converter, reading + /// to the given stream, using the given encoding. + /// + /// Converter to use when reading data + /// Stream to read data from + /// Encoding to use when reading character data + public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream, Encoding encoding) + { + // TODO: Use Guard + if (bitConverter == null) + { + throw new ArgumentNullException("bitConverter"); + } + + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (encoding == null) + { + throw new ArgumentNullException("encoding"); + } + + if (!stream.CanRead) + { + throw new ArgumentException("Stream isn't writable", "stream"); + } + + this.BaseStream = stream; + this.BitConverter = bitConverter; + this.Encoding = encoding; + this.decoder = encoding.GetDecoder(); + this.minBytesPerChar = 1; + + if (encoding is UnicodeEncoding) + { + this.minBytesPerChar = 2; + } + } + + /// + /// Gets the bit converter used to read values from the stream. + /// + public EndianBitConverter BitConverter { get; } + + /// + /// Gets the encoding used to read strings + /// + public Encoding Encoding { get; } + + /// + /// Gets the underlying stream of the EndianBinaryReader. + /// + public Stream BaseStream { get; } + + /// + /// Closes the reader, including the underlying stream. + /// + public void Close() + { + this.Dispose(); + } + + /// + /// Seeks within the stream. + /// + /// Offset to seek to. + /// Origin of seek operation. + public void Seek(int offset, SeekOrigin origin) + { + this.CheckDisposed(); + this.BaseStream.Seek(offset, origin); + } + + /// + /// Reads a single byte from the stream. + /// + /// The byte read + public byte ReadByte() + { + this.ReadInternal(this.buffer, 1); + return this.buffer[0]; + } + + /// + /// Reads a single signed byte from the stream. + /// + /// The byte read + public sbyte ReadSByte() + { + this.ReadInternal(this.buffer, 1); + return unchecked((sbyte)this.buffer[0]); + } + + /// + /// Reads a boolean from the stream. 1 byte is read. + /// + /// The boolean read + public bool ReadBoolean() + { + this.ReadInternal(this.buffer, 1); + return this.BitConverter.ToBoolean(this.buffer, 0); + } + + /// + /// Reads a 16-bit signed integer from the stream, using the bit converter + /// for this reader. 2 bytes are read. + /// + /// The 16-bit integer read + public short ReadInt16() + { + this.ReadInternal(this.buffer, 2); + return this.BitConverter.ToInt16(this.buffer, 0); + } + + /// + /// Reads a 32-bit signed integer from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The 32-bit integer read + public int ReadInt32() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToInt32(this.buffer, 0); + } + + /// + /// Reads a 64-bit signed integer from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The 64-bit integer read + public long ReadInt64() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToInt64(this.buffer, 0); + } + + /// + /// Reads a 16-bit unsigned integer from the stream, using the bit converter + /// for this reader. 2 bytes are read. + /// + /// The 16-bit unsigned integer read + public ushort ReadUInt16() + { + this.ReadInternal(this.buffer, 2); + return this.BitConverter.ToUInt16(this.buffer, 0); + } + + /// + /// Reads a 32-bit unsigned integer from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The 32-bit unsigned integer read + public uint ReadUInt32() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToUInt32(this.buffer, 0); + } + + /// + /// Reads a 64-bit unsigned integer from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The 64-bit unsigned integer read + public ulong ReadUInt64() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToUInt64(this.buffer, 0); + } + + /// + /// Reads a single-precision floating-point value from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The floating point value read + public float ReadSingle() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToSingle(this.buffer, 0); + } + + /// + /// Reads a double-precision floating-point value from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The floating point value read + public double ReadDouble() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToDouble(this.buffer, 0); + } + + /// + /// Reads a decimal value from the stream, using the bit converter + /// for this reader. 16 bytes are read. + /// + /// The decimal value read + public decimal ReadDecimal() + { + this.ReadInternal(this.buffer, 16); + return this.BitConverter.ToDecimal(this.buffer, 0); + } + + /// + /// Reads a single character from the stream, using the character encoding for + /// this reader. If no characters have been fully read by the time the stream ends, + /// -1 is returned. + /// + /// The character read, or -1 for end of stream. + public int Read() + { + int charsRead = this.Read(this.charBuffer, 0, 1); + if (charsRead == 0) + { + return -1; + } + else + { + return this.charBuffer[0]; + } + } + + /// + /// Reads the specified number of characters into the given buffer, starting at + /// the given index. + /// + /// The buffer to copy data into + /// The first index to copy data into + /// The number of characters to read + /// The number of characters actually read. This will only be less than + /// the requested number of characters if the end of the stream is reached. + /// + public int Read(char[] data, int index, int count) + { + this.CheckDisposed(); + + // TODO: Use Guard + if (this.buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count + index > data.Length) + { + throw new ArgumentException("Not enough space in buffer for specified number of characters starting at specified index"); + } + + int read = 0; + bool firstTime = true; + + // Use the normal buffer if we're only reading a small amount, otherwise + // use at most 4K at a time. + byte[] byteBuffer = this.buffer; + + if (byteBuffer.Length < count * this.minBytesPerChar) + { + byteBuffer = new byte[4096]; + } + + while (read < count) + { + int amountToRead; + + // First time through we know we haven't previously read any data + if (firstTime) + { + amountToRead = count * this.minBytesPerChar; + firstTime = false; + } + + // After that we can only assume we need to fully read 'chars left -1' characters + // and a single byte of the character we may be in the middle of + else + { + amountToRead = ((count - read - 1) * this.minBytesPerChar) + 1; + } + + if (amountToRead > byteBuffer.Length) + { + amountToRead = byteBuffer.Length; + } + + int bytesRead = this.TryReadInternal(byteBuffer, amountToRead); + if (bytesRead == 0) + { + return read; + } + + int decoded = this.decoder.GetChars(byteBuffer, 0, bytesRead, data, index); + read += decoded; + index += decoded; + } + + return read; + } + + /// + /// Reads the specified number of bytes into the given buffer, starting at + /// the given index. + /// + /// The buffer to copy data into + /// The first index to copy data into + /// The number of bytes to read + /// The number of bytes actually read. This will only be less than + /// the requested number of bytes if the end of the stream is reached. + /// + public int Read(byte[] buffer, int index, int count) + { + this.CheckDisposed(); + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count + index > buffer.Length) + { + throw new ArgumentException("Not enough space in buffer for specified number of bytes starting at specified index"); + } + + int read = 0; + while (count > 0) + { + int block = this.BaseStream.Read(buffer, index, count); + if (block == 0) + { + return read; + } + + index += block; + read += block; + count -= block; + } + + return read; + } + + /// + /// Reads the specified number of bytes, returning them in a new byte array. + /// If not enough bytes are available before the end of the stream, this + /// method will return what is available. + /// + /// The number of bytes to read + /// The bytes read + public byte[] ReadBytes(int count) + { + this.CheckDisposed(); + if (count < 0) + { + throw new ArgumentOutOfRangeException("count"); + } + + byte[] ret = new byte[count]; + int index = 0; + while (index < count) + { + int read = this.BaseStream.Read(ret, index, count - index); + + // Stream has finished half way through. That's fine, return what we've got. + if (read == 0) + { + byte[] copy = new byte[index]; + Buffer.BlockCopy(ret, 0, copy, 0, index); + return copy; + } + + index += read; + } + + return ret; + } + + /// + /// Reads the specified number of bytes, returning them in a new byte array. + /// If not enough bytes are available before the end of the stream, this + /// method will throw an IOException. + /// + /// The number of bytes to read + /// The bytes read + public byte[] ReadBytesOrThrow(int count) + { + byte[] ret = new byte[count]; + this.ReadInternal(ret, count); + return ret; + } + + /// + /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. This method is not affected by the endianness + /// of the bit converter. + /// + /// The 7-bit encoded integer read from the stream. + public int Read7BitEncodedInt() + { + this.CheckDisposed(); + + int ret = 0; + for (int shift = 0; shift < 35; shift += 7) + { + int b = this.BaseStream.ReadByte(); + if (b == -1) + { + throw new EndOfStreamException(); + } + + ret = ret | ((b & 0x7f) << shift); + if ((b & 0x80) == 0) + { + return ret; + } + } + + // Still haven't seen a byte with the high bit unset? Dodgy data. + throw new IOException("Invalid 7-bit encoded integer in stream."); + } + + /// + /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. This method is not affected by the endianness + /// of the bit converter. + /// + /// The 7-bit encoded integer read from the stream. + public int ReadBigEndian7BitEncodedInt() + { + this.CheckDisposed(); + + int ret = 0; + for (int i = 0; i < 5; i++) + { + int b = this.BaseStream.ReadByte(); + if (b == -1) + { + throw new EndOfStreamException(); + } + + ret = (ret << 7) | (b & 0x7f); + if ((b & 0x80) == 0) + { + return ret; + } + } + + // Still haven't seen a byte with the high bit unset? Dodgy data. + throw new IOException("Invalid 7-bit encoded integer in stream."); + } + + /// + /// Reads a length-prefixed string from the stream, using the encoding for this reader. + /// A 7-bit encoded integer is first read, which specifies the number of bytes + /// to read from the stream. These bytes are then converted into a string with + /// the encoding for this reader. + /// + /// The string read from the stream. + public string ReadString() + { + int bytesToRead = this.Read7BitEncodedInt(); + + byte[] data = new byte[bytesToRead]; + this.ReadInternal(data, bytesToRead); + return this.Encoding.GetString(data, 0, data.Length); + } + + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!this.disposed) + { + this.disposed = true; + ((IDisposable)this.BaseStream).Dispose(); + } + } + + /// + /// Checks whether or not the reader has been disposed, throwing an exception if so. + /// + private void CheckDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException("EndianBinaryReader"); + } + } + + /// + /// Reads the given number of bytes from the stream, throwing an exception + /// if they can't all be read. + /// + /// Buffer to read into + /// Number of bytes to read + private void ReadInternal(byte[] data, int size) + { + this.CheckDisposed(); + int index = 0; + while (index < size) + { + int read = this.BaseStream.Read(data, index, size - index); + if (read == 0) + { + throw new EndOfStreamException( + string.Format( + "End of stream reached with {0} byte{1} left to read.", + size - index, + size - index == 1 ? "s" : string.Empty)); + } + + index += read; + } + } + + /// + /// Reads the given number of bytes from the stream if possible, returning + /// the number of bytes actually read, which may be less than requested if + /// (and only if) the end of the stream is reached. + /// + /// Buffer to read into + /// Number of bytes to read + /// Number of bytes actually read + private int TryReadInternal(byte[] data, int size) + { + this.CheckDisposed(); + int index = 0; + while (index < size) + { + int read = this.BaseStream.Read(data, index, size - index); + if (read == 0) + { + return index; + } + + index += read; + } + + return index; + } + } +} diff --git a/src/ImageSharp46/IO/EndianBinaryWriter.cs b/src/ImageSharp46/IO/EndianBinaryWriter.cs new file mode 100644 index 000000000..b10ae79b4 --- /dev/null +++ b/src/ImageSharp46/IO/EndianBinaryWriter.cs @@ -0,0 +1,385 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + using System.IO; + using System.Text; + + /// + /// Equivalent of , but with either endianness, depending on + /// the it is constructed with. + /// + internal class EndianBinaryWriter : IDisposable + { + /// + /// Buffer used for temporary storage during conversion from primitives + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// Buffer used for Write(char) + /// + private readonly char[] charBuffer = new char[1]; + + /// + /// Whether or not this writer has been disposed yet. + /// + private bool disposed; + + /// + /// Initializes a new instance of the class + /// with the given bit converter, writing to the given stream, using UTF-8 encoding. + /// + /// Converter to use when writing data + /// Stream to write data to + public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream) + : this(bitConverter, stream, Encoding.UTF8) + { + } + + /// + /// Initializes a new instance of the class + /// with the given bit converter, writing to the given stream, using the given encoding. + /// + /// Converter to use when writing data + /// Stream to write data to + /// + /// Encoding to use when writing character data + /// + public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream, Encoding encoding) + { + // TODO: Use Guard + if (bitConverter == null) + { + throw new ArgumentNullException("bitConverter"); + } + + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (encoding == null) + { + throw new ArgumentNullException("encoding"); + } + + if (!stream.CanWrite) + { + throw new ArgumentException("Stream isn't writable", "stream"); + } + + this.BaseStream = stream; + this.BitConverter = bitConverter; + this.Encoding = encoding; + } + + /// + /// Gets the bit converter used to write values to the stream + /// + public EndianBitConverter BitConverter { get; } + + /// + /// Gets the encoding used to write strings + /// + public Encoding Encoding { get; } + + /// + /// Gets the underlying stream of the EndianBinaryWriter. + /// + public Stream BaseStream { get; } + + /// + /// Closes the writer, including the underlying stream. + /// + public void Close() + { + this.Dispose(); + } + + /// + /// Flushes the underlying stream. + /// + public void Flush() + { + this.CheckDisposed(); + this.BaseStream.Flush(); + } + + /// + /// Seeks within the stream. + /// + /// Offset to seek to. + /// Origin of seek operation. + public void Seek(int offset, SeekOrigin origin) + { + this.CheckDisposed(); + this.BaseStream.Seek(offset, origin); + } + + /// + /// Writes a boolean value to the stream. 1 byte is written. + /// + /// The value to write + public void Write(bool value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes a 16-bit signed integer to the stream, using the bit converter + /// for this writer. 2 bytes are written. + /// + /// The value to write + public void Write(short value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 2); + } + + /// + /// Writes a 32-bit signed integer to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(int value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a 64-bit signed integer to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(long value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a 16-bit unsigned integer to the stream, using the bit converter + /// for this writer. 2 bytes are written. + /// + /// The value to write + public void Write(ushort value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 2); + } + + /// + /// Writes a 32-bit unsigned integer to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(uint value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a 64-bit unsigned integer to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(ulong value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a single-precision floating-point value to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(float value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a double-precision floating-point value to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(double value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a decimal value to the stream, using the bit converter for this writer. + /// 16 bytes are written. + /// + /// The value to write + public void Write(decimal value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 16); + } + + /// + /// Writes a signed byte to the stream. + /// + /// The value to write + public void Write(byte value) + { + this.buffer[0] = value; + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes an unsigned byte to the stream. + /// + /// The value to write + public void Write(sbyte value) + { + this.buffer[0] = unchecked((byte)value); + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes an array of bytes to the stream. + /// + /// The values to write + public void Write(byte[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.WriteInternal(value, value.Length); + } + + /// + /// Writes a portion of an array of bytes to the stream. + /// + /// An array containing the bytes to write + /// The index of the first byte to write within the array + /// The number of bytes to write + public void Write(byte[] value, int offset, int count) + { + this.CheckDisposed(); + this.BaseStream.Write(value, offset, count); + } + + /// + /// Writes a single character to the stream, using the encoding for this writer. + /// + /// The value to write + public void Write(char value) + { + this.charBuffer[0] = value; + this.Write(this.charBuffer); + } + + /// + /// Writes an array of characters to the stream, using the encoding for this writer. + /// + /// An array containing the characters to write + public void Write(char[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.CheckDisposed(); + byte[] data = this.Encoding.GetBytes(value, 0, value.Length); + this.WriteInternal(data, data.Length); + } + + /// + /// Writes a string to the stream, using the encoding for this writer. + /// + /// The value to write. Must not be null. + /// value is null + public void Write(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.CheckDisposed(); + byte[] data = this.Encoding.GetBytes(value); + this.Write7BitEncodedInt(data.Length); + this.WriteInternal(data, data.Length); + } + + /// + /// Writes a 7-bit encoded integer from the stream. This is stored with the least significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. + /// + /// The 7-bit encoded integer to write to the stream + public void Write7BitEncodedInt(int value) + { + this.CheckDisposed(); + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than or equal to 0."); + } + + int index = 0; + while (value >= 128) + { + this.buffer[index++] = (byte)((value & 0x7f) | 0x80); + value = value >> 7; + index++; + } + + this.buffer[index++] = (byte)value; + this.BaseStream.Write(this.buffer, 0, index); + } + + /// + /// Checks whether or not the writer has been disposed, throwing an exception if so. + /// + private void CheckDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException("EndianBinaryWriter"); + } + } + + /// + /// Writes the specified number of bytes from the start of the given byte array, + /// after checking whether or not the writer has been disposed. + /// + /// The array of bytes to write from + /// The number of bytes to write + private void WriteInternal(byte[] bytes, int length) + { + this.CheckDisposed(); + this.BaseStream.Write(bytes, 0, length); + } + + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!this.disposed) + { + this.Flush(); + this.disposed = true; + ((IDisposable)this.BaseStream).Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/IO/EndianBitConverter.cs b/src/ImageSharp46/IO/EndianBitConverter.cs new file mode 100644 index 000000000..d7a8d91fa --- /dev/null +++ b/src/ImageSharp46/IO/EndianBitConverter.cs @@ -0,0 +1,724 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.InteropServices; + + /// + /// Equivalent of , but with either endianness. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "Reviewed. Suppression is OK here. Better readability.")] + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Reviewed. Suppression is OK here. Better readability.")] + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "Reviewed. Suppression is OK here. Better readability.")] + internal abstract class EndianBitConverter + { + #region Endianness of this converter + /// + /// Indicates the byte order ("endianness") in which data is converted using this class. + /// + /// + /// Different computer architectures store data using different byte orders. "Big-endian" + /// means the most significant byte is on the left end of a word. "Little-endian" means the + /// most significant byte is on the right end of a word. + /// + /// true if this converter is little-endian, false otherwise. + public abstract bool IsLittleEndian(); + + /// + /// Gets the byte order ("endianness") in which data is converted using this class. + /// + public abstract Endianness Endianness { get; } + #endregion + + #region Factory properties + /// + /// The little-endian bit converter. + /// + private static readonly LittleEndianBitConverter LittleConverter = new LittleEndianBitConverter(); + + /// + /// Gets a little-endian bit converter instance. The same instance is + /// always returned. + /// + public static LittleEndianBitConverter Little => LittleConverter; + + /// + /// The big-endian bit converter. + /// + private static readonly BigEndianBitConverter BigConverter = new BigEndianBitConverter(); + + /// + /// Gets a big-endian bit converter instance. The same instance is + /// always returned. + /// + public static BigEndianBitConverter Big => BigConverter; + #endregion + + #region Double/primitive conversions + /// + /// Converts the specified double-precision floating point number to a + /// 64-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 64-bit signed integer whose value is equivalent to value. + public long DoubleToInt64Bits(double value) + { + return BitConverter.DoubleToInt64Bits(value); + } + + /// + /// Converts the specified 64-bit signed integer to a double-precision + /// floating point number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A double-precision floating point number whose value is equivalent to value. + public double Int64BitsToDouble(long value) + { + return BitConverter.Int64BitsToDouble(value); + } + + /// + /// Converts the specified single-precision floating point number to a + /// 32-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 32-bit signed integer whose value is equivalent to value. + public int SingleToInt32Bits(float value) + { + return new Int32SingleUnion(value).AsInt32; + } + + /// + /// Converts the specified 32-bit signed integer to a single-precision floating point + /// number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A single-precision floating point number whose value is equivalent to value. + public float Int32BitsToSingle(int value) + { + return new Int32SingleUnion(value).AsSingle; + } + #endregion + + #region To(PrimitiveType) conversions + /// + /// Returns a Boolean value converted from one byte at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// true if the byte at startIndex in value is nonzero; otherwise, false. + public bool ToBoolean(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 1); + return BitConverter.ToBoolean(value, startIndex); + } + + /// + /// Returns a Unicode character converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A character formed by two bytes beginning at startIndex. + public char ToChar(byte[] value, int startIndex) + { + return unchecked((char)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A double precision floating point number formed by eight bytes beginning at startIndex. + public double ToDouble(byte[] value, int startIndex) + { + return this.Int64BitsToDouble(this.ToInt64(value, startIndex)); + } + + /// + /// Returns a single-precision floating point number converted from four bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A single precision floating point number formed by four bytes beginning at startIndex. + public float ToSingle(byte[] value, int startIndex) + { + return this.Int32BitsToSingle(this.ToInt32(value, startIndex)); + } + + /// + /// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit signed integer formed by two bytes beginning at startIndex. + public short ToInt16(byte[] value, int startIndex) + { + return unchecked((short)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit signed integer formed by four bytes beginning at startIndex. + public int ToInt32(byte[] value, int startIndex) + { + return unchecked((int)this.CheckedFromBytes(value, startIndex, 4)); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit signed integer formed by eight bytes beginning at startIndex. + public long ToInt64(byte[] value, int startIndex) + { + return this.CheckedFromBytes(value, startIndex, 8); + } + + /// + /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit unsigned integer formed by two bytes beginning at startIndex. + public ushort ToUInt16(byte[] value, int startIndex) + { + return unchecked((ushort)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit unsigned integer formed by four bytes beginning at startIndex. + public uint ToUInt32(byte[] value, int startIndex) + { + return unchecked((uint)this.CheckedFromBytes(value, startIndex, 4)); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit unsigned integer formed by eight bytes beginning at startIndex. + public ulong ToUInt64(byte[] value, int startIndex) + { + return unchecked((ulong)this.CheckedFromBytes(value, startIndex, 8)); + } + + /// + /// Convert the given number of bytes from the given array, from the given start + /// position, into a long, using the bytes as the least significant part of the long. + /// By the time this is called, the arguments have been checked for validity. + /// + /// The bytes to convert + /// The index of the first byte to convert + /// The number of bytes to use in the conversion + /// The converted number + protected internal abstract long FromBytes(byte[] value, int startIndex, int bytesToConvert); + + /// + /// Checks the given argument for validity. + /// + /// The byte array passed in + /// The start index passed in + /// The number of bytes required + /// value is a null reference + /// + /// startIndex is less than zero or greater than the length of value minus bytesRequired. + /// + [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Keeps code DRY")] + private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (startIndex < 0 || startIndex > value.Length - bytesRequired) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + } + + /// + /// Checks the arguments for validity before calling FromBytes + /// (which can therefore assume the arguments are valid). + /// + /// The bytes to convert after checking + /// The index of the first byte to convert + /// The number of bytes to convert + /// The + private long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert) + { + CheckByteArgument(value, startIndex, bytesToConvert); + return this.FromBytes(value, startIndex, bytesToConvert); + } + #endregion + + #region ToString conversions + /// + /// Returns a String converted from the elements of a byte array. + /// + /// An array of bytes. + /// All the elements of value are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value) + { + return BitConverter.ToString(value); + } + + /// + /// Returns a String converted from the elements of a byte array starting at a specified array position. + /// + /// An array of bytes. + /// The starting position within value. + /// The elements from array position startIndex to the end of the array are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value, int startIndex) + { + return BitConverter.ToString(value, startIndex); + } + + /// + /// Returns a String converted from a specified number of bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// The number of bytes to convert. + /// The length elements from array position startIndex are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value, int startIndex, int length) + { + return BitConverter.ToString(value, startIndex, length); + } + #endregion + + #region Decimal conversions + /// + /// Returns a decimal value converted from sixteen bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A decimal formed by sixteen bytes beginning at startIndex. + public decimal ToDecimal(byte[] value, int startIndex) + { + // HACK: This always assumes four parts, each in their own endianness, + // starting with the first part at the start of the byte array. + // On the other hand, there's no real format specified... + int[] parts = new int[4]; + for (int i = 0; i < 4; i++) + { + parts[i] = this.ToInt32(value, startIndex + (i * 4)); + } + + return new decimal(parts); + } + + /// + /// Returns the specified decimal value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 16. + public byte[] GetBytes(decimal value) + { + byte[] bytes = new byte[16]; + int[] parts = decimal.GetBits(value); + for (int i = 0; i < 4; i++) + { + this.CopyBytesImpl(parts[i], 4, bytes, i * 4); + } + + return bytes; + } + + /// + /// Copies the specified decimal value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(decimal value, byte[] buffer, int index) + { + int[] parts = decimal.GetBits(value); + for (int i = 0; i < 4; i++) + { + this.CopyBytesImpl(parts[i], 4, buffer, (i * 4) + index); + } + } + #endregion + + #region GetBytes conversions + /// + /// Returns an array with the given number of bytes formed + /// from the least significant bytes of the specified value. + /// This is used to implement the other GetBytes methods. + /// + /// The value to get bytes for + /// The number of significant bytes to return + /// + /// The . + /// + private byte[] GetBytes(long value, int bytes) + { + byte[] buffer = new byte[bytes]; + this.CopyBytes(value, bytes, buffer, 0); + return buffer; + } + + /// + /// Returns the specified Boolean value as an array of bytes. + /// + /// A Boolean value. + /// An array of bytes with length 1. + /// + /// The . + /// + public byte[] GetBytes(bool value) + { + return BitConverter.GetBytes(value); + } + + /// + /// Returns the specified Unicode character value as an array of bytes. + /// + /// A character to convert. + /// An array of bytes with length 2. + /// + /// The . + /// + public byte[] GetBytes(char value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified double-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(double value) + { + return this.GetBytes(this.DoubleToInt64Bits(value), 8); + } + + /// + /// Returns the specified 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(short value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(int value) + { + return this.GetBytes(value, 4); + } + + /// + /// Returns the specified 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(long value) + { + return this.GetBytes(value, 8); + } + + /// + /// Returns the specified single-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(float value) + { + return this.GetBytes(this.SingleToInt32Bits(value), 4); + } + + /// + /// Returns the specified 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(ushort value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(uint value) + { + return this.GetBytes(value, 4); + } + + /// + /// Returns the specified 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(ulong value) + { + return this.GetBytes(unchecked((long)value), 8); + } + + #endregion + + #region CopyBytes conversions + /// + /// Copies the given number of bytes from the least-specific + /// end of the specified value into the specified byte array, beginning + /// at the specified index. + /// This is used to implement the other CopyBytes methods. + /// + /// The value to copy bytes for + /// The number of significant bytes to copy + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + private void CopyBytes(long value, int bytes, byte[] buffer, int index) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer), "Byte array must not be null"); + } + + if (buffer.Length < index + bytes) + { + throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer not big enough for value"); + } + + this.CopyBytesImpl(value, bytes, buffer, index); + } + + /// + /// Copies the given number of bytes from the least-specific + /// end of the specified value into the specified byte array, beginning + /// at the specified index. + /// This must be implemented in concrete derived classes, but the implementation + /// may assume that the value will fit into the buffer. + /// + /// The value to copy bytes for + /// The number of significant bytes to copy + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + protected internal abstract void CopyBytesImpl(long value, int bytes, byte[] buffer, int index); + + /// + /// Copies the specified Boolean value into the specified byte array, + /// beginning at the specified index. + /// + /// A Boolean value. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(bool value, byte[] buffer, int index) + { + this.CopyBytes(value ? 1 : 0, 1, buffer, index); + } + + /// + /// Copies the specified Unicode character value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(char value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified double-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(double value, byte[] buffer, int index) + { + this.CopyBytes(this.DoubleToInt64Bits(value), 8, buffer, index); + } + + /// + /// Copies the specified 16-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(short value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified 32-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(int value, byte[] buffer, int index) + { + this.CopyBytes(value, 4, buffer, index); + } + + /// + /// Copies the specified 64-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(long value, byte[] buffer, int index) + { + this.CopyBytes(value, 8, buffer, index); + } + + /// + /// Copies the specified single-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(float value, byte[] buffer, int index) + { + this.CopyBytes(this.SingleToInt32Bits(value), 4, buffer, index); + } + + /// + /// Copies the specified 16-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ushort value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified 32-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(uint value, byte[] buffer, int index) + { + this.CopyBytes(value, 4, buffer, index); + } + + /// + /// Copies the specified 64-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ulong value, byte[] buffer, int index) + { + this.CopyBytes(unchecked((long)value), 8, buffer, index); + } + + #endregion + + #region Private struct used for Single/Int32 conversions + /// + /// Union used solely for the equivalent of DoubleToInt64Bits and vice versa. + /// + [StructLayout(LayoutKind.Explicit)] + private struct Int32SingleUnion + { + /// + /// Int32 version of the value. + /// + [FieldOffset(0)] + private readonly int i; + + /// + /// Single version of the value. + /// + [FieldOffset(0)] + private readonly float f; + + /// + /// Initializes a new instance of the struct. + /// + /// The integer value of the new instance. + internal Int32SingleUnion(int i) + { + this.f = 0; // Just to keep the compiler happy + this.i = i; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The floating point value of the new instance. + /// + internal Int32SingleUnion(float f) + { + this.i = 0; // Just to keep the compiler happy + this.f = f; + } + + /// + /// Gets the value of the instance as an integer. + /// + internal int AsInt32 => this.i; + + /// + /// Gets the value of the instance as a floating point number. + /// + internal float AsSingle => this.f; + } + #endregion + } +} \ No newline at end of file diff --git a/src/ImageSharp46/IO/Endianness.cs b/src/ImageSharp46/IO/Endianness.cs new file mode 100644 index 000000000..aefda6dc4 --- /dev/null +++ b/src/ImageSharp46/IO/Endianness.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + /// + /// Endianness of a converter + /// + internal enum Endianness + { + /// + /// Little endian - least significant byte first + /// + LittleEndian, + + /// + /// Big endian - most significant byte first + /// + BigEndian + } +} diff --git a/src/ImageSharp46/IO/LittleEndianBitConverter.cs b/src/ImageSharp46/IO/LittleEndianBitConverter.cs new file mode 100644 index 000000000..63ebe18a3 --- /dev/null +++ b/src/ImageSharp46/IO/LittleEndianBitConverter.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + /// + /// Implementation of EndianBitConverter which converts to/from little-endian + /// byte arrays. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + internal sealed class LittleEndianBitConverter : EndianBitConverter + { + /// + public override Endianness Endianness => Endianness.LittleEndian; + + /// + public override bool IsLittleEndian() => true; + + /// + protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + { + for (int i = 0; i < bytes; i++) + { + buffer[i + index] = unchecked((byte)(value & 0xff)); + value = value >> 8; + } + } + + /// + protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i = 0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex + bytesToConvert - 1 - i]); + } + + return ret; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Image.cs b/src/ImageSharp46/Image.cs new file mode 100644 index 000000000..83a4b9dff --- /dev/null +++ b/src/ImageSharp46/Image.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Diagnostics; + using System.IO; + + /// + /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha + /// packed into a single unsigned integer value. + /// + [DebuggerDisplay("Image: {Width}x{Height}")] + public class Image : Image + { + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + public Image() + { + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(int width, int height) + : base(width, height) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + : base(stream) + { + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another image. + /// + /// The other image, where the clone should be made from. + /// is null. + public Image(Image other) + : base(other) + { + } + + /// + public override PixelAccessor Lock() + { + return new PixelAccessor(this); + } + } +} diff --git a/src/ImageSharp46/Image/IImageBase.cs b/src/ImageSharp46/Image/IImageBase.cs new file mode 100644 index 000000000..3168b4bb5 --- /dev/null +++ b/src/ImageSharp46/Image/IImageBase.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the basic properties and methods required to manipulate images in varying formats. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public interface IImageBase : IImageBase + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Gets the pixels as an array of the given packed pixel format. + /// + TColor[] Pixels { get; } + + /// + /// Sets the size of the pixel array of the image to the given width and height. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// + /// Thrown if either or are less than or equal to 0. + /// + void InitPixels(int width, int height); + + /// + /// Sets the pixel array of the image to the given value. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// The array with pixels. Must be a multiple of the width and height. + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height. + /// + void SetPixels(int width, int height, TColor[] pixels); + + /// + /// Sets the pixel array of the image to the given value, creating a copy of + /// the original pixels. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// The array with pixels. Must be a multiple of four times the width and height. + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height. + /// + void ClonePixels(int width, int height, TColor[] pixels); + + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The + PixelAccessor Lock(); + } + + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + public interface IImageBase + { + /// + /// Gets the representing the bounds of the image. + /// + Rectangle Bounds { get; } + + /// + /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. + /// + int Quality { get; set; } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + int FrameDelay { get; set; } + + /// + /// Gets or sets the maximum allowable width in pixels. + /// + int MaxWidth { get; set; } + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + int MaxHeight { get; set; } + + /// + /// Gets the width in pixels. + /// + int Width { get; } + + /// + /// Gets the height in pixels. + /// + int Height { get; } + + /// + /// Gets the pixel ratio made up of the width and height. + /// + double PixelRatio { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Image/IImageFrame.cs b/src/ImageSharp46/Image/IImageFrame.cs new file mode 100644 index 000000000..f6f856b22 --- /dev/null +++ b/src/ImageSharp46/Image/IImageFrame.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Represents a single frame in a animation. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public interface IImageFrame : IImageBase + where TColor : struct, IPackedPixel + where TPacked : struct + { + } +} diff --git a/src/ImageSharp46/Image/IImageProcessor.cs b/src/ImageSharp46/Image/IImageProcessor.cs new file mode 100644 index 000000000..145e83136 --- /dev/null +++ b/src/ImageSharp46/Image/IImageProcessor.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Threading.Tasks; + + /// + /// Encapsulates methods to alter the pixels of an image. + /// + public interface IImageProcessor + { + /// + /// Gets or sets the parallel options for processing tasks in parallel. + /// + ParallelOptions ParallelOptions { get; set; } + + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + bool Compand { get; set; } + } +} diff --git a/src/ImageSharp46/Image/Image.cs b/src/ImageSharp46/Image/Image.cs new file mode 100644 index 000000000..337f38fcf --- /dev/null +++ b/src/ImageSharp46/Image/Image.cs @@ -0,0 +1,361 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Text; + + using Formats; + + /// + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// The pixel format. + /// The packed format. uint, long, float. + [DebuggerDisplay("Image: {Width}x{Height}")] + public class Image : ImageBase + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultVerticalResolution = 96; + + /// + /// Initializes a new instance of the class. + /// + public Image() + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(int width, int height) + : base(width, height) + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.Load(stream); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another image. + /// + /// The other image, where the clone should be made from. + /// is null. + public Image(Image other) + : base(other) + { + foreach (ImageFrame frame in other.Frames) + { + if (frame != null) + { + this.Frames.Add(new ImageFrame(frame)); + } + } + + this.CopyProperties(other); + } + + /// + /// Gets a list of supported image formats. + /// + public IReadOnlyCollection Formats { get; } = Bootstrapper.Instance.ImageFormats; + + /// + /// Gets or sets the resolution of the image in x- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; + + /// + /// Gets or sets the resolution of the image in y- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution { get; set; } = DefaultVerticalResolution; + + /// + /// Gets the width of the image in inches. It is calculated as the width of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The width of the image in inches. + public double InchWidth + { + get + { + double resolution = this.HorizontalResolution; + + if (resolution <= 0) + { + resolution = DefaultHorizontalResolution; + } + + return this.Width / resolution; + } + } + + /// + /// Gets the height of the image in inches. It is calculated as the height of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The height of the image in inches. + public double InchHeight + { + get + { + double resolution = this.VerticalResolution; + + if (resolution <= 0) + { + resolution = DefaultVerticalResolution; + } + + return this.Height / resolution; + } + } + + /// + /// Gets a value indicating whether this image is animated. + /// + /// + /// True if this image is animated; otherwise, false. + /// + public bool IsAnimated => this.Frames.Count > 0; + + /// + /// Gets or sets the number of times any animation is repeated. + /// 0 means to repeat indefinitely. + /// + public ushort RepeatCount { get; set; } + + /// + /// Gets the other frames for the animation. + /// + /// The list of frame images. + public IList> Frames { get; } = new List>(); + + /// + /// Gets the list of properties for storing meta information about this image. + /// + /// A list of image properties. + public IList Properties { get; } = new List(); + + /// + /// Gets the currently loaded image format. + /// + public IImageFormat CurrentImageFormat { get; internal set; } + + /// + /// Gets or sets the Exif profile. + /// + public ExifProfile ExifProfile { get; set; } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// Thrown if the stream is null. + public Image Save(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.CurrentImageFormat.Encoder.Encode(this, stream); + return this; + } + + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// Thrown if the stream is null. + public Image Save(Stream stream, IImageFormat format) + { + Guard.NotNull(stream, nameof(stream)); + format.Encoder.Encode(this, stream); + return this; + } + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// + /// The . + /// + public Image Save(Stream stream, IImageEncoder encoder) + { + Guard.NotNull(stream, nameof(stream)); + encoder.Encode(this, stream); + + // Reset to the start of the stream. + if (stream.CanSeek) + { + stream.Position = 0; + } + + return this; + } + + /// + public override string ToString() + { + return $"Image: {this.Width}x{this.Height}"; + } + + /// + /// Returns a Base64 encoded string from the given image. + /// + /// data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA== + /// The + public string ToBase64String() + { + using (MemoryStream stream = new MemoryStream()) + { + this.Save(stream); + stream.Flush(); + return $"data:{this.CurrentImageFormat.Encoder.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + } + } + + /// + /// Copies the properties from the other . + /// + /// + /// The other to copy the properties from. + /// + internal void CopyProperties(Image other) + { + base.CopyProperties(other); + + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.CurrentImageFormat = other.CurrentImageFormat; + this.RepeatCount = other.RepeatCount; + + if (other.ExifProfile != null) + { + this.ExifProfile = new ExifProfile(other.ExifProfile); + } + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + private void Load(Stream stream) + { + if (!this.Formats.Any()) + { + return; + } + + if (!stream.CanRead) + { + throw new NotSupportedException("Cannot read from the stream."); + } + + if (stream.CanSeek) + { + if (this.Decode(stream)) + { + return; + } + } + else + { + // We want to be able to load images from things like HttpContext.Request.Body + using (MemoryStream ms = new MemoryStream()) + { + stream.CopyTo(ms); + ms.Position = 0; + + if (this.Decode(ms)) + { + return; + } + } + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + + foreach (IImageFormat format in this.Formats) + { + stringBuilder.AppendLine("-" + format); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + + /// + /// Decodes the image stream to the current image. + /// + /// The stream. + /// + /// The . + /// + private bool Decode(Stream stream) + { + int maxHeaderSize = this.Formats.Max(x => x.Decoder.HeaderSize); + if (maxHeaderSize > 0) + { + byte[] header = new byte[maxHeaderSize]; + + stream.Position = 0; + stream.Read(header, 0, maxHeaderSize); + stream.Position = 0; + + IImageFormat format = this.Formats.FirstOrDefault(x => x.Decoder.IsSupportedFileFormat(header)); + if (format != null) + { + format.Decoder.Decode(this, stream); + this.CurrentImageFormat = format; + return true; + } + } + + return false; + } + } +} diff --git a/src/ImageSharp46/Image/ImageBase.cs b/src/ImageSharp46/Image/ImageBase.cs new file mode 100644 index 000000000..7e9ca2701 --- /dev/null +++ b/src/ImageSharp46/Image/ImageBase.cs @@ -0,0 +1,166 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + + /// + /// The base class of all images. Encapsulates the basic properties and methods required to manipulate + /// images in different pixel formats. + /// + /// The pixel format. + /// The packed format. uint, long, float. + [DebuggerDisplay("Image: {Width}x{Height}")] + public abstract class ImageBase : IImageBase + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The image pixels + /// + private TColor[] pixelBuffer; + + /// + /// Initializes a new instance of the class. + /// + protected ImageBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// + /// Thrown if either or are less than or equal to 0. + /// + protected ImageBase(int width, int height) + { + this.InitPixels(width, height); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The other to create this instance from. + /// + /// + /// Thrown if the given is null. + /// + protected ImageBase(ImageBase other) + { + Guard.NotNull(other, nameof(other), "Other image cannot be null."); + + this.Width = other.Width; + this.Height = other.Height; + this.CopyProperties(other); + + // Copy the pixels. Unsafe.CopyBlock gives us a nice speed boost here. + this.pixelBuffer = new TColor[this.Width * this.Height]; + using (PixelAccessor sourcePixels = other.Lock()) + using (PixelAccessor target = this.Lock()) + { + sourcePixels.CopyImage(target); + } + } + + /// + public int MaxWidth { get; set; } = int.MaxValue; + + /// + public int MaxHeight { get; set; } = int.MaxValue; + + /// + public TColor[] Pixels => this.pixelBuffer; + + /// + public int Width { get; private set; } + + /// + public int Height { get; private set; } + + /// + public double PixelRatio => (double)this.Width / this.Height; + + /// + public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); + + /// + public int Quality { get; set; } + + /// + public int FrameDelay { get; set; } + + /// + public void InitPixels(int width, int height) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Width = width; + this.Height = height; + this.pixelBuffer = new TColor[width * height]; + } + + /// + public void SetPixels(int width, int height, TColor[] pixels) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.NotNull(pixels, nameof(pixels)); + + if (pixels.Length != width * height) + { + throw new ArgumentException("Pixel array must have the length of Width * Height."); + } + + this.Width = width; + this.Height = height; + this.pixelBuffer = pixels; + } + + /// + public void ClonePixels(int width, int height, TColor[] pixels) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.NotNull(pixels, nameof(pixels)); + + if (pixels.Length != width * height) + { + throw new ArgumentException("Pixel array must have the length of Width * Height."); + } + + this.Width = width; + this.Height = height; + + // Copy the pixels. TODO: use Unsafe.Copy. + this.pixelBuffer = new TColor[pixels.Length]; + Array.Copy(pixels, this.pixelBuffer, pixels.Length); + } + + /// + public virtual PixelAccessor Lock() + { + return new PixelAccessor(this); + } + + /// + /// Copies the properties from the other . + /// + /// + /// The other to copy the properties from. + /// + protected void CopyProperties(ImageBase other) + { + this.Quality = other.Quality; + this.FrameDelay = other.FrameDelay; + } + } +} diff --git a/src/ImageSharp46/Image/ImageFrame.cs b/src/ImageSharp46/Image/ImageFrame.cs new file mode 100644 index 000000000..fa576c4eb --- /dev/null +++ b/src/ImageSharp46/Image/ImageFrame.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Represents a single frame in a animation. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class ImageFrame : ImageBase + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + public ImageFrame() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The frame to create the frame from. + /// + public ImageFrame(ImageFrame frame) + : base(frame) + { + } + + /// + public override string ToString() + { + return $"ImageFrame: {this.Width}x{this.Height}"; + } + } +} diff --git a/src/ImageSharp46/Image/ImageIOExtensions.cs b/src/ImageSharp46/Image/ImageIOExtensions.cs new file mode 100644 index 000000000..bb0369d8a --- /dev/null +++ b/src/ImageSharp46/Image/ImageIOExtensions.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.IO; + + using Formats; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the bmp format. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsBmp(this Image source, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct + => source.Save(stream, new BmpEncoder()); + + /// + /// Saves the image to the given stream with the png format. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to representing the number of colors. + /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. + /// + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsPng(this Image source, Stream stream, int quality = int.MaxValue) + where TColor : struct, IPackedPixel + where TPacked : struct + => source.Save(stream, new PngEncoder { Quality = quality }); + + /// + /// Saves the image to the given stream with the jpeg format. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to. Between 1 and 100. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsJpeg(this Image source, Stream stream, int quality = 75) + where TColor : struct, IPackedPixel + where TPacked : struct + => source.Save(stream, new JpegEncoder { Quality = quality }); + + /// + /// Saves the image to the given stream with the gif format. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to representing the number of colors. Between 1 and 256. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsGif(this Image source, Stream stream, int quality = 256) + where TColor : struct, IPackedPixel + where TPacked : struct + => source.Save(stream, new GifEncoder { Quality = quality }); + } +} diff --git a/src/ImageSharp46/Image/ImageProcessingExtensions.cs b/src/ImageSharp46/Image/ImageProcessingExtensions.cs new file mode 100644 index 000000000..e4b9ff3fd --- /dev/null +++ b/src/ImageSharp46/Image/ImageProcessingExtensions.cs @@ -0,0 +1,203 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies the processor to the image. + /// This method does not resize the target image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The processor to apply to the image. + /// The . + internal static Image Process(this Image source, IImageFilter processor) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Process(source, source.Bounds, processor); + } + + /// + /// Applies the processor to the image. + /// This method does not resize the target image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The processors to apply to the image. + /// The . + internal static Image Process(this Image source, Rectangle sourceRectangle, IImageFilter processor) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return PerformAction(source, (sourceImage) => processor.Apply(sourceImage, sourceRectangle)); + } + + /// + /// Applies the processor to the image. + /// This method does not resize the target image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The processor to apply to the image. + /// The . + internal static Image Process(this Image source, IImageSampler processor) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Process(source, source.Bounds, processor); + } + + /// + /// Applies the processor to the image. + /// This method does not resize the target image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The processors to apply to the image. + /// The . + internal static Image Process(this Image source, Rectangle sourceRectangle, IImageSampler processor) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); + } + + /// + /// Applies the processor to the image. + /// + /// This method resizes the image. + /// + /// + /// The pixel format. + /// The packed format. long, float. + /// The source image. Cannot be null. + /// The target image width. + /// The target image height. + /// The processor to apply to the image. + /// The . + internal static Image Process(this Image source, int width, int height, IImageSampler sampler) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Process(source, width, height, source.Bounds, default(Rectangle), sampler); + } + + /// + /// Applies the processor to the image. + /// + /// This method does will resize the target image if the source and target rectangles are different. + /// + /// + /// The pixel format. + /// The packed format. long, float. + /// The source image. Cannot be null. + /// The target image width. + /// The target image height. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// The processor to apply to the image. + /// The . + internal static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); + } + + /// + /// Performs the given action on the source image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image to perform the action against. + /// The to perform against the image. + /// The . + private static Image PerformAction(Image source, Action> action) + where TColor : struct, IPackedPixel + where TPacked : struct + { + action(source); + + foreach (ImageFrame sourceFrame in source.Frames) + { + action(sourceFrame); + } + + return source; + } + + /// + /// Performs the given action on the source image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image to perform the action against. + /// Whether to clone the image. + /// The to perform against the image. + /// The . + private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Image transformedImage = clone + ? new Image(source) + : new Image(); + + // Several properties still require copying + if (!clone) + { + transformedImage.CopyProperties(source); + } + + action(source, transformedImage); + + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame tranformedFrame = clone + ? new ImageFrame(sourceFrame) + : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; + + action(sourceFrame, tranformedFrame); + + if (!clone) + { + transformedImage.Frames.Add(tranformedFrame); + } + else + { + transformedImage.Frames[i] = tranformedFrame; + } + } + + source = transformedImage; + return source; + } + } +} diff --git a/src/ImageSharp46/Image/ImageProperty.cs b/src/ImageSharp46/Image/ImageProperty.cs new file mode 100644 index 000000000..7fda749e9 --- /dev/null +++ b/src/ImageSharp46/Image/ImageProperty.cs @@ -0,0 +1,153 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Stores meta information about a image, like the name of the author, + /// the copyright information, the date, where the image was created + /// or some other information. + /// + public class ImageProperty : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The value of the property. + public ImageProperty(string name, string value) + { + Guard.NotNullOrEmpty(name, nameof(name)); + + this.Name = name; + this.Value = value; + } + + /// + /// Gets the name of this indicating which kind of + /// information this property stores. + /// + /// + /// Typical properties are the author, copyright + /// information or other meta information. + /// + public string Name { get; } + + /// + /// Gets the value of this . + /// + public string Value { get; } + + /// + /// Compares two objects. The result specifies whether the values + /// of the or properties of the two + /// objects are equal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(ImageProperty left, ImageProperty right) + { + return Equals(left, right); + } + + /// + /// Compares two objects. The result specifies whether the values + /// of the or properties of the two + /// objects are unequal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(ImageProperty left, ImageProperty right) + { + return !Equals(left, right); + } + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// + /// The object to compare with the current instance. + /// + /// + /// true if and this instance are the same type and represent the + /// same value; otherwise, false. + /// + public override bool Equals(object obj) + { + ImageProperty other = obj as ImageProperty; + + return this.Equals(other); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Name.GetHashCode(); + if (this.Value != null) + { + hashCode = (hashCode * 397) ^ this.Value.GetHashCode(); + } + + return hashCode; + } + } + + /// + /// Returns the fully qualified type name of this instance. + /// + /// + /// A containing a fully qualified type name. + /// + public override string ToString() + { + return $"ImageProperty [ Name={this.Name}, Value={this.Value} ]"; + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// True if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(ImageProperty other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Name.Equals(other.Name) && Equals(this.Value, other.Value); + } + } +} diff --git a/src/ImageSharp46/Image/PixelAccessor.cs b/src/ImageSharp46/Image/PixelAccessor.cs new file mode 100644 index 000000000..fec50b972 --- /dev/null +++ b/src/ImageSharp46/Image/PixelAccessor.cs @@ -0,0 +1,398 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Provides per-pixel access to generic pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public unsafe class PixelAccessor : IDisposable + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The pointer to the pixel buffer. + /// + private IntPtr dataPointer; + + /// + /// The position of the first pixel in the image. + /// + private byte* pixelsBase; + + /// + /// Provides a way to access the pixels from unmanaged memory. + /// + private GCHandle pixelsHandle; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The image to provide pixel access for. + public PixelAccessor(ImageBase image) + { + Guard.NotNull(image, nameof(image)); + Guard.MustBeGreaterThan(image.Width, 0, "image width"); + Guard.MustBeGreaterThan(image.Height, 0, "image height"); + + this.Width = image.Width; + this.Height = image.Height; + this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); + this.pixelsBase = (byte*)this.dataPointer.ToPointer(); + this.PixelSize = Unsafe.SizeOf(); + this.RowStride = this.Width * this.PixelSize; + } + + /// + /// Finalizes an instance of the class. + /// + ~PixelAccessor() + { + this.Dispose(); + } + + /// + /// Gets the pointer to the pixel buffer. + /// + public IntPtr DataPointer => this.dataPointer; + + /// + /// Gets the size of a single pixel in the number of bytes. + /// + public int PixelSize { get; } + + /// + /// Gets the width of one row in the number of bytes. + /// + public int RowStride { get; } + + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets or sets the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. + /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. + /// The at the specified position. + public TColor this[int x, int y] + { + get { return Unsafe.Read(this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf())); } + set { Unsafe.Write(this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf()), value); } + } + + /// + /// Copies a block of pixels at the specified position. + /// + /// The x-coordinate of the source image. + /// The y-coordinate of the source image. + /// The target pixel buffer accessor. + /// The x-coordinate of the target image. + /// The y-coordinate of the target image. + /// The number of pixels to copy + public void CopyBlock(int sourceX, int sourceY, PixelAccessor target, int targetX, int targetY, int pixelCount) + { + int size = Unsafe.SizeOf(); + byte* sourcePtr = this.pixelsBase + (((sourceY * this.Width) + sourceX) * size); + byte* targetPtr = target.pixelsBase + (((targetY * target.Width) + targetX) * size); + uint byteCount = (uint)(pixelCount * size); + + Unsafe.CopyBlock(targetPtr, sourcePtr, byteCount); + } + + /// + /// Copies an entire image. + /// + /// The target pixel buffer accessor. + public void CopyImage(PixelAccessor target) + { + this.CopyBlock(0, 0, target, 0, 0, target.Width * target.Height); + } + + /// + /// Copied a row of pixels from the image. + /// + /// The row. + /// The target row index. + /// + /// Thrown when an unsupported component order value is passed. + /// + public void CopyFrom(PixelRow row, int targetY) + { + switch (row.ComponentOrder) + { + case ComponentOrder.ZYX: + this.CopyFromZYX(row, targetY, Math.Min(row.Width, this.Width)); + break; + case ComponentOrder.ZYXW: + this.CopyFromZYXW(row, targetY, Math.Min(row.Width, this.Width)); + break; + case ComponentOrder.XYZ: + this.CopyFromXYZ(row, targetY, Math.Min(row.Width, this.Width)); + break; + case ComponentOrder.XYZW: + this.CopyFromXYZW(row, targetY, Math.Min(row.Width, this.Width)); + break; + default: + throw new NotSupportedException(); + } + } + + /// + /// Copied a row of pixels to the image. + /// + /// The row. + /// The source row index. + /// + /// Thrown when an unsupported component order value is passed. + /// + public void CopyTo(PixelRow row, int sourceY) + { + switch (row.ComponentOrder) + { + case ComponentOrder.ZYX: + this.CopyToZYX(row, sourceY, Math.Min(row.Width, this.Width)); + break; + case ComponentOrder.ZYXW: + this.CopyToZYXW(row, sourceY, Math.Min(row.Width, this.Width)); + break; + case ComponentOrder.XYZ: + this.CopyToXYZ(row, sourceY, Math.Min(row.Width, this.Width)); + break; + case ComponentOrder.XYZW: + this.CopyToXYZW(row, sourceY, Math.Min(row.Width, this.Width)); + break; + default: + throw new NotSupportedException(); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + this.dataPointer = IntPtr.Zero; + this.pixelsBase = null; + + // Note disposing is done. + this.isDisposed = true; + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Copies from a row in format. + /// + /// The row. + /// The target row index. + /// The width. + protected virtual void CopyFromZYX(PixelRow row, int targetY, int width) + { + byte* source = row.PixelBase; + byte* destination = this.GetRowPointer(targetY); + + TColor packed = default(TColor); + int size = Unsafe.SizeOf(); + + for (int x = 0; x < width; x++) + { + packed.PackFromBytes(*(source + 2), *(source + 1), *source, 255); + Unsafe.Write(destination, packed); + + source += 3; + destination += size; + } + } + + /// + /// Copies from a row in format. + /// + /// The row. + /// The target row index. + /// The width. + protected virtual void CopyFromZYXW(PixelRow row, int targetY, int width) + { + byte* source = row.PixelBase; + byte* destination = this.GetRowPointer(targetY); + + TColor packed = default(TColor); + int size = Unsafe.SizeOf(); + + for (int x = 0; x < width; x++) + { + packed.PackFromBytes(*(source + 2), *(source + 1), *source, *(source + 3)); + Unsafe.Write(destination, packed); + + source += 4; + destination += size; + } + } + + /// + /// Copies from a row in format. + /// + /// The row. + /// The target row index. + /// The width. + protected virtual void CopyFromXYZ(PixelRow row, int targetY, int width) + { + byte* source = row.PixelBase; + byte* destination = this.GetRowPointer(targetY); + + TColor packed = default(TColor); + int size = Unsafe.SizeOf(); + + for (int x = 0; x < width; x++) + { + packed.PackFromBytes(*source, *(source + 1), *(source + 2), 255); + Unsafe.Write(destination, packed); + + source += 3; + destination += size; + } + } + + /// + /// Copies from a row in format. + /// + /// The row. + /// The target row index. + /// The width. + protected virtual void CopyFromXYZW(PixelRow row, int targetY, int width) + { + byte* source = row.PixelBase; + byte* destination = this.GetRowPointer(targetY); + + TColor packed = default(TColor); + int size = Unsafe.SizeOf(); + + for (int x = 0; x < width; x++) + { + packed.PackFromBytes(*source, *(source + 1), *(source + 2), *(source + 3)); + Unsafe.Write(destination, packed); + + source += 4; + destination += size; + } + } + + /// + /// Copies to a row in format. + /// + /// The row. + /// The target row index. + /// The width. + protected virtual void CopyToZYX(PixelRow row, int sourceY, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + this[x, sourceY].ToBytes(row.Bytes, offset, ComponentOrder.ZYX); + offset += 3; + } + } + + /// + /// Copies to a row in format. + /// + /// The row. + /// The target row index. + /// The width. + protected virtual void CopyToZYXW(PixelRow row, int sourceY, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + this[x, sourceY].ToBytes(row.Bytes, offset, ComponentOrder.ZYXW); + offset += 4; + } + } + + /// + /// Copies to a row in format. + /// + /// The row. + /// The target row index. + /// The width. + protected virtual void CopyToXYZ(PixelRow row, int sourceY, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + this[x, sourceY].ToBytes(row.Bytes, offset, ComponentOrder.XYZ); + offset += 3; + } + } + + /// + /// Copies to a row in format. + /// + /// The row. + /// The target row index. + /// The width. + protected virtual void CopyToXYZW(PixelRow row, int sourceY, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + this[x, sourceY].ToBytes(row.Bytes, offset, ComponentOrder.XYZW); + offset += 4; + } + } + + /// + /// Gets the pointer at the specified row. + /// + /// The target row index. + /// + /// The . + /// + protected byte* GetRowPointer(int targetY) + { + return this.pixelsBase + ((targetY * this.Width) * Unsafe.SizeOf()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Image/PixelRow.cs b/src/ImageSharp46/Image/PixelRow.cs new file mode 100644 index 000000000..4682aaaac --- /dev/null +++ b/src/ImageSharp46/Image/PixelRow.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.IO; + using System.Runtime.InteropServices; + + /// + /// Represents a row of generic pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public sealed unsafe class PixelRow : IDisposable + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Provides a way to access the pixels from unmanaged memory. + /// + private readonly GCHandle pixelsHandle; + + /// + /// The pointer to the pixel buffer. + /// + private IntPtr dataPointer; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The component order. + public PixelRow(int width, ComponentOrder componentOrder) + : this(width, componentOrder, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The component order. + /// The number of bytes to pad each row. + public PixelRow(int width, ComponentOrder componentOrder, int padding) + { + this.Width = width; + this.ComponentOrder = componentOrder; + this.Bytes = new byte[(width * GetComponentCount(componentOrder)) + padding]; + this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned); + + // TODO: Why is Resharper warning us about an impure method call? + this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); + this.PixelBase = (byte*)this.dataPointer.ToPointer(); + } + + /// + /// Finalizes an instance of the class. + /// + ~PixelRow() + { + this.Dispose(); + } + + /// + /// Gets the data in bytes. + /// + public byte[] Bytes { get; } + + /// + /// Gets the pointer to the pixel buffer. + /// + public IntPtr DataPointer => this.dataPointer; + + /// + /// Gets the data pointer. + /// + public byte* PixelBase { get; private set; } + + /// + /// Gets the component order. + /// + public ComponentOrder ComponentOrder { get; } + + /// + /// Gets the width. + /// + public int Width { get; } + + /// + /// Reads the stream to the row. + /// + /// The stream. + public void Read(Stream stream) + { + stream.Read(this.Bytes, 0, this.Bytes.Length); + } + + /// + /// Writes the row to the stream. + /// + /// The stream. + public void Write(Stream stream) + { + stream.Write(this.Bytes, 0, this.Bytes.Length); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + if (this.PixelBase == null) + { + return; + } + + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + this.dataPointer = IntPtr.Zero; + this.PixelBase = null; + + this.isDisposed = true; + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Gets component count for the given order. + /// + /// The component order. + /// + /// The . + /// + /// + /// Thrown if an invalid order is given. + /// + private static int GetComponentCount(ComponentOrder componentOrder) + { + switch (componentOrder) + { + case ComponentOrder.ZYX: + case ComponentOrder.XYZ: + return 3; + case ComponentOrder.ZYXW: + case ComponentOrder.XYZW: + return 4; + } + + throw new NotSupportedException(); + } + } +} diff --git a/src/ImageSharp46/ImageProcessor.cs b/src/ImageSharp46/ImageProcessor.cs new file mode 100644 index 000000000..17395218d --- /dev/null +++ b/src/ImageSharp46/ImageProcessor.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Threading.Tasks; + + /// + /// Allows the application of processors to images. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class ImageProcessor : IImageProcessor + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public virtual ParallelOptions ParallelOptions { get; set; } = Bootstrapper.Instance.ParallelOptions; + + /// + public virtual bool Compand { get; set; } = false; + } +} \ No newline at end of file diff --git a/src/ImageSharp46/ImageSharp46.csproj b/src/ImageSharp46/ImageSharp46.csproj new file mode 100644 index 000000000..b2b5d3755 --- /dev/null +++ b/src/ImageSharp46/ImageSharp46.csproj @@ -0,0 +1,393 @@ + + + + + Debug + AnyCPU + {FBA0B5F6-09C2-4317-8EF6-6ADB9B20E6B1} + Library + Properties + ImageSharp + ImageSharp + v4.6.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + ..\..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll + True + + + + ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll + True + + + + ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll + True + + + + ..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + True + + + ..\..\packages\System.Globalization.Calendars.4.0.1\lib\net46\System.Globalization.Calendars.dll + True + + + ..\..\packages\System.IO.Compression.4.1.0\lib\net46\System.IO.Compression.dll + True + + + + ..\..\packages\System.IO.Compression.ZipFile.4.0.1\lib\net46\System.IO.Compression.ZipFile.dll + True + + + ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll + True + + + ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll + True + + + ..\..\packages\System.Net.Http.4.1.0\lib\net46\System.Net.Http.dll + True + + + ..\..\packages\System.Net.Sockets.4.1.0\lib\net46\System.Net.Sockets.dll + True + + + + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.0.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + True + + + ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.0.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + + + ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll + True + + + ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Ellipse.cs b/src/ImageSharp46/Numerics/Ellipse.cs new file mode 100644 index 000000000..f464c4b26 --- /dev/null +++ b/src/ImageSharp46/Numerics/Ellipse.cs @@ -0,0 +1,174 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + + public struct Ellipse : IEquatable + { + /// + /// The center point. + /// + private Point center; + + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly Ellipse Empty = default(Ellipse); + + public Ellipse(Point center, float radiusX, float radiusY) + { + this.center = center; + this.RadiusX = radiusX; + this.RadiusY = radiusY; + } + + /// + /// Gets the x-radius of this . + /// + public float RadiusX { get; } + + /// + /// Gets the y-radius of this . + /// + public float RadiusY { get; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Ellipse left, Ellipse right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Ellipse left, Ellipse right) + { + return !left.Equals(right); + } + + /// + /// Returns the center point of the given + /// + /// The ellipse + /// + public static Vector2 Center(Ellipse ellipse) + { + return new Vector2(ellipse.center.X, ellipse.center.Y); + } + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The + public bool Contains(int x, int y) + { + if (this.RadiusX <= 0 || this.RadiusY <= 0) + { + return false; + } + + // TODO: SIMD? + // This is a more general form of the circle equation + // X^2/a^2 + Y^2/b^2 <= 1 + Point normalized = new Point(x - this.center.X, y - this.center.Y); + int nX = normalized.X; + int nY = normalized.Y; + + return ((double)(nX * nX) / (this.RadiusX * this.RadiusX)) + + ((double)(nY * nY) / (this.RadiusY * this.RadiusY)) + <= 1.0; + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Ellipse [ Empty ]"; + } + + return + $"Ellipse [ RadiusX={this.RadiusX}, RadiusY={this.RadiusX}, Centre={this.center.X},{this.center.Y} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Ellipse) + { + return this.Equals((Ellipse)obj); + } + + return false; + } + + /// + public bool Equals(Ellipse other) + { + return this.center.Equals(other.center) + && this.RadiusX.Equals(other.RadiusX) + && this.RadiusY.Equals(other.RadiusY); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Ellipse ellipse) + { + unchecked + { + int hashCode = ellipse.center.GetHashCode(); + hashCode = (hashCode * 397) ^ ellipse.RadiusX.GetHashCode(); + hashCode = (hashCode * 397) ^ ellipse.RadiusY.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/src/ImageSharp46/Numerics/LongRational.cs b/src/ImageSharp46/Numerics/LongRational.cs new file mode 100644 index 000000000..f56abc289 --- /dev/null +++ b/src/ImageSharp46/Numerics/LongRational.cs @@ -0,0 +1,355 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + using System.Text; + + /// + /// Represents a number that can be expressed as a fraction + /// + /// + /// This is a very simplified implementation of a rational number designed for use with metadata only. + /// + internal struct LongRational : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// + /// The number above the line in a vulgar fraction showing how many of the parts + /// indicated by the denominator are taken. + /// + /// + /// The number below the line in a vulgar fraction; a divisor. + /// + public LongRational(long numerator, long denominator) + : this(numerator, denominator, false) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The number above the line in a vulgar fraction showing how many of the parts + /// indicated by the denominator are taken. + /// + /// + /// The number below the line in a vulgar fraction; a divisor. + /// + /// + /// Whether to attempt to simplify the fractional parts. + /// + public LongRational(long numerator, long denominator, bool simplify) + : this() + { + this.Numerator = numerator; + this.Denominator = denominator; + + if (simplify) + { + this.Simplify(); + } + } + + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public LongRational(double value, bool bestPrecision) + : this() + { + if (double.IsNaN(value)) + { + this.Numerator = this.Denominator = 0; + return; + } + + if (double.IsPositiveInfinity(value)) + { + this.Numerator = 1; + this.Denominator = 0; + return; + } + + if (double.IsNegativeInfinity(value)) + { + this.Numerator = -1; + this.Denominator = 0; + return; + } + + this.Numerator = 1; + this.Denominator = 1; + + double val = Math.Abs(value); + double df = this.Numerator / (double)this.Denominator; + double epsilon = bestPrecision ? double.Epsilon : .000001; + + while (Math.Abs(df - val) > epsilon) + { + if (df < val) + { + this.Numerator++; + } + else + { + this.Denominator++; + this.Numerator = (int)(val * this.Denominator); + } + + df = this.Numerator / (double)this.Denominator; + } + + if (value < 0.0) + { + this.Numerator *= -1; + } + + this.Simplify(); + } + + /// + /// Gets the numerator of a number. + /// + public long Numerator + { + get; + private set; + } + + /// + /// Gets the denominator of a number. + /// + public long Denominator + { + get; + private set; + } + + /// + /// Gets a value indicating whether this instance is indeterminate. + /// + public bool IsIndeterminate + { + get + { + if (this.Denominator != 0) + { + return false; + } + + return this.Numerator == 0; + } + } + + /// + /// Gets a value indicating whether this instance is an integer (n, 1) + /// + public bool IsInteger => this.Denominator == 1; + + /// + /// Gets a value indicating whether this instance is equal to negative infinity (-1, 0) + /// + public bool IsNegativeInfinity + { + get + { + if (this.Denominator != 0) + { + return false; + } + + return this.Numerator == -1; + } + } + + /// + /// Gets a value indicating whether this instance is equal to positive infinity (1, 0) + /// + public bool IsPositiveInfinity + { + get + { + if (this.Denominator != 0) + { + return false; + } + + return this.Numerator == 1; + } + } + + /// + /// Gets a value indicating whether this instance is equal to 0 (0, 1) + /// + public bool IsZero + { + get + { + if (this.Denominator != 1) + { + return false; + } + + return this.Numerator == 0; + } + } + + /// + public bool Equals(LongRational other) + { + if (this.Denominator == other.Denominator) + { + return this.Numerator == other.Numerator; + } + + if (this.Numerator == 0 && this.Denominator == 0) + { + return other.Numerator == 0 && other.Denominator == 0; + } + + if (other.Numerator == 0 && other.Denominator == 0) + { + return this.Numerator == 0 && this.Denominator == 0; + } + + return (this.Numerator * other.Denominator) == (this.Denominator * other.Numerator); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + return this.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) + { + if (this.IsIndeterminate) + { + return "[ Indeterminate ]"; + } + + if (this.IsPositiveInfinity) + { + return "[ PositiveInfinity ]"; + } + + if (this.IsNegativeInfinity) + { + return "[ NegativeInfinity ]"; + } + + if (this.IsZero) + { + return "0"; + } + + if (this.IsInteger) + { + return this.Numerator.ToString(provider); + } + + StringBuilder sb = new StringBuilder(); + sb.Append(this.Numerator.ToString(provider)); + sb.Append("/"); + sb.Append(this.Denominator.ToString(provider)); + return sb.ToString(); + } + + /// + /// Finds the greatest common divisor of two values. + /// + /// The first value + /// The second value + /// The + private static long GreatestCommonDivisor(long left, long right) + { + return right == 0 ? left : GreatestCommonDivisor(right, left % right); + } + + /// + /// Simplifies the + /// + private void Simplify() + { + if (this.IsIndeterminate) + { + return; + } + + if (this.IsNegativeInfinity) + { + return; + } + + if (this.IsPositiveInfinity) + { + return; + } + + if (this.IsInteger) + { + return; + } + + if (this.IsZero) + { + return; + } + + if (this.Numerator == 0) + { + this.Denominator = 0; + return; + } + + if (this.Numerator == this.Denominator) + { + this.Numerator = 1; + this.Denominator = 1; + } + + long gcd = GreatestCommonDivisor(Math.Abs(this.Numerator), Math.Abs(this.Denominator)); + if (gcd > 1) + { + this.Numerator = this.Numerator / gcd; + this.Denominator = this.Denominator / gcd; + } + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(LongRational rational) + { + return ((rational.Numerator * 397) ^ rational.Denominator).GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Point.cs b/src/ImageSharp46/Numerics/Point.cs new file mode 100644 index 000000000..3cd47659c --- /dev/null +++ b/src/ImageSharp46/Numerics/Point.cs @@ -0,0 +1,282 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents an ordered pair of integer x- and y-coordinates that defines a point in + /// a two-dimensional plane. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Point : IEquatable + { + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly Point Empty = default(Point); + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public Point(int x, int y) + : this() + { + this.X = x; + this.Y = y; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector representing the width and height. + /// + public Point(Vector2 vector) + { + this.X = (int)Math.Round(vector.X); + this.Y = (int)Math.Round(vector.Y); + } + + /// + /// Gets or sets the x-coordinate of this . + /// + public int X { get; set; } + + /// + /// Gets or sets the y-coordinate of this . + /// + public int Y { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Computes the sum of adding two points. + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point operator +(Point left, Point right) + { + return new Point(left.X + right.X, left.Y + right.Y); + } + + /// + /// Computes the difference left by subtracting one point from another. + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point operator -(Point left, Point right) + { + return new Point(left.X - right.X, left.Y - right.Y); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Point left, Point right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Point left, Point right) + { + return !left.Equals(right); + } + + /// + /// Creates a rotation matrix for the given point and angle. + /// + /// The origin point to rotate around + /// Rotation in degrees + /// The rotation + public static Matrix3x2 CreateRotation(Point origin, float degrees) + { + float radians = ImageMaths.DegreesToRadians(degrees); + return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y)); + } + + /// + /// Rotates a point around a given a rotation matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Rotate(Point point, Matrix3x2 rotation) + { + return new Point(Vector2.Transform(new Vector2(point.X, point.Y), rotation)); + } + + /// + /// Rotates a point around a given origin by the specified angle in degrees. + /// + /// The point to rotate + /// The center point to rotate around. + /// The angle in degrees. + /// The rotated + public static Point Rotate(Point point, Point origin, float degrees) + { + return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateRotation(origin, degrees))); + } + + /// + /// Creates a skew matrix for the given point and angle. + /// + /// The origin point to rotate around + /// The x-angle in degrees. + /// The y-angle in degrees. + /// The rotation + public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) + { + float radiansX = ImageMaths.DegreesToRadians(degreesX); + float radiansY = ImageMaths.DegreesToRadians(degreesY); + return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(origin.X, origin.Y)); + } + + /// + /// Skews a point using a given a skew matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Skew(Point point, Matrix3x2 skew) + { + return new Point(Vector2.Transform(new Vector2(point.X, point.Y), skew)); + } + + /// + /// Skews a point around a given origin by the specified angles in degrees. + /// + /// The point to skew. + /// The center point to rotate around. + /// The x-angle in degrees. + /// The y-angle in degrees. + /// The skewed + public static Point Skew(Point point, Point origin, float degreesX, float degreesY) + { + return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateSkew(origin, degreesX, degreesY))); + } + + /// + /// Gets a representation for this . + /// + /// A representation for this object. + public Vector2 ToVector2() + { + return new Vector2(this.X, this.Y); + } + + /// + /// Translates this by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + public void Offset(int dx, int dy) + { + this.X += dx; + this.Y += dy; + } + + /// + /// Translates this by the specified amount. + /// + /// The used offset this . + public void Offset(Point p) + { + this.Offset(p.X, p.Y); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Point [ Empty ]"; + } + + return $"Point [ X={this.X}, Y={this.Y} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Point) + { + return this.Equals((Point)obj); + } + + return false; + } + + /// + public bool Equals(Point other) + { + return this.X == other.X && this.Y == other.Y; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Point point) + { + return point.X ^ point.Y; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Rational.cs b/src/ImageSharp46/Numerics/Rational.cs new file mode 100644 index 000000000..d219a5469 --- /dev/null +++ b/src/ImageSharp46/Numerics/Rational.cs @@ -0,0 +1,189 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + + /// + /// Represents a number that can be expressed as a fraction. + /// + /// + /// This is a very simplified implementation of a rational number designed for use with metadata only. + /// + public struct Rational : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The to create the rational from. + public Rational(uint value) + : this(value, 1) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + public Rational(uint numerator, uint denominator) + : this(numerator, denominator, true) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + /// Specified if the rational should be simplified. + public Rational(uint numerator, uint denominator, bool simplify) + { + LongRational rational = new LongRational(numerator, denominator, simplify); + + this.Numerator = (uint)rational.Numerator; + this.Denominator = (uint)rational.Denominator; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + public Rational(double value) + : this(value, false) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public Rational(double value, bool bestPrecision) + { + LongRational rational = new LongRational(Math.Abs(value), bestPrecision); + + this.Numerator = (uint)rational.Numerator; + this.Denominator = (uint)rational.Denominator; + } + + /// + /// Gets the numerator of a number. + /// + public uint Numerator { get; } + + /// + /// Gets the denominator of a number. + /// + public uint Denominator { get; } + + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator ==(Rational left, Rational right) + { + return Rational.Equals(left, right); + } + + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator !=(Rational left, Rational right) + { + return !Rational.Equals(left, right); + } + + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// + /// The . + /// + public static Rational FromDouble(double value) + { + return new Rational(value, false); + } + + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// Whether to use the best possible precision when parsing the value. + /// + /// The . + /// + public static Rational FromDouble(double value, bool bestPrecision) + { + return new Rational(value, bestPrecision); + } + + /// + public override bool Equals(object obj) + { + if (obj is Rational) + { + return this.Equals((Rational)obj); + } + + return false; + } + + /// + public bool Equals(Rational other) + { + LongRational left = new LongRational(this.Numerator, this.Denominator); + LongRational right = new LongRational(other.Numerator, other.Denominator); + + return left.Equals(right); + } + + /// + public override int GetHashCode() + { + LongRational self = new LongRational(this.Numerator, this.Denominator); + return self.GetHashCode(); + } + + /// + /// Converts a rational number to the nearest . + /// + /// + /// The . + /// + public double ToDouble() + { + return this.Numerator / (double)this.Denominator; + } + + /// + public override string ToString() + { + return this.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) + { + LongRational rational = new LongRational(this.Numerator, this.Denominator); + return rational.ToString(provider); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Rectangle.cs b/src/ImageSharp46/Numerics/Rectangle.cs new file mode 100644 index 000000000..fb623c2eb --- /dev/null +++ b/src/ImageSharp46/Numerics/Rectangle.cs @@ -0,0 +1,291 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Stores a set of four integers that represent the location and size of a rectangle. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Rectangle : IEquatable + { + /// + /// Represents a that has X, Y, Width, and Height values set to zero. + /// + public static readonly Rectangle Empty = default(Rectangle); + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the rectangle. + /// The vertical position of the rectangle. + /// The width of the rectangle. + /// The height of the rectangle. + public Rectangle(int x, int y, int width, int height) + { + this.backingVector = new Vector4(x, y, width, height); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The which specifies the rectangles point in a two-dimensional plane. + /// + /// + /// The which specifies the rectangles height and width. + /// + public Rectangle(Point point, Size size) + { + this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector. + public Rectangle(Vector4 vector) + { + this.backingVector = vector; + } + + /// + /// Gets or sets the x-coordinate of this . + /// + public int X + { + get + { + return (int)this.backingVector.X; + } + + set + { + this.backingVector.X = value; + } + } + + /// + /// Gets or sets the y-coordinate of this . + /// + public int Y + { + get + { + return (int)this.backingVector.Y; + } + + set + { + this.backingVector.Y = value; + } + } + + /// + /// Gets or sets the width of this . + /// + public int Width + { + get + { + return (int)this.backingVector.Z; + } + + set + { + this.backingVector.Z = value; + } + } + + /// + /// Gets or sets the height of this . + /// + public int Height + { + get + { + return (int)this.backingVector.W; + } + + set + { + this.backingVector.W = value; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Gets the y-coordinate of the top edge of this . + /// + public int Top => this.Y; + + /// + /// Gets the x-coordinate of the right edge of this . + /// + public int Right => this.X + this.Width; + + /// + /// Gets the y-coordinate of the bottom edge of this . + /// + public int Bottom => this.Y + this.Height; + + /// + /// Gets the x-coordinate of the left edge of this . + /// + public int Left => this.X; + + /// + /// Computes the sum of adding two rectangles. + /// + /// The rectangle on the left hand of the operand. + /// The rectangle on the right hand of the operand. + /// + /// The + /// + public static Rectangle operator +(Rectangle left, Rectangle right) + { + return new Rectangle(left.backingVector + right.backingVector); + } + + /// + /// Computes the difference left by subtracting one rectangle from another. + /// + /// The rectangle on the left hand of the operand. + /// The rectangle on the right hand of the operand. + /// + /// The + /// + public static Rectangle operator -(Rectangle left, Rectangle right) + { + return new Rectangle(left.backingVector - right.backingVector); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Rectangle left, Rectangle right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Rectangle left, Rectangle right) + { + return !left.Equals(right); + } + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The + public bool Contains(int x, int y) + { + // TODO: SIMD? + return this.X <= x + && x < this.Right + && this.Y <= y + && y < this.Bottom; + } + + /// + /// Returns the center point of the given + /// + /// The rectangle + /// + public static Point Center(Rectangle rectangle) + { + return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Rectangle [ Empty ]"; + } + + return + $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Rectangle) + { + return this.Equals((Rectangle)obj); + } + + return false; + } + + /// + public bool Equals(Rectangle other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Rectangle rectangle) + { + return rectangle.backingVector.GetHashCode(); + } + } +} diff --git a/src/ImageSharp46/Numerics/SignedRational.cs b/src/ImageSharp46/Numerics/SignedRational.cs new file mode 100644 index 000000000..bd2213dce --- /dev/null +++ b/src/ImageSharp46/Numerics/SignedRational.cs @@ -0,0 +1,189 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + + /// + /// Represents a number that can be expressed as a fraction. + /// + /// + /// This is a very simplified implementation of a rational number designed for use with metadata only. + /// + public struct SignedRational : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The to create the rational from. + public SignedRational(int value) + : this(value, 1) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + public SignedRational(int numerator, int denominator) + : this(numerator, denominator, true) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + /// Specified if the rational should be simplified. + public SignedRational(int numerator, int denominator, bool simplify) + { + LongRational rational = new LongRational(numerator, denominator, simplify); + + this.Numerator = (int)rational.Numerator; + this.Denominator = (int)rational.Denominator; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + public SignedRational(double value) + : this(value, false) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public SignedRational(double value, bool bestPrecision) + { + LongRational rational = new LongRational(value, bestPrecision); + + this.Numerator = (int)rational.Numerator; + this.Denominator = (int)rational.Denominator; + } + + /// + /// Gets the numerator of a number. + /// + public int Numerator { get; } + + /// + /// Gets the denominator of a number. + /// + public int Denominator { get; } + + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator ==(SignedRational left, SignedRational right) + { + return SignedRational.Equals(left, right); + } + + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator !=(SignedRational left, SignedRational right) + { + return !SignedRational.Equals(left, right); + } + + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// + /// The . + /// + public static SignedRational FromDouble(double value) + { + return new SignedRational(value, false); + } + + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// Whether to use the best possible precision when parsing the value. + /// + /// The . + /// + public static SignedRational FromDouble(double value, bool bestPrecision) + { + return new SignedRational(value, bestPrecision); + } + + /// + public override bool Equals(object obj) + { + if (obj is SignedRational) + { + return this.Equals((SignedRational)obj); + } + + return false; + } + + /// + public bool Equals(SignedRational other) + { + LongRational left = new LongRational(this.Numerator, this.Denominator); + LongRational right = new LongRational(other.Numerator, other.Denominator); + + return left.Equals(right); + } + + /// + public override int GetHashCode() + { + LongRational self = new LongRational(this.Numerator, this.Denominator); + return self.GetHashCode(); + } + + /// + /// Converts a rational number to the nearest . + /// + /// + /// The . + /// + public double ToDouble() + { + return this.Numerator / (double)this.Denominator; + } + + /// + public override string ToString() + { + return this.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) + { + LongRational rational = new LongRational(this.Numerator, this.Denominator); + return rational.ToString(provider); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Size.cs b/src/ImageSharp46/Numerics/Size.cs new file mode 100644 index 000000000..bae645ac8 --- /dev/null +++ b/src/ImageSharp46/Numerics/Size.cs @@ -0,0 +1,166 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Runtime.CompilerServices; + + /// + /// Stores an ordered pair of integers, which specify a height and width. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Size : IEquatable + { + /// + /// Represents a that has Width and Height values set to zero. + /// + public static readonly Size Empty = default(Size); + + /// + /// Initializes a new instance of the struct. + /// + /// The width of the size. + /// The height of the size. + public Size(int width, int height) + { + this.Width = width; + this.Height = height; + } + + /// + /// Gets or sets the width of this . + /// + public int Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public int Height { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Computes the sum of adding two sizes. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size operator +(Size left, Size right) + { + return new Size(left.Width + right.Width, left.Height + right.Height); + } + + /// + /// Computes the difference left by subtracting one size from another. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size operator -(Size left, Size right) + { + return new Size(left.Width - right.Width, left.Height - right.Height); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Size left, Size right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Size left, Size right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Size [ Empty ]"; + } + + return $"Size [ Width={this.Width}, Height={this.Height} ]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is Size) + { + return this.Equals((Size)obj); + } + + return false; + } + + /// + public bool Equals(Size other) + { + return this.Width == other.Width && this.Height == other.Height; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Size size) + { + return size.Width ^ size.Height; + } + } +} diff --git a/src/ImageSharp46/PixelAccessor.cs b/src/ImageSharp46/PixelAccessor.cs new file mode 100644 index 000000000..5c2ec5065 --- /dev/null +++ b/src/ImageSharp46/PixelAccessor.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// An optimized pixel accessor for the class. + /// + public sealed unsafe class PixelAccessor : PixelAccessor + { + /// + /// Initializes a new instance of the class. + /// + /// The image to provide pixel access for. + public PixelAccessor(ImageBase image) + : base(image) + { + } + + /// + protected override void CopyFromZYX(PixelRow row, int targetY, int width) + { + byte* source = row.PixelBase; + byte* destination = this.GetRowPointer(targetY); + + for (int x = 0; x < width; x++) + { + Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | 255 << 24)); + + source += 3; + destination += 4; + } + } + + /// + protected override void CopyFromZYXW(PixelRow row, int targetY, int width) + { + byte* source = row.PixelBase; + byte* destination = this.GetRowPointer(targetY); + + for (int x = 0; x < width; x++) + { + Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | *(source + 3) << 24)); + + source += 4; + destination += 4; + } + } + + /// + protected override void CopyToZYX(PixelRow row, int sourceY, int width) + { + byte* source = this.GetRowPointer(sourceY); + byte* destination = row.PixelBase; + + for (int x = 0; x < width; x++) + { + *destination = *(source + 2); + *(destination + 1) = *(source + 1); + *(destination + 2) = *(source + 0); + + source += 4; + destination += 3; + } + } + + /// + protected override void CopyToZYXW(PixelRow row, int sourceY, int width) + { + byte* source = this.GetRowPointer(sourceY); + byte* destination = row.PixelBase; + + for (int x = 0; x < width; x++) + { + *destination = *(source + 2); + *(destination + 1) = *(source + 1); + *(destination + 2) = *(source + 0); + *(destination + 3) = *(source + 3); + + source += 4; + destination += 4; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifDataType.cs b/src/ImageSharp46/Profiles/Exif/ExifDataType.cs new file mode 100644 index 000000000..f2d012c6c --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/ExifDataType.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Specifies exif data types. + /// + public enum ExifDataType + { + /// + /// Unknown + /// + Unknown, + + /// + /// Byte + /// + Byte, + + /// + /// Ascii + /// + Ascii, + + /// + /// Short + /// + Short, + + /// + /// Long + /// + Long, + + /// + /// Rational + /// + Rational, + + /// + /// SignedByte + /// + SignedByte, + + /// + /// Undefined + /// + Undefined, + + /// + /// SignedShort + /// + SignedShort, + + /// + /// SignedLong + /// + SignedLong, + + /// + /// SignedRational + /// + SignedRational, + + /// + /// SingleFloat + /// + SingleFloat, + + /// + /// DoubleFloat + /// + DoubleFloat + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifParts.cs b/src/ImageSharp46/Profiles/Exif/ExifParts.cs new file mode 100644 index 000000000..880a43453 --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/ExifParts.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Specifies which parts will be written when the profile is added to an image. + /// + [Flags] + public enum ExifParts + { + /// + /// None + /// + None = 0, + + /// + /// IfdTags + /// + IfdTags = 1, + + /// + /// ExifTags + /// + ExifTags = 4, + + /// + /// GPSTags + /// + GPSTags = 8, + + /// + /// All + /// + All = IfdTags | ExifTags | GPSTags + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifProfile.cs b/src/ImageSharp46/Profiles/Exif/ExifProfile.cs new file mode 100644 index 000000000..7694ef7c6 --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/ExifProfile.cs @@ -0,0 +1,239 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.IO; + + /// + /// Represents an EXIF profile providing access to the collection of values. + /// + public sealed class ExifProfile + { + /// + /// The byte array to read the EXIF profile from. + /// + private readonly byte[] data; + + /// + /// The collection of EXIF values + /// + private Collection values; + + /// + /// The list of invalid EXIF tags + /// + private List invalidTags; + + /// + /// The thumbnail offset position in the byte stream + /// + private int thumbnailOffset; + + /// + /// The thumbnail length in the byte stream + /// + private int thumbnailLength; + + /// + /// Initializes a new instance of the class. + /// + public ExifProfile() + : this((byte[])null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The byte array to read the EXIF profile from. + public ExifProfile(byte[] data) + { + this.Parts = ExifParts.All; + this.data = data; + this.invalidTags = new List(); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another EXIF profile. + /// + /// The other EXIF profile, where the clone should be made from. + /// is null. + public ExifProfile(ExifProfile other) + { + Guard.NotNull(other, nameof(other)); + + this.Parts = other.Parts; + this.thumbnailLength = other.thumbnailLength; + this.thumbnailOffset = other.thumbnailOffset; + this.invalidTags = new List(other.invalidTags); + if (other.values != null) + { + this.values = new Collection(); + foreach (ExifValue value in other.values) + { + this.values.Add(new ExifValue(value)); + } + } + else + { + this.data = other.data; + } + } + + /// + /// Gets or sets which parts will be written when the profile is added to an image. + /// + public ExifParts Parts + { + get; + set; + } + + /// + /// Gets the tags that where found but contained an invalid value. + /// + public IEnumerable InvalidTags => this.invalidTags; + + /// + /// Gets the values of this EXIF profile. + /// + public IEnumerable Values + { + get + { + this.InitializeValues(); + return this.values; + } + } + + /// + /// Returns the thumbnail in the EXIF profile when available. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public Image CreateThumbnail() + where TColor : struct, IPackedPixel + where TPacked : struct + { + this.InitializeValues(); + + if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) + { + return null; + } + + if (this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) + { + return null; + } + + using (MemoryStream memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) + { + return new Image(memStream); + } + } + + /// + /// Returns the value with the specified tag. + /// + /// The tag of the EXIF value. + public ExifValue GetValue(ExifTag tag) + { + foreach (ExifValue exifValue in this.Values) + { + if (exifValue.Tag == tag) + { + return exifValue; + } + } + + return null; + } + + /// + /// Removes the value with the specified tag. + /// + /// The tag of the EXIF value. + public bool RemoveValue(ExifTag tag) + { + this.InitializeValues(); + + for (int i = 0; i < this.values.Count; i++) + { + if (this.values[i].Tag == tag) + { + this.values.RemoveAt(i); + return true; + } + } + + return false; + } + + /// + /// Sets the value of the specified tag. + /// + /// The tag of the EXIF value. + /// The value. + public void SetValue(ExifTag tag, object value) + { + foreach (ExifValue exifValue in this.Values) + { + if (exifValue.Tag == tag) + { + exifValue.Value = value; + return; + } + } + + ExifValue newExifValue = ExifValue.Create(tag, value); + this.values.Add(newExifValue); + } + + /// + /// Converts this instance to a byte array. + /// + /// The + public byte[] ToByteArray() + { + if (this.values == null) + { + return this.data; + } + + if (this.values.Count == 0) + { + return null; + } + + ExifWriter writer = new ExifWriter(this.values, this.Parts); + return writer.GetData(); + } + + private void InitializeValues() + { + if (this.values != null) + { + return; + } + + if (this.data == null) + { + this.values = new Collection(); + return; + } + + ExifReader reader = new ExifReader(); + this.values = reader.Read(this.data); + this.invalidTags = new List(reader.InvalidTags); + this.thumbnailOffset = (int)reader.ThumbnailOffset; + this.thumbnailLength = (int)reader.ThumbnailLength; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifReader.cs b/src/ImageSharp46/Profiles/Exif/ExifReader.cs new file mode 100644 index 000000000..eef6f6f3a --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/ExifReader.cs @@ -0,0 +1,515 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Text; + + /// + /// Reads and parses EXIF data from a byte array + /// + internal sealed class ExifReader + { + private delegate TDataType ConverterMethod(byte[] data); + + private readonly Collection invalidTags = new Collection(); + private byte[] exifData; + private uint currentIndex; + private bool isLittleEndian; + private uint exifOffset; + private uint gpsOffset; + private uint startIndex; + + /// + /// Gets the thumbnail length in the byte stream + /// + public uint ThumbnailLength + { + get; + private set; + } + + /// + /// Gets the thumbnail offset position in the byte stream + /// + public uint ThumbnailOffset + { + get; + private set; + } + + /// + /// Gets the remaining length. + /// + private int RemainingLength + { + get + { + if (this.currentIndex >= this.exifData.Length) + { + return 0; + } + + return this.exifData.Length - (int)this.currentIndex; + } + } + + /// + /// Reads and returns the collection of EXIF values. + /// + /// The data. + /// + /// The . + /// + public Collection Read(byte[] data) + { + Collection result = new Collection(); + + this.exifData = data; + + if (this.GetString(4) == "Exif") + { + if (this.GetShort() != 0) + { + return result; + } + + this.startIndex = 6; + } + else + { + this.currentIndex = 0; + } + + this.isLittleEndian = this.GetString(2) == "II"; + + if (this.GetShort() != 0x002A) + { + return result; + } + + uint ifdOffset = this.GetLong(); + this.AddValues(result, ifdOffset); + + uint thumbnailOffset = this.GetLong(); + this.GetThumbnail(thumbnailOffset); + + if (this.exifOffset != 0) + { + this.AddValues(result, this.exifOffset); + } + + if (this.gpsOffset != 0) + { + this.AddValues(result, this.gpsOffset); + } + + return result; + } + + public IEnumerable InvalidTags => this.invalidTags; + + private void AddValues(Collection values, uint index) + { + this.currentIndex = this.startIndex + index; + ushort count = this.GetShort(); + + for (ushort i = 0; i < count; i++) + { + ExifValue value = this.CreateValue(); + if (value == null) + { + continue; + } + + bool duplicate = false; + foreach (ExifValue val in values) + { + if (val.Tag == value.Tag) + { + duplicate = true; + break; + } + } + + if (duplicate) + { + continue; + } + + if (value.Tag == ExifTag.SubIFDOffset) + { + if (value.DataType == ExifDataType.Long) + { + this.exifOffset = (uint)value.Value; + } + } + else if (value.Tag == ExifTag.GPSIFDOffset) + { + if (value.DataType == ExifDataType.Long) + { + this.gpsOffset = (uint)value.Value; + } + } + else + { + values.Add(value); + } + } + } + + private object ConvertValue(ExifDataType dataType, byte[] data, uint numberOfComponents) + { + if (data == null || data.Length == 0) + { + return null; + } + + switch (dataType) + { + case ExifDataType.Unknown: + return null; + case ExifDataType.Ascii: + return ToString(data); + case ExifDataType.Byte: + if (numberOfComponents == 1) + { + return ToByte(data); + } + + return data; + case ExifDataType.DoubleFloat: + if (numberOfComponents == 1) + { + return this.ToDouble(data); + } + + return ToArray(dataType, data, this.ToDouble); + case ExifDataType.Long: + if (numberOfComponents == 1) + { + return this.ToLong(data); + } + + return ToArray(dataType, data, this.ToLong); + case ExifDataType.Rational: + if (numberOfComponents == 1) + { + return this.ToRational(data); + } + + return ToArray(dataType, data, this.ToRational); + case ExifDataType.Short: + if (numberOfComponents == 1) + { + return this.ToShort(data); + } + + return ToArray(dataType, data, this.ToShort); + case ExifDataType.SignedByte: + if (numberOfComponents == 1) + { + return this.ToSignedByte(data); + } + + return ToArray(dataType, data, this.ToSignedByte); + case ExifDataType.SignedLong: + if (numberOfComponents == 1) + { + return this.ToSignedLong(data); + } + + return ToArray(dataType, data, this.ToSignedLong); + case ExifDataType.SignedRational: + if (numberOfComponents == 1) + { + return this.ToSignedRational(data); + } + + return ToArray(dataType, data, this.ToSignedRational); + case ExifDataType.SignedShort: + if (numberOfComponents == 1) + { + return this.ToSignedShort(data); + } + + return ToArray(dataType, data, this.ToSignedShort); + case ExifDataType.SingleFloat: + if (numberOfComponents == 1) + { + return this.ToSingle(data); + } + + return ToArray(dataType, data, this.ToSingle); + case ExifDataType.Undefined: + if (numberOfComponents == 1) + { + return ToByte(data); + } + + return data; + default: + throw new NotSupportedException(); + } + } + + private ExifValue CreateValue() + { + if (this.RemainingLength < 12) + { + return null; + } + + ExifTag tag = this.ToEnum(this.GetShort(), ExifTag.Unknown); + ExifDataType dataType = this.ToEnum(this.GetShort(), ExifDataType.Unknown); + object value; + + if (dataType == ExifDataType.Unknown) + { + return new ExifValue(tag, dataType, null, false); + } + + uint numberOfComponents = this.GetLong(); + + uint size = numberOfComponents * ExifValue.GetSize(dataType); + byte[] data = this.GetBytes(4); + + if (size > 4) + { + uint oldIndex = this.currentIndex; + this.currentIndex = this.ToLong(data) + this.startIndex; + if (this.RemainingLength < size) + { + this.invalidTags.Add(tag); + this.currentIndex = oldIndex; + return null; + } + + value = this.ConvertValue(dataType, this.GetBytes(size), numberOfComponents); + this.currentIndex = oldIndex; + } + else + { + value = this.ConvertValue(dataType, data, numberOfComponents); + } + + bool isArray = value != null && numberOfComponents > 1; + return new ExifValue(tag, dataType, value, isArray); + } + + private TEnum ToEnum(int value, TEnum defaultValue) + where TEnum : struct + { + TEnum enumValue = (TEnum)(object)value; + if (Enum.GetValues(typeof(TEnum)).Cast().Any(v => v.Equals(enumValue))) + { + return enumValue; + } + + return defaultValue; + } + + private byte[] GetBytes(uint length) + { + if (this.currentIndex + length > (uint)this.exifData.Length) + { + return null; + } + + byte[] data = new byte[length]; + Array.Copy(this.exifData, (int)this.currentIndex, data, 0, (int)length); + this.currentIndex += length; + + return data; + } + + private uint GetLong() + { + return this.ToLong(this.GetBytes(4)); + } + + private ushort GetShort() + { + return this.ToShort(this.GetBytes(2)); + } + + private string GetString(uint length) + { + return ToString(this.GetBytes(length)); + } + + private void GetThumbnail(uint offset) + { + Collection values = new Collection(); + this.AddValues(values, offset); + + foreach (ExifValue value in values) + { + if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long)) + { + this.ThumbnailOffset = (uint)value.Value + this.startIndex; + } + else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long) + { + this.ThumbnailLength = (uint)value.Value; + } + } + } + + private static TDataType[] ToArray(ExifDataType dataType, byte[] data, ConverterMethod converter) + { + int dataTypeSize = (int)ExifValue.GetSize(dataType); + int length = data.Length / dataTypeSize; + + TDataType[] result = new TDataType[length]; + byte[] buffer = new byte[dataTypeSize]; + + for (int i = 0; i < length; i++) + { + Array.Copy(data, i * dataTypeSize, buffer, 0, dataTypeSize); + + result.SetValue(converter(buffer), i); + } + + return result; + } + + private static byte ToByte(byte[] data) + { + return data[0]; + } + + private double ToDouble(byte[] data) + { + if (!this.ValidateArray(data, 8)) + { + return default(double); + } + + return BitConverter.ToDouble(data, 0); + } + + private uint ToLong(byte[] data) + { + if (!this.ValidateArray(data, 4)) + { + return default(uint); + } + + return BitConverter.ToUInt32(data, 0); + } + + private ushort ToShort(byte[] data) + { + if (!this.ValidateArray(data, 2)) + { + return default(ushort); + } + + return BitConverter.ToUInt16(data, 0); + } + + private float ToSingle(byte[] data) + { + if (!this.ValidateArray(data, 4)) + { + return default(float); + } + + return BitConverter.ToSingle(data, 0); + } + + private static string ToString(byte[] data) + { + string result = Encoding.UTF8.GetString(data, 0, data.Length); + int nullCharIndex = result.IndexOf('\0'); + if (nullCharIndex != -1) + { + result = result.Substring(0, nullCharIndex); + } + + return result; + } + + private Rational ToRational(byte[] data) + { + if (!this.ValidateArray(data, 8, 4)) + { + return default(Rational); + } + + uint numerator = BitConverter.ToUInt32(data, 0); + uint denominator = BitConverter.ToUInt32(data, 4); + + return new Rational(numerator, denominator, false); + } + + private sbyte ToSignedByte(byte[] data) + { + return unchecked((sbyte)data[0]); + } + + private int ToSignedLong(byte[] data) + { + if (!this.ValidateArray(data, 4)) + { + return default(int); + } + + return BitConverter.ToInt32(data, 0); + } + + private SignedRational ToSignedRational(byte[] data) + { + if (!this.ValidateArray(data, 8, 4)) + { + return default(SignedRational); + } + + int numerator = BitConverter.ToInt32(data, 0); + int denominator = BitConverter.ToInt32(data, 4); + + return new SignedRational(numerator, denominator, false); + } + + private short ToSignedShort(byte[] data) + { + if (!this.ValidateArray(data, 2)) + { + return default(short); + } + + return BitConverter.ToInt16(data, 0); + } + + private bool ValidateArray(byte[] data, int size) + { + return this.ValidateArray(data, size, size); + } + + private bool ValidateArray(byte[] data, int size, int stepSize) + { + if (data == null || data.Length < size) + { + return false; + } + + if (this.isLittleEndian == BitConverter.IsLittleEndian) + { + return true; + } + + for (int i = 0; i < data.Length; i += stepSize) + { + Array.Reverse(data, i, stepSize); + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifTag.cs b/src/ImageSharp46/Profiles/Exif/ExifTag.cs new file mode 100644 index 000000000..43f725f0c --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/ExifTag.cs @@ -0,0 +1,1547 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// Descriptions from: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html + +namespace ImageSharp +{ + /// + /// All exif tags from the Exif standard 2.2 + /// + public enum ExifTag + { + /// + /// Unknown + /// + Unknown = 0xFFFF, + + /// + /// SubIFDOffset + /// + SubIFDOffset = 0x8769, + + /// + /// GPSIFDOffset + /// + GPSIFDOffset = 0x8825, + + /// + /// SubfileType + /// + [ExifTagDescription((uint)0, "Full-resolution Image")] + [ExifTagDescription((uint)1, "Reduced-resolution image")] + [ExifTagDescription((uint)2, "Single page of multi-page image")] + [ExifTagDescription((uint)3, "Single page of multi-page reduced-resolution image")] + [ExifTagDescription((uint)4, "Transparency mask")] + [ExifTagDescription((uint)5, "Transparency mask of reduced-resolution image")] + [ExifTagDescription((uint)6, "Transparency mask of multi-page image")] + [ExifTagDescription((uint)7, "Transparency mask of reduced-resolution multi-page image")] + [ExifTagDescription((uint)0x10001, "Alternate reduced-resolution image ")] + SubfileType = 0x00FE, + + /// + /// OldSubfileType + /// + [ExifTagDescription((ushort)1, "Full-resolution Image")] + [ExifTagDescription((ushort)2, "Reduced-resolution image")] + [ExifTagDescription((ushort)3, "Single page of multi-page image")] + OldSubfileType = 0x00FF, + + /// + /// ImageWidth + /// + ImageWidth = 0x0100, + + /// + /// ImageLength + /// + ImageLength = 0x0101, + + /// + /// BitsPerSample + /// + BitsPerSample = 0x0102, + + /// + /// Compression + /// + [ExifTagDescription((ushort)1, "Uncompressed")] + [ExifTagDescription((ushort)2, "CCITT 1D")] + [ExifTagDescription((ushort)3, "T4/Group 3 Fax")] + [ExifTagDescription((ushort)4, "T6/Group 4 Fax")] + [ExifTagDescription((ushort)5, "LZW")] + [ExifTagDescription((ushort)6, "JPEG (old-style)")] + [ExifTagDescription((ushort)7, "JPEG")] + [ExifTagDescription((ushort)8, "Adobe Deflate")] + [ExifTagDescription((ushort)9, "JBIG B&W")] + [ExifTagDescription((ushort)10, "JBIG Color")] + [ExifTagDescription((ushort)99, "JPEG")] + [ExifTagDescription((ushort)262, "Kodak 262")] + [ExifTagDescription((ushort)32766, "Next")] + [ExifTagDescription((ushort)32767, "Sony ARW Compressed")] + [ExifTagDescription((ushort)32769, "Packed RAW")] + [ExifTagDescription((ushort)32770, "Samsung SRW Compressed")] + [ExifTagDescription((ushort)32771, "CCIRLEW")] + [ExifTagDescription((ushort)32772, "Samsung SRW Compressed 2")] + [ExifTagDescription((ushort)32773, "PackBits")] + [ExifTagDescription((ushort)32809, "Thunderscan")] + [ExifTagDescription((ushort)32867, "Kodak KDC Compressed")] + [ExifTagDescription((ushort)32895, "IT8CTPAD")] + [ExifTagDescription((ushort)32896, "IT8LW")] + [ExifTagDescription((ushort)32897, "IT8MP")] + [ExifTagDescription((ushort)32898, "IT8BL")] + [ExifTagDescription((ushort)32908, "PixarFilm")] + [ExifTagDescription((ushort)32909, "PixarLog")] + [ExifTagDescription((ushort)32946, "Deflate")] + [ExifTagDescription((ushort)32947, "DCS")] + [ExifTagDescription((ushort)34661, "JBIG")] + [ExifTagDescription((ushort)34676, "SGILog")] + [ExifTagDescription((ushort)34677, "SGILog24")] + [ExifTagDescription((ushort)34712, "JPEG 2000")] + [ExifTagDescription((ushort)34713, "Nikon NEF Compressed")] + [ExifTagDescription((ushort)34715, "JBIG2 TIFF FX")] + [ExifTagDescription((ushort)34718, "Microsoft Document Imaging (MDI) Binary Level Codec")] + [ExifTagDescription((ushort)34719, "Microsoft Document Imaging (MDI) Progressive Transform Codec")] + [ExifTagDescription((ushort)34720, "Microsoft Document Imaging (MDI) Vector")] + [ExifTagDescription((ushort)34892, "Lossy JPEG")] + [ExifTagDescription((ushort)65000, "Kodak DCR Compressed")] + [ExifTagDescription((ushort)65535, "Pentax PEF Compressed")] + Compression = 0x0103, + + /// + /// PhotometricInterpretation + /// + [ExifTagDescription((ushort)0, "WhiteIsZero")] + [ExifTagDescription((ushort)1, "BlackIsZero")] + [ExifTagDescription((ushort)2, "RGB")] + [ExifTagDescription((ushort)3, "RGB Palette")] + [ExifTagDescription((ushort)4, "Transparency Mask")] + [ExifTagDescription((ushort)5, "CMYK")] + [ExifTagDescription((ushort)6, "YCbCr")] + [ExifTagDescription((ushort)8, "CIELab")] + [ExifTagDescription((ushort)9, "ICCLab")] + [ExifTagDescription((ushort)10, "TULab")] + [ExifTagDescription((ushort)32803, "Color Filter Array")] + [ExifTagDescription((ushort)32844, "Pixar LogL")] + [ExifTagDescription((ushort)32845, "Pixar LogLuv")] + [ExifTagDescription((ushort)34892, "Linear Raw")] + PhotometricInterpretation = 0x0106, + + /// + /// Thresholding + /// + [ExifTagDescription((ushort)1, "No dithering or halftoning")] + [ExifTagDescription((ushort)2, "Ordered dither or halftone")] + [ExifTagDescription((ushort)3, "Randomized dither")] + Thresholding = 0x0107, + + /// + /// CellWidth + /// + CellWidth = 0x0108, + + /// + /// CellLength + /// + CellLength = 0x0109, + + /// + /// FillOrder + /// + [ExifTagDescription((ushort)1, "Normal")] + [ExifTagDescription((ushort)2, "Reversed")] + FillOrder = 0x010A, + + /// + /// DocumentName + /// + DocumentName = 0x010D, + + /// + /// ImageDescription + /// + ImageDescription = 0x010E, + + /// + /// Make + /// + Make = 0x010F, + + /// + /// Model + /// + Model = 0x0110, + + /// + /// StripOffsets + /// + StripOffsets = 0x0111, + + /// + /// Orientation + /// + [ExifTagDescription((ushort)1, "Horizontal (normal)")] + [ExifTagDescription((ushort)2, "Mirror horizontal")] + [ExifTagDescription((ushort)3, "Rotate 180")] + [ExifTagDescription((ushort)4, "Mirror vertical")] + [ExifTagDescription((ushort)5, "Mirror horizontal and rotate 270 CW")] + [ExifTagDescription((ushort)6, "Rotate 90 CW")] + [ExifTagDescription((ushort)7, "Mirror horizontal and rotate 90 CW")] + [ExifTagDescription((ushort)8, "Rotate 270 CW")] + Orientation = 0x0112, + + /// + /// SamplesPerPixel + /// + SamplesPerPixel = 0x0115, + + /// + /// RowsPerStrip + /// + RowsPerStrip = 0x0116, + + /// + /// StripByteCounts + /// + StripByteCounts = 0x0117, + + /// + /// MinSampleValue + /// + MinSampleValue = 0x0118, + + /// + /// MaxSampleValue + /// + MaxSampleValue = 0x0119, + + /// + /// XResolution + /// + XResolution = 0x011A, + + /// + /// YResolution + /// + YResolution = 0x011B, + + /// + /// PlanarConfiguration + /// + [ExifTagDescription((ushort)1, "Chunky")] + [ExifTagDescription((ushort)2, "Planar")] + PlanarConfiguration = 0x011C, + + /// + /// PageName + /// + PageName = 0x011D, + + /// + /// XPosition + /// + XPosition = 0x011E, + + /// + /// YPosition + /// + YPosition = 0x011F, + + /// + /// FreeOffsets + /// + FreeOffsets = 0x0120, + + /// + /// FreeByteCounts + /// + FreeByteCounts = 0x0121, + + /// + /// GrayResponseUnit + /// + [ExifTagDescription((ushort)1, "0.1")] + [ExifTagDescription((ushort)2, "0.001")] + [ExifTagDescription((ushort)3, "0.0001")] + [ExifTagDescription((ushort)4, "1e-05")] + [ExifTagDescription((ushort)5, "1e-06")] + GrayResponseUnit = 0x0122, + + /// + /// GrayResponseCurve + /// + GrayResponseCurve = 0x0123, + + /// + /// T4Options + /// + [ExifTagDescription((uint)0, "2-Dimensional encoding")] + [ExifTagDescription((uint)1, "Uncompressed")] + [ExifTagDescription((uint)2, "Fill bits added")] + T4Options = 0x0124, + + /// + /// T6Options + /// + [ExifTagDescription((uint)1, "Uncompressed")] + T6Options = 0x0125, + + /// + /// ResolutionUnit + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + ResolutionUnit = 0x0128, + + /// + /// PageNumber + /// + PageNumber = 0x0129, + + /// + /// ColorResponseUnit + /// + ColorResponseUnit = 0x012C, + + /// + /// TransferFunction + /// + TransferFunction = 0x012D, + + /// + /// Software + /// + Software = 0x0131, + + /// + /// DateTime + /// + DateTime = 0x0132, + + /// + /// Artist + /// + Artist = 0x013B, + + /// + /// HostComputer + /// + HostComputer = 0x013C, + + /// + /// Predictor + /// + Predictor = 0x013D, + + /// + /// WhitePoint + /// + WhitePoint = 0x013E, + + /// + /// PrimaryChromaticities + /// + PrimaryChromaticities = 0x013F, + + /// + /// ColorMap + /// + ColorMap = 0x0140, + + /// + /// HalftoneHints + /// + HalftoneHints = 0x0141, + + /// + /// TileWidth + /// + TileWidth = 0x0142, + + /// + /// TileLength + /// + TileLength = 0x0143, + + /// + /// TileOffsets + /// + TileOffsets = 0x0144, + + /// + /// TileByteCounts + /// + TileByteCounts = 0x0145, + + /// + /// BadFaxLines + /// + BadFaxLines = 0x0146, + + /// + /// CleanFaxData + /// + [ExifTagDescription((uint)0, "Clean")] + [ExifTagDescription((uint)1, "Regenerated")] + [ExifTagDescription((uint)2, "Unclean")] + CleanFaxData = 0x0147, + + /// + /// ConsecutiveBadFaxLines + /// + ConsecutiveBadFaxLines = 0x0148, + + /// + /// InkSet + /// + [ExifTagDescription((ushort)1, "CMYK")] + [ExifTagDescription((ushort)2, "Not CMYK")] + InkSet = 0x014C, + + /// + /// InkNames + /// + InkNames = 0x014D, + + /// + /// NumberOfInks + /// + NumberOfInks = 0x014E, + + /// + /// DotRange + /// + DotRange = 0x0150, + + /// + /// TargetPrinter + /// + TargetPrinter = 0x0151, + + /// + /// ExtraSamples + /// + [ExifTagDescription((ushort)0, "Unspecified")] + [ExifTagDescription((ushort)1, "Associated Alpha")] + [ExifTagDescription((ushort)2, "Unassociated Alpha")] + ExtraSamples = 0x0152, + + /// + /// SampleFormat + /// + [ExifTagDescription((ushort)1, "Unsigned")] + [ExifTagDescription((ushort)2, "Signed")] + [ExifTagDescription((ushort)3, "Float")] + [ExifTagDescription((ushort)4, "Undefined")] + [ExifTagDescription((ushort)5, "Complex int")] + [ExifTagDescription((ushort)6, "Complex float")] + SampleFormat = 0x0153, + + /// + /// SMinSampleValue + /// + SMinSampleValue = 0x0154, + + /// + /// SMaxSampleValue + /// + SMaxSampleValue = 0x0155, + + /// + /// TransferRange + /// + TransferRange = 0x0156, + + /// + /// ClipPath + /// + ClipPath = 0x0157, + + /// + /// XClipPathUnits + /// + XClipPathUnits = 0x0158, + + /// + /// YClipPathUnits + /// + YClipPathUnits = 0x0159, + + /// + /// Indexed + /// + [ExifTagDescription((ushort)0, "Not indexed")] + [ExifTagDescription((ushort)1, "Indexed")] + Indexed = 0x015A, + + /// + /// JPEGTables + /// + JPEGTables = 0x015B, + + /// + /// OPIProxy + /// + [ExifTagDescription((ushort)0, "Higher resolution image does not exist")] + [ExifTagDescription((ushort)1, "Higher resolution image exists")] + OPIProxy = 0x015F, + + /// + /// ProfileType + /// + [ExifTagDescription((uint)0, "Unspecified")] + [ExifTagDescription((uint)1, "Group 3 FAX")] + ProfileType = 0x0191, + + /// + /// FaxProfile + /// + [ExifTagDescription((byte)0, "Unknown")] + [ExifTagDescription((byte)1, "Minimal B&W lossless, S")] + [ExifTagDescription((byte)2, "Extended B&W lossless, F")] + [ExifTagDescription((byte)3, "Lossless JBIG B&W, J")] + [ExifTagDescription((byte)4, "Lossy color and grayscale, C")] + [ExifTagDescription((byte)5, "Lossless color and grayscale, L")] + [ExifTagDescription((byte)6, "Mixed raster content, M")] + [ExifTagDescription((byte)7, "Profile T")] + [ExifTagDescription((byte)255, "Multi Profiles")] + FaxProfile = 0x0192, + + /// + /// CodingMethods + /// + [ExifTagDescription((ulong)0, "Unspecified compression")] + [ExifTagDescription((ulong)1, "Modified Huffman")] + [ExifTagDescription((ulong)2, "Modified Read")] + [ExifTagDescription((ulong)4, "Modified MR")] + [ExifTagDescription((ulong)8, "JBIG")] + [ExifTagDescription((ulong)16, "Baseline JPEG")] + [ExifTagDescription((ulong)32, "JBIG color")] + CodingMethods = 0x0193, + + /// + /// VersionYear + /// + VersionYear = 0x0194, + + /// + /// ModeNumber + /// + ModeNumber = 0x0195, + + /// + /// Decode + /// + Decode = 0x01B1, + + /// + /// DefaultImageColor + /// + DefaultImageColor = 0x01B2, + + /// + /// T82ptions + /// + T82ptions = 0x01B3, + + /// + /// JPEGProc + /// + [ExifTagDescription((ushort)1, "Baseline")] + [ExifTagDescription((ushort)14, "Lossless")] + JPEGProc = 0x0200, + + /// + /// JPEGInterchangeFormat + /// + JPEGInterchangeFormat = 0x0201, + + /// + /// JPEGInterchangeFormatLength + /// + JPEGInterchangeFormatLength = 0x0202, + + /// + /// JPEGRestartInterval + /// + JPEGRestartInterval = 0x0203, + + /// + /// JPEGLosslessPredictors + /// + JPEGLosslessPredictors = 0x0205, + + /// + /// JPEGPointTransforms + /// + JPEGPointTransforms = 0x0206, + + /// + /// JPEGQTables + /// + JPEGQTables = 0x0207, + + /// + /// JPEGDCTables + /// + JPEGDCTables = 0x0208, + + /// + /// JPEGACTables + /// + JPEGACTables = 0x0209, + + /// + /// YCbCrCoefficients + /// + YCbCrCoefficients = 0x0211, + + /// + /// YCbCrSubsampling + /// + YCbCrSubsampling = 0x0212, + + /// + /// YCbCrPositioning + /// + [ExifTagDescription((ushort)1, "Centered")] + [ExifTagDescription((ushort)2, "Co-sited")] + YCbCrPositioning = 0x0213, + + /// + /// ReferenceBlackWhite + /// + ReferenceBlackWhite = 0x0214, + + /// + /// StripRowCounts + /// + StripRowCounts = 0x022F, + + /// + /// XMP + /// + XMP = 0x02BC, + + /// + /// Rating + /// + Rating = 0x4746, + + /// + /// RatingPercent + /// + RatingPercent = 0x4749, + + /// + /// ImageID + /// + ImageID = 0x800D, + + /// + /// CFARepeatPatternDim + /// + CFARepeatPatternDim = 0x828D, + + /// + /// CFAPattern2 + /// + CFAPattern2 = 0x828E, + + /// + /// BatteryLevel + /// + BatteryLevel = 0x828F, + + /// + /// Copyright + /// + Copyright = 0x8298, + + /// + /// ExposureTime + /// + ExposureTime = 0x829A, + + /// + /// FNumber + /// + FNumber = 0x829D, + + /// + /// MDFileTag + /// + MDFileTag = 0x82A5, + + /// + /// MDScalePixel + /// + MDScalePixel = 0x82A6, + + /// + /// MDLabName + /// + MDLabName = 0x82A8, + + /// + /// MDSampleInfo + /// + MDSampleInfo = 0x82A9, + + /// + /// MDPrepDate + /// + MDPrepDate = 0x82AA, + + /// + /// MDPrepTime + /// + MDPrepTime = 0x82AB, + + /// + /// MDFileUnits + /// + MDFileUnits = 0x82AC, + + /// + /// PixelScale + /// + PixelScale = 0x830E, + + /// + /// IntergraphPacketData + /// + IntergraphPacketData = 0x847E, + + /// + /// IntergraphRegisters + /// + IntergraphRegisters = 0x847F, + + /// + /// IntergraphMatrix + /// + IntergraphMatrix = 0x8480, + + /// + /// ModelTiePoint + /// + ModelTiePoint = 0x8482, + + /// + /// SEMInfo + /// + SEMInfo = 0x8546, + + /// + /// ModelTransform + /// + ModelTransform = 0x85D8, + + /// + /// ImageLayer + /// + ImageLayer = 0x87AC, + + /// + /// ExposureProgram + /// + [ExifTagDescription((ushort)0, "Not Defined")] + [ExifTagDescription((ushort)1, "Manual")] + [ExifTagDescription((ushort)2, "Program AE")] + [ExifTagDescription((ushort)3, "Aperture-priority AE")] + [ExifTagDescription((ushort)4, "Shutter speed priority AE")] + [ExifTagDescription((ushort)5, "Creative (Slow speed)")] + [ExifTagDescription((ushort)6, "Action (High speed)")] + [ExifTagDescription((ushort)7, "Portrait")] + [ExifTagDescription((ushort)8, "Landscape")] + [ExifTagDescription((ushort)9, "Bulb")] + ExposureProgram = 0x8822, + + /// + /// SpectralSensitivity + /// + SpectralSensitivity = 0x8824, + + /// + /// ISOSpeedRatings + /// + ISOSpeedRatings = 0x8827, + + /// + /// OECF + /// + OECF = 0x8828, + + /// + /// Interlace + /// + Interlace = 0x8829, + + /// + /// TimeZoneOffset + /// + TimeZoneOffset = 0x882A, + + /// + /// SelfTimerMode + /// + SelfTimerMode = 0x882B, + + /// + /// SensitivityType + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Standard Output Sensitivity")] + [ExifTagDescription((ushort)2, "Recommended Exposure Index")] + [ExifTagDescription((ushort)3, "ISO Speed")] + [ExifTagDescription((ushort)4, "Standard Output Sensitivity and Recommended Exposure Index")] + [ExifTagDescription((ushort)5, "Standard Output Sensitivity and ISO Speed")] + [ExifTagDescription((ushort)6, "Recommended Exposure Index and ISO Speed")] + [ExifTagDescription((ushort)7, "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed")] + SensitivityType = 0x8830, + + /// + /// StandardOutputSensitivity + /// + StandardOutputSensitivity = 0x8831, + + /// + /// RecommendedExposureIndex + /// + RecommendedExposureIndex = 0x8832, + + /// + /// ISOSpeed + /// + ISOSpeed = 0x8833, + + /// + /// ISOSpeedLatitudeyyy + /// + ISOSpeedLatitudeyyy = 0x8834, + + /// + /// ISOSpeedLatitudezzz + /// + ISOSpeedLatitudezzz = 0x8835, + + /// + /// FaxRecvParams + /// + FaxRecvParams = 0x885C, + + /// + /// FaxSubaddress + /// + FaxSubaddress = 0x885D, + + /// + /// FaxRecvTime + /// + FaxRecvTime = 0x885E, + + /// + /// ExifVersion + /// + ExifVersion = 0x9000, + + /// + /// DateTimeOriginal + /// + DateTimeOriginal = 0x9003, + + /// + /// DateTimeDigitized + /// + DateTimeDigitized = 0x9004, + + /// + /// OffsetTime + /// + OffsetTime = 0x9010, + + /// + /// OffsetTimeOriginal + /// + OffsetTimeOriginal = 0x9011, + + /// + /// OffsetTimeDigitized + /// + OffsetTimeDigitized = 0x9012, + + /// + /// ComponentsConfiguration + /// + ComponentsConfiguration = 0x9101, + + /// + /// CompressedBitsPerPixel + /// + CompressedBitsPerPixel = 0x9102, + + /// + /// ShutterSpeedValue + /// + ShutterSpeedValue = 0x9201, + + /// + /// ApertureValue + /// + ApertureValue = 0x9202, + + /// + /// BrightnessValue + /// + BrightnessValue = 0x9203, + + /// + /// ExposureBiasValue + /// + ExposureBiasValue = 0x9204, + + /// + /// MaxApertureValue + /// + MaxApertureValue = 0x9205, + + /// + /// SubjectDistance + /// + SubjectDistance = 0x9206, + + /// + /// MeteringMode + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Average")] + [ExifTagDescription((ushort)2, "Center-weighted average")] + [ExifTagDescription((ushort)3, "Spot")] + [ExifTagDescription((ushort)4, "Multi-spot")] + [ExifTagDescription((ushort)5, "Multi-segment")] + [ExifTagDescription((ushort)6, "Partial")] + [ExifTagDescription((ushort)255, "Other")] + MeteringMode = 0x9207, + + /// + /// LightSource + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Daylight")] + [ExifTagDescription((ushort)2, "Fluorescent")] + [ExifTagDescription((ushort)3, "Tungsten (Incandescent)")] + [ExifTagDescription((ushort)4, "Flash")] + [ExifTagDescription((ushort)9, "Fine Weather")] + [ExifTagDescription((ushort)10, "Cloudy")] + [ExifTagDescription((ushort)11, "Shade")] + [ExifTagDescription((ushort)12, "Daylight Fluorescent")] + [ExifTagDescription((ushort)13, "Day White Fluorescent")] + [ExifTagDescription((ushort)14, "Cool White Fluorescent")] + [ExifTagDescription((ushort)15, "White Fluorescent")] + [ExifTagDescription((ushort)16, "Warm White Fluorescent")] + [ExifTagDescription((ushort)17, "Standard Light A")] + [ExifTagDescription((ushort)18, "Standard Light B")] + [ExifTagDescription((ushort)19, "Standard Light C")] + [ExifTagDescription((ushort)20, "D55")] + [ExifTagDescription((ushort)21, "D65")] + [ExifTagDescription((ushort)22, "D75")] + [ExifTagDescription((ushort)23, "D50")] + [ExifTagDescription((ushort)24, "ISO Studio Tungsten")] + [ExifTagDescription((ushort)255, "Other")] + LightSource = 0x9208, + + /// + /// Flash + /// + [ExifTagDescription((ushort)0, "No Flash")] + [ExifTagDescription((ushort)1, "Fired")] + [ExifTagDescription((ushort)5, "Fired, Return not detected")] + [ExifTagDescription((ushort)7, "Fired, Return detected")] + [ExifTagDescription((ushort)8, "On, Did not fire")] + [ExifTagDescription((ushort)9, "On, Fired")] + [ExifTagDescription((ushort)13, "On, Return not detected")] + [ExifTagDescription((ushort)15, "On, Return detected")] + [ExifTagDescription((ushort)16, "Off, Did not fire")] + [ExifTagDescription((ushort)20, "Off, Did not fire, Return not detected")] + [ExifTagDescription((ushort)24, "Auto, Did not fire")] + [ExifTagDescription((ushort)25, "Auto, Fired")] + [ExifTagDescription((ushort)29, "Auto, Fired, Return not detected")] + [ExifTagDescription((ushort)31, "Auto, Fired, Return detected")] + [ExifTagDescription((ushort)32, "No flash function")] + [ExifTagDescription((ushort)48, "Off, No flash function")] + [ExifTagDescription((ushort)65, "Fired, Red-eye reduction")] + [ExifTagDescription((ushort)69, "Fired, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)71, "Fired, Red-eye reduction, Return detected")] + [ExifTagDescription((ushort)73, "On, Red-eye reduction")] + [ExifTagDescription((ushort)77, "On, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)69, "On, Red-eye reduction, Return detected")] + [ExifTagDescription((ushort)80, "Off, Red-eye reduction")] + [ExifTagDescription((ushort)88, "Auto, Did not fire, Red-eye reduction")] + [ExifTagDescription((ushort)89, "Auto, Fired, Red-eye reduction")] + [ExifTagDescription((ushort)93, "Auto, Fired, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)95, "Auto, Fired, Red-eye reduction, Return detected")] + Flash = 0x9209, + + /// + /// FocalLength + /// + FocalLength = 0x920A, + + /// + /// FlashEnergy2 + /// + FlashEnergy2 = 0x920B, + + /// + /// SpatialFrequencyResponse2 + /// + SpatialFrequencyResponse2 = 0x920C, + + /// + /// Noise + /// + Noise = 0x920D, + + /// + /// FocalPlaneXResolution2 + /// + FocalPlaneXResolution2 = 0x920E, + + /// + /// FocalPlaneYResolution2 + /// + FocalPlaneYResolution2 = 0x920F, + + /// + /// FocalPlaneResolutionUnit2 + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + [ExifTagDescription((ushort)4, "Millimeter")] + [ExifTagDescription((ushort)5, "Micrometer")] + FocalPlaneResolutionUnit2 = 0x9210, + + /// + /// ImageNumber + /// + ImageNumber = 0x9211, + + /// + /// SecurityClassification + /// + [ExifTagDescription("C", "Confidential")] + [ExifTagDescription("R", "Restricted")] + [ExifTagDescription("S", "Secret")] + [ExifTagDescription("T", "Top Secret")] + [ExifTagDescription("U", "Unclassified")] + SecurityClassification = 0x9212, + + /// + /// ImageHistory + /// + ImageHistory = 0x9213, + + /// + /// SubjectArea + /// + SubjectArea = 0x9214, + + /// + /// ExposureIndex2 + /// + ExposureIndex2 = 0x9215, + + /// + /// TIFFEPStandardID + /// + TIFFEPStandardID = 0x9216, + + /// + /// SensingMethod + /// + [ExifTagDescription((ushort)1, "Not defined")] + [ExifTagDescription((ushort)2, "One-chip color area")] + [ExifTagDescription((ushort)3, "Two-chip color area")] + [ExifTagDescription((ushort)4, "Three-chip color area")] + [ExifTagDescription((ushort)5, "Color sequential area")] + [ExifTagDescription((ushort)7, "Trilinear")] + [ExifTagDescription((ushort)8, "Color sequential linear")] + SensingMethod2 = 0x9217, + + /// + /// MakerNote + /// + MakerNote = 0x927C, + + /// + /// UserComment + /// + UserComment = 0x9286, + + /// + /// SubsecTime + /// + SubsecTime = 0x9290, + + /// + /// SubsecTimeOriginal + /// + SubsecTimeOriginal = 0x9291, + + /// + /// SubsecTimeDigitized + /// + SubsecTimeDigitized = 0x9292, + + /// + /// ImageSourceData + /// + ImageSourceData = 0x935C, + + /// + /// AmbientTemperature + /// + AmbientTemperature = 0x9400, + + /// + /// Humidity + /// + Humidity = 0x9401, + + /// + /// Pressure + /// + Pressure = 0x9402, + + /// + /// WaterDepth + /// + WaterDepth = 0x9403, + + /// + /// Acceleration + /// + Acceleration = 0x9404, + + /// + /// CameraElevationAngle + /// + CameraElevationAngle = 0x9405, + + /// + /// XPTitle + /// + XPTitle = 0x9C9B, + + /// + /// XPComment + /// + XPComment = 0x9C9C, + + /// + /// XPAuthor + /// + XPAuthor = 0x9C9D, + + /// + /// XPKeywords + /// + XPKeywords = 0x9C9E, + + /// + /// XPSubject + /// + XPSubject = 0x9C9F, + + /// + /// FlashpixVersion + /// + FlashpixVersion = 0xA000, + + /// + /// ColorSpace + /// + [ExifTagDescription((ushort)1, "sRGB")] + [ExifTagDescription((ushort)2, "Adobe RGB")] + [ExifTagDescription((ushort)4093, "Wide Gamut RGB")] + [ExifTagDescription((ushort)65534, "ICC Profile")] + [ExifTagDescription((ushort)65535, "Uncalibrated")] + ColorSpace = 0xA001, + + /// + /// PixelXDimension + /// + PixelXDimension = 0xA002, + + /// + /// PixelYDimension + /// + PixelYDimension = 0xA003, + + /// + /// RelatedSoundFile + /// + RelatedSoundFile = 0xA004, + + /// + /// FlashEnergy + /// + FlashEnergy = 0xA20B, + + /// + /// SpatialFrequencyResponse + /// + SpatialFrequencyResponse = 0xA20C, + + /// + /// FocalPlaneXResolution + /// + FocalPlaneXResolution = 0xA20E, + + /// + /// FocalPlaneYResolution + /// + FocalPlaneYResolution = 0xA20F, + + /// + /// FocalPlaneResolutionUnit + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + [ExifTagDescription((ushort)4, "Millimeter")] + [ExifTagDescription((ushort)5, "Micrometer")] + FocalPlaneResolutionUnit = 0xA210, + + /// + /// SubjectLocation + /// + SubjectLocation = 0xA214, + + /// + /// ExposureIndex + /// + ExposureIndex = 0xA215, + + /// + /// SensingMethod + /// + [ExifTagDescription((ushort)1, "Not defined")] + [ExifTagDescription((ushort)2, "One-chip color area")] + [ExifTagDescription((ushort)3, "Two-chip color area")] + [ExifTagDescription((ushort)4, "Three-chip color area")] + [ExifTagDescription((ushort)5, "Color sequential area")] + [ExifTagDescription((ushort)7, "Trilinear")] + [ExifTagDescription((ushort)8, "Color sequential linear")] + SensingMethod = 0xA217, + + /// + /// FileSource + /// + FileSource = 0xA300, + + /// + /// SceneType + /// + SceneType = 0xA301, + + /// + /// CFAPattern + /// + CFAPattern = 0xA302, + + /// + /// CustomRendered + /// + [ExifTagDescription((ushort)1, "Normal")] + [ExifTagDescription((ushort)2, "Custom")] + CustomRendered = 0xA401, + + /// + /// ExposureMode + /// + [ExifTagDescription((ushort)0, "Auto")] + [ExifTagDescription((ushort)1, "Manual")] + [ExifTagDescription((ushort)2, "Auto bracket")] + ExposureMode = 0xA402, + + /// + /// WhiteBalance + /// + [ExifTagDescription((ushort)0, "Auto")] + [ExifTagDescription((ushort)1, "Manual")] + WhiteBalance = 0xA403, + + /// + /// DigitalZoomRatio + /// + DigitalZoomRatio = 0xA404, + + /// + /// FocalLengthIn35mmFilm + /// + FocalLengthIn35mmFilm = 0xA405, + + /// + /// SceneCaptureType + /// + [ExifTagDescription((ushort)0, "Standard")] + [ExifTagDescription((ushort)1, "Landscape")] + [ExifTagDescription((ushort)2, "Portrait")] + [ExifTagDescription((ushort)3, "Night")] + SceneCaptureType = 0xA406, + + /// + /// GainControl + /// + [ExifTagDescription((ushort)0, "None")] + [ExifTagDescription((ushort)1, "Low gain up")] + [ExifTagDescription((ushort)2, "High gain up")] + [ExifTagDescription((ushort)3, "Low gain down")] + [ExifTagDescription((ushort)4, "High gain down")] + GainControl = 0xA407, + + /// + /// Contrast + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Low")] + [ExifTagDescription((ushort)2, "High")] + Contrast = 0xA408, + + /// + /// Saturation + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Low")] + [ExifTagDescription((ushort)2, "High")] + Saturation = 0xA409, + + /// + /// Sharpness + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Soft")] + [ExifTagDescription((ushort)2, "Hard")] + Sharpness = 0xA40A, + + /// + /// DeviceSettingDescription + /// + DeviceSettingDescription = 0xA40B, + + /// + /// SubjectDistanceRange + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Macro")] + [ExifTagDescription((ushort)2, "Close")] + [ExifTagDescription((ushort)3, "Distant")] + SubjectDistanceRange = 0xA40C, + + /// + /// ImageUniqueID + /// + ImageUniqueID = 0xA420, + + /// + /// OwnerName + /// + OwnerName = 0xA430, + + /// + /// SerialNumber + /// + SerialNumber = 0xA431, + + /// + /// LensInfo + /// + LensInfo = 0xA432, + + /// + /// LensMake + /// + LensMake = 0xA433, + + /// + /// LensModel + /// + LensModel = 0xA434, + + /// + /// LensSerialNumber + /// + LensSerialNumber = 0xA435, + + /// + /// GDALMetadata + /// + GDALMetadata = 0xA480, + + /// + /// GDALNoData + /// + GDALNoData = 0xA481, + + /// + /// GPSVersionID + /// + GPSVersionID = 0x0000, + + /// + /// GPSLatitudeRef + /// + GPSLatitudeRef = 0x0001, + + /// + /// GPSLatitude + /// + GPSLatitude = 0x0002, + + /// + /// GPSLongitudeRef + /// + GPSLongitudeRef = 0x0003, + + /// + /// GPSLongitude + /// + GPSLongitude = 0x0004, + + /// + /// GPSAltitudeRef + /// + GPSAltitudeRef = 0x0005, + + /// + /// GPSAltitude + /// + GPSAltitude = 0x0006, + + /// + /// GPSTimestamp + /// + GPSTimestamp = 0x0007, + + /// + /// GPSSatellites + /// + GPSSatellites = 0x0008, + + /// + /// GPSStatus + /// + GPSStatus = 0x0009, + + /// + /// GPSMeasureMode + /// + GPSMeasureMode = 0x000A, + + /// + /// GPSDOP + /// + GPSDOP = 0x000B, + + /// + /// GPSSpeedRef + /// + GPSSpeedRef = 0x000C, + + /// + /// GPSSpeed + /// + GPSSpeed = 0x000D, + + /// + /// GPSTrackRef + /// + GPSTrackRef = 0x000E, + + /// + /// GPSTrack + /// + GPSTrack = 0x000F, + + /// + /// GPSImgDirectionRef + /// + GPSImgDirectionRef = 0x0010, + + /// + /// GPSImgDirection + /// + GPSImgDirection = 0x0011, + + /// + /// GPSMapDatum + /// + GPSMapDatum = 0x0012, + + /// + /// GPSDestLatitudeRef + /// + GPSDestLatitudeRef = 0x0013, + + /// + /// GPSDestLatitude + /// + GPSDestLatitude = 0x0014, + + /// + /// GPSDestLongitudeRef + /// + GPSDestLongitudeRef = 0x0015, + + /// + /// GPSDestLongitude + /// + GPSDestLongitude = 0x0016, + + /// + /// GPSDestBearingRef + /// + GPSDestBearingRef = 0x0017, + + /// + /// GPSDestBearing + /// + GPSDestBearing = 0x0018, + + /// + /// GPSDestDistanceRef + /// + GPSDestDistanceRef = 0x0019, + + /// + /// GPSDestDistance + /// + GPSDestDistance = 0x001A, + + /// + /// GPSProcessingMethod + /// + GPSProcessingMethod = 0x001B, + + /// + /// GPSAreaInformation + /// + GPSAreaInformation = 0x001C, + + /// + /// GPSDateStamp + /// + GPSDateStamp = 0x001D, + + /// + /// GPSDifferential + /// + GPSDifferential = 0x001E + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp46/Profiles/Exif/ExifTagDescriptionAttribute.cs new file mode 100644 index 000000000..ca19a9776 --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/ExifTagDescriptionAttribute.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Reflection; + + /// + /// Class that provides a description for an ExifTag value. + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] + internal sealed class ExifTagDescriptionAttribute : Attribute + { + private object value; + private string description; + + /// + /// Initializes a new instance of the class. + /// + /// The value of the exif tag. + /// The description for the value of the exif tag. + public ExifTagDescriptionAttribute(object value, string description) + { + this.value = value; + this.description = description; + } + + public static string GetDescription(ExifTag tag, object value) + { + FieldInfo field = tag.GetType().GetTypeInfo().GetDeclaredField(tag.ToString()); + if (field == null) + { + return null; + } + + foreach (CustomAttributeData customAttribute in field.CustomAttributes) + { + object attributeValue = customAttribute.ConstructorArguments[0].Value; + + if (Equals(attributeValue, value)) + { + return (string)customAttribute.ConstructorArguments[1].Value; + } + } + + return null; + } + } +} diff --git a/src/ImageSharp46/Profiles/Exif/ExifValue.cs b/src/ImageSharp46/Profiles/Exif/ExifValue.cs new file mode 100644 index 000000000..db62be4c2 --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/ExifValue.cs @@ -0,0 +1,716 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + using System.Text; + + /// + /// A value of the exif profile. + /// + public sealed class ExifValue : IEquatable + { + private object exifValue; + + /// + /// Initializes a new instance of the class + /// by making a copy from another exif value. + /// + /// The other exif value, where the clone should be made from. + /// is null. + public ExifValue(ExifValue other) + { + Guard.NotNull(other, nameof(other)); + + this.DataType = other.DataType; + this.IsArray = other.IsArray; + this.Tag = other.Tag; + + if (!other.IsArray) + { + this.exifValue = other.exifValue; + } + else + { + Array array = (Array)other.exifValue; + this.exifValue = array.Clone(); + } + } + + /// + /// Gets the data type of the exif value. + /// + public ExifDataType DataType + { + get; + } + + /// + /// Gets a value indicating whether the value is an array. + /// + public bool IsArray + { + get; + } + + /// + /// Gets the tag of the exif value. + /// + public ExifTag Tag + { + get; + } + + /// + /// Gets or sets the value. + /// + public object Value + { + get + { + return this.exifValue; + } + set + { + this.CheckValue(value); + this.exifValue = value; + } + } + + /// + /// Determines whether the specified ExifValue instances are considered equal. + /// + /// The first ExifValue to compare. + /// The second ExifValue to compare. + /// + public static bool operator ==(ExifValue left, ExifValue right) + { + return Equals(left, right); + } + + /// + /// Determines whether the specified ExifValue instances are not considered equal. + /// + /// The first ExifValue to compare. + /// The second ExifValue to compare. + /// + public static bool operator !=(ExifValue left, ExifValue right) + { + return !Equals(left, right); + } + + /// + /// Determines whether the specified object is equal to the current exif value. + /// + /// The object to compare this exif value with. + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + return this.Equals(obj as ExifValue); + } + + /// + /// Determines whether the specified exif value is equal to the current exif value. + /// + /// The exif value to compare this exif value with. + public bool Equals(ExifValue other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return + this.Tag == other.Tag && + this.DataType == other.DataType && + Equals(this.exifValue, other.exifValue); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.exifValue == null) + { + return null; + } + + if (this.DataType == ExifDataType.Ascii) + { + return (string)this.exifValue; + } + + if (!this.IsArray) + { + return this.ToString(this.exifValue); + } + + StringBuilder sb = new StringBuilder(); + foreach (object value in (Array)this.exifValue) + { + sb.Append(this.ToString(value)); + sb.Append(" "); + } + + return sb.ToString(); + } + + internal bool HasValue + { + get + { + if (this.exifValue == null) + { + return false; + } + + if (this.DataType == ExifDataType.Ascii) + { + return ((string)this.exifValue).Length > 0; + } + + return true; + } + } + + internal int Length + { + get + { + if (this.exifValue == null) + { + return 4; + } + + int size = (int)(GetSize(this.DataType) * this.NumberOfComponents); + + return size < 4 ? 4 : size; + } + } + + internal int NumberOfComponents + { + get + { + if (this.DataType == ExifDataType.Ascii) + { + return Encoding.UTF8.GetBytes((string)this.exifValue).Length; + } + + if (this.IsArray) + { + return ((Array)this.exifValue).Length; + } + + return 1; + } + } + + internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray) + { + this.Tag = tag; + this.DataType = dataType; + this.IsArray = isArray; + + if (dataType == ExifDataType.Ascii) + { + this.IsArray = false; + } + } + + internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) + : this(tag, dataType, isArray) + { + this.exifValue = value; + } + + internal static ExifValue Create(ExifTag tag, object value) + { + Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag"); + + ExifValue exifValue; + Type type = value?.GetType(); + if (type != null && type.IsArray) + { + type = type.GetElementType(); + } + + switch (tag) + { + case ExifTag.ImageDescription: + case ExifTag.Make: + case ExifTag.Model: + case ExifTag.Software: + case ExifTag.DateTime: + case ExifTag.Artist: + case ExifTag.HostComputer: + case ExifTag.Copyright: + case ExifTag.DocumentName: + case ExifTag.PageName: + case ExifTag.InkNames: + case ExifTag.TargetPrinter: + case ExifTag.ImageID: + case ExifTag.MDLabName: + case ExifTag.MDSampleInfo: + case ExifTag.MDPrepDate: + case ExifTag.MDPrepTime: + case ExifTag.MDFileUnits: + case ExifTag.SEMInfo: + case ExifTag.SpectralSensitivity: + case ExifTag.DateTimeOriginal: + case ExifTag.DateTimeDigitized: + case ExifTag.SubsecTime: + case ExifTag.SubsecTimeOriginal: + case ExifTag.SubsecTimeDigitized: + case ExifTag.FaxSubaddress: + case ExifTag.OffsetTime: + case ExifTag.OffsetTimeOriginal: + case ExifTag.OffsetTimeDigitized: + case ExifTag.SecurityClassification: + case ExifTag.ImageHistory: + case ExifTag.ImageUniqueID: + case ExifTag.OwnerName: + case ExifTag.SerialNumber: + case ExifTag.LensMake: + case ExifTag.LensModel: + case ExifTag.LensSerialNumber: + case ExifTag.GDALMetadata: + case ExifTag.GDALNoData: + case ExifTag.GPSLatitudeRef: + case ExifTag.GPSLongitudeRef: + case ExifTag.GPSSatellites: + case ExifTag.GPSStatus: + case ExifTag.GPSMeasureMode: + case ExifTag.GPSSpeedRef: + case ExifTag.GPSTrackRef: + case ExifTag.GPSImgDirectionRef: + case ExifTag.GPSMapDatum: + case ExifTag.GPSDestLatitudeRef: + case ExifTag.GPSDestLongitudeRef: + case ExifTag.GPSDestBearingRef: + case ExifTag.GPSDestDistanceRef: + case ExifTag.GPSDateStamp: + exifValue = new ExifValue(tag, ExifDataType.Ascii, true); + break; + + case ExifTag.ClipPath: + case ExifTag.VersionYear: + case ExifTag.XMP: + case ExifTag.CFAPattern2: + case ExifTag.TIFFEPStandardID: + case ExifTag.XPTitle: + case ExifTag.XPComment: + case ExifTag.XPAuthor: + case ExifTag.XPKeywords: + case ExifTag.XPSubject: + case ExifTag.GPSVersionID: + exifValue = new ExifValue(tag, ExifDataType.Byte, true); + break; + case ExifTag.FaxProfile: + case ExifTag.ModeNumber: + case ExifTag.GPSAltitudeRef: + exifValue = new ExifValue(tag, ExifDataType.Byte, false); + break; + + case ExifTag.FreeOffsets: + case ExifTag.FreeByteCounts: + case ExifTag.ColorResponseUnit: + case ExifTag.TileOffsets: + case ExifTag.SMinSampleValue: + case ExifTag.SMaxSampleValue: + case ExifTag.JPEGQTables: + case ExifTag.JPEGDCTables: + case ExifTag.JPEGACTables: + case ExifTag.StripRowCounts: + case ExifTag.IntergraphRegisters: + case ExifTag.TimeZoneOffset: + exifValue = new ExifValue(tag, ExifDataType.Long, true); + break; + case ExifTag.SubfileType: + case ExifTag.SubIFDOffset: + case ExifTag.GPSIFDOffset: + case ExifTag.T4Options: + case ExifTag.T6Options: + case ExifTag.XClipPathUnits: + case ExifTag.YClipPathUnits: + case ExifTag.ProfileType: + case ExifTag.CodingMethods: + case ExifTag.T82ptions: + case ExifTag.JPEGInterchangeFormat: + case ExifTag.JPEGInterchangeFormatLength: + case ExifTag.MDFileTag: + case ExifTag.StandardOutputSensitivity: + case ExifTag.RecommendedExposureIndex: + case ExifTag.ISOSpeed: + case ExifTag.ISOSpeedLatitudeyyy: + case ExifTag.ISOSpeedLatitudezzz: + case ExifTag.FaxRecvParams: + case ExifTag.FaxRecvTime: + case ExifTag.ImageNumber: + exifValue = new ExifValue(tag, ExifDataType.Long, false); + break; + + case ExifTag.WhitePoint: + case ExifTag.PrimaryChromaticities: + case ExifTag.YCbCrCoefficients: + case ExifTag.ReferenceBlackWhite: + case ExifTag.PixelScale: + case ExifTag.IntergraphMatrix: + case ExifTag.ModelTiePoint: + case ExifTag.ModelTransform: + case ExifTag.GPSLatitude: + case ExifTag.GPSLongitude: + case ExifTag.GPSTimestamp: + case ExifTag.GPSDestLatitude: + case ExifTag.GPSDestLongitude: + exifValue = new ExifValue(tag, ExifDataType.Rational, true); + break; + case ExifTag.XPosition: + case ExifTag.YPosition: + case ExifTag.XResolution: + case ExifTag.YResolution: + case ExifTag.BatteryLevel: + case ExifTag.ExposureTime: + case ExifTag.FNumber: + case ExifTag.MDScalePixel: + case ExifTag.CompressedBitsPerPixel: + case ExifTag.ApertureValue: + case ExifTag.MaxApertureValue: + case ExifTag.SubjectDistance: + case ExifTag.FocalLength: + case ExifTag.FlashEnergy2: + case ExifTag.FocalPlaneXResolution2: + case ExifTag.FocalPlaneYResolution2: + case ExifTag.ExposureIndex2: + case ExifTag.Humidity: + case ExifTag.Pressure: + case ExifTag.Acceleration: + case ExifTag.FlashEnergy: + case ExifTag.FocalPlaneXResolution: + case ExifTag.FocalPlaneYResolution: + case ExifTag.ExposureIndex: + case ExifTag.DigitalZoomRatio: + case ExifTag.LensInfo: + case ExifTag.GPSAltitude: + case ExifTag.GPSDOP: + case ExifTag.GPSSpeed: + case ExifTag.GPSTrack: + case ExifTag.GPSImgDirection: + case ExifTag.GPSDestBearing: + case ExifTag.GPSDestDistance: + exifValue = new ExifValue(tag, ExifDataType.Rational, false); + break; + + case ExifTag.BitsPerSample: + case ExifTag.MinSampleValue: + case ExifTag.MaxSampleValue: + case ExifTag.GrayResponseCurve: + case ExifTag.ColorMap: + case ExifTag.ExtraSamples: + case ExifTag.PageNumber: + case ExifTag.TransferFunction: + case ExifTag.Predictor: + case ExifTag.HalftoneHints: + case ExifTag.SampleFormat: + case ExifTag.TransferRange: + case ExifTag.DefaultImageColor: + case ExifTag.JPEGLosslessPredictors: + case ExifTag.JPEGPointTransforms: + case ExifTag.YCbCrSubsampling: + case ExifTag.CFARepeatPatternDim: + case ExifTag.IntergraphPacketData: + case ExifTag.ISOSpeedRatings: + case ExifTag.SubjectArea: + case ExifTag.SubjectLocation: + exifValue = new ExifValue(tag, ExifDataType.Short, true); + break; + case ExifTag.OldSubfileType: + case ExifTag.Compression: + case ExifTag.PhotometricInterpretation: + case ExifTag.Thresholding: + case ExifTag.CellWidth: + case ExifTag.CellLength: + case ExifTag.FillOrder: + case ExifTag.Orientation: + case ExifTag.SamplesPerPixel: + case ExifTag.PlanarConfiguration: + case ExifTag.GrayResponseUnit: + case ExifTag.ResolutionUnit: + case ExifTag.CleanFaxData: + case ExifTag.InkSet: + case ExifTag.NumberOfInks: + case ExifTag.DotRange: + case ExifTag.Indexed: + case ExifTag.OPIProxy: + case ExifTag.JPEGProc: + case ExifTag.JPEGRestartInterval: + case ExifTag.YCbCrPositioning: + case ExifTag.Rating: + case ExifTag.RatingPercent: + case ExifTag.ExposureProgram: + case ExifTag.Interlace: + case ExifTag.SelfTimerMode: + case ExifTag.SensitivityType: + case ExifTag.MeteringMode: + case ExifTag.LightSource: + case ExifTag.FocalPlaneResolutionUnit2: + case ExifTag.SensingMethod2: + case ExifTag.Flash: + case ExifTag.ColorSpace: + case ExifTag.FocalPlaneResolutionUnit: + case ExifTag.SensingMethod: + case ExifTag.CustomRendered: + case ExifTag.ExposureMode: + case ExifTag.WhiteBalance: + case ExifTag.FocalLengthIn35mmFilm: + case ExifTag.SceneCaptureType: + case ExifTag.GainControl: + case ExifTag.Contrast: + case ExifTag.Saturation: + case ExifTag.Sharpness: + case ExifTag.SubjectDistanceRange: + case ExifTag.GPSDifferential: + exifValue = new ExifValue(tag, ExifDataType.Short, false); + break; + + case ExifTag.Decode: + exifValue = new ExifValue(tag, ExifDataType.SignedRational, true); + break; + case ExifTag.ShutterSpeedValue: + case ExifTag.BrightnessValue: + case ExifTag.ExposureBiasValue: + case ExifTag.AmbientTemperature: + case ExifTag.WaterDepth: + case ExifTag.CameraElevationAngle: + exifValue = new ExifValue(tag, ExifDataType.SignedRational, false); + break; + + case ExifTag.JPEGTables: + case ExifTag.OECF: + case ExifTag.ExifVersion: + case ExifTag.ComponentsConfiguration: + case ExifTag.MakerNote: + case ExifTag.UserComment: + case ExifTag.FlashpixVersion: + case ExifTag.SpatialFrequencyResponse: + case ExifTag.SpatialFrequencyResponse2: + case ExifTag.Noise: + case ExifTag.CFAPattern: + case ExifTag.DeviceSettingDescription: + case ExifTag.ImageSourceData: + case ExifTag.GPSProcessingMethod: + case ExifTag.GPSAreaInformation: + exifValue = new ExifValue(tag, ExifDataType.Undefined, true); + break; + case ExifTag.FileSource: + case ExifTag.SceneType: + exifValue = new ExifValue(tag, ExifDataType.Undefined, false); + break; + + case ExifTag.StripOffsets: + case ExifTag.TileByteCounts: + case ExifTag.ImageLayer: + exifValue = CreateNumber(tag, type, true); + break; + case ExifTag.ImageWidth: + case ExifTag.ImageLength: + case ExifTag.TileWidth: + case ExifTag.TileLength: + case ExifTag.BadFaxLines: + case ExifTag.ConsecutiveBadFaxLines: + case ExifTag.PixelXDimension: + case ExifTag.PixelYDimension: + exifValue = CreateNumber(tag, type, false); + break; + + default: + throw new NotSupportedException(); + } + + exifValue.Value = value; + return exifValue; + } + + internal static uint GetSize(ExifDataType dataType) + { + switch (dataType) + { + case ExifDataType.Ascii: + case ExifDataType.Byte: + case ExifDataType.SignedByte: + case ExifDataType.Undefined: + return 1; + case ExifDataType.Short: + case ExifDataType.SignedShort: + return 2; + case ExifDataType.Long: + case ExifDataType.SignedLong: + case ExifDataType.SingleFloat: + return 4; + case ExifDataType.DoubleFloat: + case ExifDataType.Rational: + case ExifDataType.SignedRational: + return 8; + default: + throw new NotSupportedException(dataType.ToString()); + } + } + + private void CheckValue(object value) + { + if (value == null) + { + return; + } + + Type type = value.GetType(); + + if (this.DataType == ExifDataType.Ascii) + { + Guard.IsTrue(type == typeof(string), nameof(value), "Value should be a string."); + return; + } + + if (type.IsArray) + { + Guard.IsTrue(this.IsArray, nameof(value), "Value should not be an array."); + type = type.GetElementType(); + } + else + { + Guard.IsFalse(this.IsArray, nameof(value), "Value should not be an array."); + } + + switch (this.DataType) + { + case ExifDataType.Byte: + Guard.IsTrue(type == typeof(byte), nameof(value), $"Value should be a byte{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.DoubleFloat: + Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.Long: + Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.Rational: + Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.Short: + Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SignedByte: + Guard.IsTrue(type == typeof(sbyte), nameof(value), $"Value should be a signed byte{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SignedLong: + Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SignedRational: + Guard.IsTrue(type == typeof(SignedRational), nameof(value), $"Value should be a SignedRational{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SignedShort: + Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SingleFloat: + Guard.IsTrue(type == typeof(float), nameof(value), $"Value should be a float{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.Undefined: + Guard.IsTrue(type == typeof(byte), nameof(value), "Value should be a byte array."); + break; + default: + throw new NotSupportedException(); + } + } + + private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray) + { + if (type == null || type == typeof(ushort)) + { + return new ExifValue(tag, ExifDataType.Short, isArray); + } + + if (type == typeof(short)) + { + return new ExifValue(tag, ExifDataType.SignedShort, isArray); + } + + if (type == typeof(uint)) + { + return new ExifValue(tag, ExifDataType.Long, isArray); + } + + return new ExifValue(tag, ExifDataType.SignedLong, isArray); + } + + private string ToString(object value) + { + string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, value); + if (description != null) + { + return description; + } + + switch (this.DataType) + { + case ExifDataType.Ascii: + return (string)value; + case ExifDataType.Byte: + return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); + case ExifDataType.DoubleFloat: + return ((double)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.Long: + return ((uint)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.Rational: + return ((Rational)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.Short: + return ((ushort)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.SignedByte: + return ((sbyte)value).ToString("X2", CultureInfo.InvariantCulture); + case ExifDataType.SignedLong: + return ((int)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.SignedRational: + return ((Rational)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.SignedShort: + return ((short)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.SingleFloat: + return ((float)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.Undefined: + return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); + default: + throw new NotSupportedException(); + } + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(ExifValue exif) + { + int hashCode = exif.Tag.GetHashCode() ^ exif.DataType.GetHashCode(); + return hashCode ^ exif.exifValue?.GetHashCode() ?? hashCode; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifWriter.cs b/src/ImageSharp46/Profiles/Exif/ExifWriter.cs new file mode 100644 index 000000000..f7653b240 --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/ExifWriter.cs @@ -0,0 +1,585 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Text; + + internal sealed class ExifWriter + { + private static readonly ExifTag[] IfdTags = new ExifTag[127] + { + ExifTag.SubfileType, + ExifTag.OldSubfileType, + ExifTag.ImageWidth, + ExifTag.ImageLength, + ExifTag.BitsPerSample, + ExifTag.Compression, + ExifTag.PhotometricInterpretation, + ExifTag.Thresholding, + ExifTag.CellWidth, + ExifTag.CellLength, + ExifTag.FillOrder, + ExifTag.DocumentName, + ExifTag.ImageDescription, + ExifTag.Make, + ExifTag.Model, + ExifTag.StripOffsets, + ExifTag.Orientation, + ExifTag.SamplesPerPixel, + ExifTag.RowsPerStrip, + ExifTag.StripByteCounts, + ExifTag.MinSampleValue, + ExifTag.MaxSampleValue, + ExifTag.XResolution, + ExifTag.YResolution, + ExifTag.PlanarConfiguration, + ExifTag.PageName, + ExifTag.XPosition, + ExifTag.YPosition, + ExifTag.FreeOffsets, + ExifTag.FreeByteCounts, + ExifTag.GrayResponseUnit, + ExifTag.GrayResponseCurve, + ExifTag.T4Options, + ExifTag.T6Options, + ExifTag.ResolutionUnit, + ExifTag.PageNumber, + ExifTag.ColorResponseUnit, + ExifTag.TransferFunction, + ExifTag.Software, + ExifTag.DateTime, + ExifTag.Artist, + ExifTag.HostComputer, + ExifTag.Predictor, + ExifTag.WhitePoint, + ExifTag.PrimaryChromaticities, + ExifTag.ColorMap, + ExifTag.HalftoneHints, + ExifTag.TileWidth, + ExifTag.TileLength, + ExifTag.TileOffsets, + ExifTag.TileByteCounts, + ExifTag.BadFaxLines, + ExifTag.CleanFaxData, + ExifTag.ConsecutiveBadFaxLines, + ExifTag.InkSet, + ExifTag.InkNames, + ExifTag.NumberOfInks, + ExifTag.DotRange, + ExifTag.TargetPrinter, + ExifTag.ExtraSamples, + ExifTag.SampleFormat, + ExifTag.SMinSampleValue, + ExifTag.SMaxSampleValue, + ExifTag.TransferRange, + ExifTag.ClipPath, + ExifTag.XClipPathUnits, + ExifTag.YClipPathUnits, + ExifTag.Indexed, + ExifTag.JPEGTables, + ExifTag.OPIProxy, + ExifTag.ProfileType, + ExifTag.FaxProfile, + ExifTag.CodingMethods, + ExifTag.VersionYear, + ExifTag.ModeNumber, + ExifTag.Decode, + ExifTag.DefaultImageColor, + ExifTag.T82ptions, + ExifTag.JPEGProc, + ExifTag.JPEGInterchangeFormat, + ExifTag.JPEGInterchangeFormatLength, + ExifTag.JPEGRestartInterval, + ExifTag.JPEGLosslessPredictors, + ExifTag.JPEGPointTransforms, + ExifTag.JPEGQTables, + ExifTag.JPEGDCTables, + ExifTag.JPEGACTables, + ExifTag.YCbCrCoefficients, + ExifTag.YCbCrSubsampling, + ExifTag.YCbCrSubsampling, + ExifTag.YCbCrPositioning, + ExifTag.ReferenceBlackWhite, + ExifTag.StripRowCounts, + ExifTag.XMP, + ExifTag.Rating, + ExifTag.RatingPercent, + ExifTag.ImageID, + ExifTag.CFARepeatPatternDim, + ExifTag.CFAPattern2, + ExifTag.BatteryLevel, + ExifTag.Copyright, + ExifTag.MDFileTag, + ExifTag.MDScalePixel, + ExifTag.MDLabName, + ExifTag.MDSampleInfo, + ExifTag.MDPrepDate, + ExifTag.MDPrepTime, + ExifTag.MDFileUnits, + ExifTag.PixelScale, + ExifTag.IntergraphPacketData, + ExifTag.IntergraphRegisters, + ExifTag.IntergraphMatrix, + ExifTag.ModelTiePoint, + ExifTag.SEMInfo, + ExifTag.ModelTransform, + ExifTag.ImageLayer, + ExifTag.FaxRecvParams, + ExifTag.FaxSubaddress, + ExifTag.FaxRecvTime, + ExifTag.ImageSourceData, + ExifTag.XPTitle, + ExifTag.XPComment, + ExifTag.XPAuthor, + ExifTag.XPKeywords, + ExifTag.XPSubject, + ExifTag.GDALMetadata, + ExifTag.GDALNoData + }; + + private static readonly ExifTag[] ExifTags = new ExifTag[92] + { + ExifTag.ExposureTime, + ExifTag.FNumber, + ExifTag.ExposureProgram, + ExifTag.SpectralSensitivity, + ExifTag.ISOSpeedRatings, + ExifTag.OECF, + ExifTag.Interlace, + ExifTag.TimeZoneOffset, + ExifTag.SelfTimerMode, + ExifTag.SensitivityType, + ExifTag.StandardOutputSensitivity, + ExifTag.RecommendedExposureIndex, + ExifTag.ISOSpeed, + ExifTag.ISOSpeedLatitudeyyy, + ExifTag.ISOSpeedLatitudezzz, + ExifTag.ExifVersion, + ExifTag.DateTimeOriginal, + ExifTag.DateTimeDigitized, + ExifTag.OffsetTime, + ExifTag.OffsetTimeOriginal, + ExifTag.OffsetTimeDigitized, + ExifTag.ComponentsConfiguration, + ExifTag.CompressedBitsPerPixel, + ExifTag.ShutterSpeedValue, + ExifTag.ApertureValue, + ExifTag.BrightnessValue, + ExifTag.ExposureBiasValue, + ExifTag.MaxApertureValue, + ExifTag.SubjectDistance, + ExifTag.MeteringMode, + ExifTag.LightSource, + ExifTag.Flash, + ExifTag.FocalLength, + ExifTag.FlashEnergy2, + ExifTag.SpatialFrequencyResponse2, + ExifTag.Noise, + ExifTag.FocalPlaneXResolution2, + ExifTag.FocalPlaneYResolution2, + ExifTag.FocalPlaneResolutionUnit2, + ExifTag.ImageNumber, + ExifTag.SecurityClassification, + ExifTag.ImageHistory, + ExifTag.SubjectArea, + ExifTag.ExposureIndex2, + ExifTag.TIFFEPStandardID, + ExifTag.SensingMethod2, + ExifTag.MakerNote, + ExifTag.UserComment, + ExifTag.SubsecTime, + ExifTag.SubsecTimeOriginal, + ExifTag.SubsecTimeDigitized, + ExifTag.AmbientTemperature, + ExifTag.Humidity, + ExifTag.Pressure, + ExifTag.WaterDepth, + ExifTag.Acceleration, + ExifTag.CameraElevationAngle, + ExifTag.FlashpixVersion, + ExifTag.ColorSpace, + ExifTag.PixelXDimension, + ExifTag.PixelYDimension, + ExifTag.RelatedSoundFile, + ExifTag.FlashEnergy, + ExifTag.SpatialFrequencyResponse, + ExifTag.FocalPlaneXResolution, + ExifTag.FocalPlaneYResolution, + ExifTag.FocalPlaneResolutionUnit, + ExifTag.SubjectLocation, + ExifTag.ExposureIndex, + ExifTag.SensingMethod, + ExifTag.FileSource, + ExifTag.SceneType, + ExifTag.CFAPattern, + ExifTag.CustomRendered, + ExifTag.ExposureMode, + ExifTag.WhiteBalance, + ExifTag.DigitalZoomRatio, + ExifTag.FocalLengthIn35mmFilm, + ExifTag.SceneCaptureType, + ExifTag.GainControl, + ExifTag.Contrast, + ExifTag.Saturation, + ExifTag.Sharpness, + ExifTag.DeviceSettingDescription, + ExifTag.SubjectDistanceRange, + ExifTag.ImageUniqueID, + ExifTag.OwnerName, + ExifTag.SerialNumber, + ExifTag.LensInfo, + ExifTag.LensMake, + ExifTag.LensModel, + ExifTag.LensSerialNumber + }; + + private static readonly ExifTag[] GPSTags = new ExifTag[31] + { + ExifTag.GPSVersionID, + ExifTag.GPSLatitudeRef, + ExifTag.GPSLatitude, + ExifTag.GPSLongitudeRef, + ExifTag.GPSLongitude, + ExifTag.GPSAltitudeRef, + ExifTag.GPSAltitude, + ExifTag.GPSTimestamp, + ExifTag.GPSSatellites, + ExifTag.GPSStatus, + ExifTag.GPSMeasureMode, + ExifTag.GPSDOP, + ExifTag.GPSSpeedRef, + ExifTag.GPSSpeed, + ExifTag.GPSTrackRef, + ExifTag.GPSTrack, + ExifTag.GPSImgDirectionRef, + ExifTag.GPSImgDirection, + ExifTag.GPSMapDatum, + ExifTag.GPSDestLatitudeRef, + ExifTag.GPSDestLatitude, + ExifTag.GPSDestLongitudeRef, + ExifTag.GPSDestLongitude, + ExifTag.GPSDestBearingRef, + ExifTag.GPSDestBearing, + ExifTag.GPSDestDistanceRef, + ExifTag.GPSDestDistance, + ExifTag.GPSProcessingMethod, + ExifTag.GPSAreaInformation, + ExifTag.GPSDateStamp, + ExifTag.GPSDifferential + }; + + private const int StartIndex = 6; + + private ExifParts allowedParts; + private Collection values; + private Collection dataOffsets; + private Collection ifdIndexes; + private Collection exifIndexes; + private Collection gpsIndexes; + + public ExifWriter(Collection values, ExifParts allowedParts) + { + this.values = values; + this.allowedParts = allowedParts; + this.ifdIndexes = this.GetIndexes(ExifParts.IfdTags, IfdTags); + this.exifIndexes = this.GetIndexes(ExifParts.ExifTags, ExifTags); + this.gpsIndexes = this.GetIndexes(ExifParts.GPSTags, GPSTags); + } + + public byte[] GetData() + { + uint length = 0; + int exifIndex = -1; + int gpsIndex = -1; + + if (this.exifIndexes.Count > 0) + { + exifIndex = (int)this.GetIndex(this.ifdIndexes, ExifTag.SubIFDOffset); + } + + if (this.gpsIndexes.Count > 0) + { + gpsIndex = (int)this.GetIndex(this.ifdIndexes, ExifTag.GPSIFDOffset); + } + + uint ifdLength = 2 + this.GetLength(this.ifdIndexes) + 4; + uint exifLength = this.GetLength(this.exifIndexes); + uint gpsLength = this.GetLength(this.gpsIndexes); + + if (exifLength > 0) + { + exifLength += 2; + } + + if (gpsLength > 0) + { + gpsLength += 2; + } + + length = ifdLength + exifLength + gpsLength; + + if (length == 6) + { + return null; + } + + length += 10 + 4 + 2; + + byte[] result = new byte[length]; + result[0] = (byte)'E'; + result[1] = (byte)'x'; + result[2] = (byte)'i'; + result[3] = (byte)'f'; + result[4] = 0x00; + result[5] = 0x00; + result[6] = (byte)'I'; + result[7] = (byte)'I'; + result[8] = 0x2A; + result[9] = 0x00; + + int i = 10; + uint ifdOffset = ((uint)i - StartIndex) + 4; + uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; + + if (exifLength > 0) + { + this.values[exifIndex].Value = ifdOffset + ifdLength; + } + + if (gpsLength > 0) + { + this.values[gpsIndex].Value = ifdOffset + ifdLength + exifLength; + } + + i = Write(BitConverter.GetBytes(ifdOffset), result, i); + i = this.WriteHeaders(this.ifdIndexes, result, i); + i = Write(BitConverter.GetBytes(thumbnailOffset), result, i); + i = this.WriteData(this.ifdIndexes, result, i); + + if (exifLength > 0) + { + i = this.WriteHeaders(this.exifIndexes, result, i); + i = this.WriteData(this.exifIndexes, result, i); + } + + if (gpsLength > 0) + { + i = this.WriteHeaders(this.gpsIndexes, result, i); + i = this.WriteData(this.gpsIndexes, result, i); + } + + Write(BitConverter.GetBytes((ushort)0), result, i); + + return result; + } + + private int GetIndex(Collection indexes, ExifTag tag) + { + foreach (int index in indexes) + { + if (this.values[index].Tag == tag) + { + return index; + } + } + + int newIndex = this.values.Count; + indexes.Add(newIndex); + this.values.Add(ExifValue.Create(tag, null)); + return newIndex; + } + + private Collection GetIndexes(ExifParts part, ExifTag[] tags) + { + if (((int)this.allowedParts & (int)part) == 0) + { + return new Collection(); + } + + Collection result = new Collection(); + for (int i = 0; i < this.values.Count; i++) + { + ExifValue value = this.values[i]; + + if (!value.HasValue) + { + continue; + } + + int index = Array.IndexOf(tags, value.Tag); + if (index > -1) + { + result.Add(i); + } + } + + return result; + } + + private uint GetLength(IEnumerable indexes) + { + uint length = 0; + + foreach (int index in indexes) + { + uint valueLength = (uint)this.values[index].Length; + + if (valueLength > 4) + { + length += 12 + valueLength; + } + else + { + length += 12; + } + } + + return length; + } + + private static int Write(byte[] source, byte[] destination, int offset) + { + Buffer.BlockCopy(source, 0, destination, offset, source.Length); + + return offset + source.Length; + } + + private int WriteArray(ExifValue value, byte[] destination, int offset) + { + if (value.DataType == ExifDataType.Ascii) + { + return this.WriteValue(ExifDataType.Ascii, value.Value, destination, offset); + } + + int newOffset = offset; + foreach (object obj in (Array)value.Value) + { + newOffset = this.WriteValue(value.DataType, obj, destination, newOffset); + } + + return newOffset; + } + + private int WriteData(Collection indexes, byte[] destination, int offset) + { + if (this.dataOffsets.Count == 0) + { + return offset; + } + + int newOffset = offset; + + int i = 0; + foreach (int index in indexes) + { + ExifValue value = this.values[index]; + if (value.Length > 4) + { + Write(BitConverter.GetBytes(newOffset - StartIndex), destination, this.dataOffsets[i++]); + newOffset = this.WriteValue(value, destination, newOffset); + } + } + + return newOffset; + } + + private int WriteHeaders(Collection indexes, byte[] destination, int offset) + { + this.dataOffsets = new Collection(); + + int newOffset = Write(BitConverter.GetBytes((ushort)indexes.Count), destination, offset); + + if (indexes.Count == 0) + { + return newOffset; + } + + foreach (int index in indexes) + { + ExifValue value = this.values[index]; + newOffset = Write(BitConverter.GetBytes((ushort)value.Tag), destination, newOffset); + newOffset = Write(BitConverter.GetBytes((ushort)value.DataType), destination, newOffset); + newOffset = Write(BitConverter.GetBytes((uint)value.NumberOfComponents), destination, newOffset); + + if (value.Length > 4) + { + this.dataOffsets.Add(newOffset); + } + else + { + this.WriteValue(value, destination, newOffset); + } + + newOffset += 4; + } + + return newOffset; + } + + private int WriteRational(Rational value, byte[] destination, int offset) + { + Write(BitConverter.GetBytes(value.Numerator), destination, offset); + Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4); + + return offset + 8; + } + + private int WriteSignedRational(SignedRational value, byte[] destination, int offset) + { + Write(BitConverter.GetBytes(value.Numerator), destination, offset); + Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4); + + return offset + 8; + } + + private int WriteValue(ExifDataType dataType, object value, byte[] destination, int offset) + { + switch (dataType) + { + case ExifDataType.Ascii: + return Write(Encoding.UTF8.GetBytes((string)value), destination, offset); + case ExifDataType.Byte: + case ExifDataType.Undefined: + destination[offset] = (byte)value; + return offset + 1; + case ExifDataType.DoubleFloat: + return Write(BitConverter.GetBytes((double)value), destination, offset); + case ExifDataType.Short: + return Write(BitConverter.GetBytes((ushort)value), destination, offset); + case ExifDataType.Long: + return Write(BitConverter.GetBytes((uint)value), destination, offset); + case ExifDataType.Rational: + return this.WriteRational((Rational)value, destination, offset); + case ExifDataType.SignedByte: + destination[offset] = unchecked((byte)((sbyte)value)); + return offset + 1; + case ExifDataType.SignedLong: + return Write(BitConverter.GetBytes((int)value), destination, offset); + case ExifDataType.SignedShort: + return Write(BitConverter.GetBytes((short)value), destination, offset); + case ExifDataType.SignedRational: + return this.WriteSignedRational((SignedRational)value, destination, offset); + case ExifDataType.SingleFloat: + return Write(BitConverter.GetBytes((float)value), destination, offset); + default: + throw new NotImplementedException(); + } + } + + private int WriteValue(ExifValue value, byte[] destination, int offset) + { + if (value.IsArray && value.DataType != ExifDataType.Ascii) + { + return this.WriteArray(value, destination, offset); + } + + return this.WriteValue(value.DataType, value.Value, destination, offset); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/README.md b/src/ImageSharp46/Profiles/Exif/README.md new file mode 100644 index 000000000..b6e27b70c --- /dev/null +++ b/src/ImageSharp46/Profiles/Exif/README.md @@ -0,0 +1,3 @@ +Adapted from Magick.NET: + +https://github.com/dlemstra/Magick.NET/tree/784e23b1f5c824fc03d4b95d3387b3efe1ed510b/Magick.NET/Core/Profiles/Exif \ No newline at end of file diff --git a/src/ImageSharp46/ProgressEventArgs.cs b/src/ImageSharp46/ProgressEventArgs.cs new file mode 100644 index 000000000..585dd1b63 --- /dev/null +++ b/src/ImageSharp46/ProgressEventArgs.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Contains event data related to the progress made processing an image. + /// + public class ProgressEventArgs : System.EventArgs + { + /// + /// Gets or sets the number of rows processed. + /// + public int RowsProcessed { get; set; } + + /// + /// Gets or sets the total number of rows. + /// + public int TotalRows { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Properties/AssemblyInfo.cs b/src/ImageSharp46/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3a9fc9d7e --- /dev/null +++ b/src/ImageSharp46/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageSharp")] +[assembly: AssemblyDescription("A cross-platform library for processing of image files; written in C#")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("James Jackson-South")] +[assembly: AssemblyProduct("ImageSharp")] +[assembly: AssemblyCopyright("Copyright (c) James Jackson-South and contributors.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersion("1.0.0.0")] + +// Ensure the internals can be tested. +[assembly: InternalsVisibleTo("ImageSharp.Benchmarks")] +[assembly: InternalsVisibleTo("ImageSharp.Tests")] +[assembly: InternalsVisibleTo("ImageSharp.Tests46")] \ No newline at end of file diff --git a/src/ImageSharp46/Quantizers/IQuantizer.cs b/src/ImageSharp46/Quantizers/IQuantizer.cs new file mode 100644 index 000000000..8b92af125 --- /dev/null +++ b/src/ImageSharp46/Quantizers/IQuantizer.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Quantizers +{ + /// + /// Provides methods for allowing quantization of images pixels. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public interface IQuantizer : IQuantizer + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Quantize an image and return the resulting output pixels. + /// + /// The image to quantize. + /// The maximum number of colors to return. + /// + /// A representing a quantized version of the image pixels. + /// + QuantizedImage Quantize(ImageBase image, int maxColors); + } + + /// + /// Provides methods for allowing quantization of images pixels. + /// + public interface IQuantizer + { + } +} diff --git a/src/ImageSharp46/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp46/Quantizers/Octree/OctreeQuantizer.cs new file mode 100644 index 000000000..5e06a721e --- /dev/null +++ b/src/ImageSharp46/Quantizers/Octree/OctreeQuantizer.cs @@ -0,0 +1,518 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Quantizers +{ + using System; + using System.Collections.Generic; + + /// + /// Encapsulates methods to calculate the colour palette if an image using an Octree pattern. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + public sealed class OctreeQuantizer : Quantizer + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Stores the tree + /// + private Octree octree; + + /// + /// Maximum allowed color depth + /// + private int colors; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, + /// the second pass quantizes a color based on the nodes in the tree + /// + public OctreeQuantizer() + : base(false) + { + } + + /// + public override QuantizedImage Quantize(ImageBase image, int maxColors) + { + this.colors = maxColors.Clamp(1, 256); + + if (this.octree == null) + { + // Construct the Octree + this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors)); + } + + return base.Quantize(image, maxColors); + } + + /// + /// Process the pixel in the first pass of the algorithm + /// + /// + /// The pixel to quantize + /// + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected override void InitialQuantizePixel(TColor pixel) + { + // Add the color to the Octree + this.octree.AddColor(pixel); + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// + /// The quantized value + /// + protected override byte QuantizePixel(TColor pixel) + { + return (byte)this.octree.GetPaletteIndex(pixel); + } + + /// + /// Retrieve the palette for the quantized image. + /// + /// + /// The new color palette + /// + protected override List GetPalette() + { + return this.octree.Palletize(Math.Max(this.colors, 1)); + } + + /// + /// Returns how many bits are required to store the specified number of colors. + /// Performs a Log2() on the value. + /// + /// The number of colors. + /// + /// The + /// + private int GetBitsNeededForColorDepth(int colorCount) + { + return (int)Math.Ceiling(Math.Log(colorCount, 2)); + } + + /// + /// Class which does the actual quantization + /// + private class Octree + { + /// + /// Mask used when getting the appropriate pixels for a given node + /// + private static readonly int[] Mask = { 0x100, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + + /// + /// The root of the Octree + /// + private readonly OctreeNode root; + + /// + /// Array of reducible nodes + /// + private readonly OctreeNode[] reducibleNodes; + + /// + /// Maximum number of significant bits in the image + /// + private readonly int maxColorBits; + + /// + /// Store the last node quantized + /// + private OctreeNode previousNode; + + /// + /// Cache the previous color quantized + /// + private TPacked previousColor; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of significant bits in the image + /// + public Octree(int maxColorBits) + { + this.maxColorBits = maxColorBits; + this.Leaves = 0; + this.reducibleNodes = new OctreeNode[9]; + this.root = new OctreeNode(0, this.maxColorBits, this); + this.previousColor = default(TPacked); + this.previousNode = null; + } + + /// + /// Gets or sets the number of leaves in the tree + /// + private int Leaves { get; set; } + + /// + /// Gets the array of reducible nodes + /// + private OctreeNode[] ReducibleNodes => this.reducibleNodes; + + /// + /// Add a given color value to the Octree + /// + /// + /// The containing color information to add. + /// + public void AddColor(TColor pixel) + { + TPacked packed = pixel.PackedValue; + + // Check if this request is for the same color as the last + if (this.previousColor.Equals(packed)) + { + // If so, check if I have a previous node setup. This will only occur if the first color in the image + // happens to be black, with an alpha component of zero. + if (this.previousNode == null) + { + this.previousColor = packed; + this.root.AddColor(pixel, this.maxColorBits, 0, this); + } + else + { + // Just update the previous node + this.previousNode.Increment(pixel); + } + } + else + { + this.previousColor = packed; + this.root.AddColor(pixel, this.maxColorBits, 0, this); + } + } + + /// + /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors + /// + /// The maximum number of colors + /// + /// An with the palletized colors + /// + public List Palletize(int colorCount) + { + while (this.Leaves > colorCount) + { + this.Reduce(); + } + + // Now palletize the nodes + List palette = new List(this.Leaves); + int paletteIndex = 0; + this.root.ConstructPalette(palette, ref paletteIndex); + + // And return the palette + return palette; + } + + /// + /// Get the palette index for the passed color + /// + /// The containing the pixel data. + /// + /// The index of the given structure. + /// + public int GetPaletteIndex(TColor pixel) + { + return this.root.GetPaletteIndex(pixel, 0); + } + + /// + /// Keep track of the previous node that was quantized + /// + /// + /// The node last quantized + /// + protected void TrackPrevious(OctreeNode node) + { + this.previousNode = node; + } + + /// + /// Reduce the depth of the tree + /// + private void Reduce() + { + // Find the deepest level containing at least one reducible node + int index = this.maxColorBits - 1; + while ((index > 0) && (this.reducibleNodes[index] == null)) + { + index--; + } + + // Reduce the node most recently added to the list at level 'index' + OctreeNode node = this.reducibleNodes[index]; + this.reducibleNodes[index] = node.NextReducible; + + // Decrement the leaf count after reducing the node + this.Leaves -= node.Reduce(); + + // And just in case I've reduced the last color to be added, and the next color to + // be added is the same, invalidate the previousNode... + this.previousNode = null; + } + + /// + /// Class which encapsulates each node in the tree + /// + protected class OctreeNode + { + /// + /// Pointers to any child nodes + /// + private readonly OctreeNode[] children; + + /// + /// Flag indicating that this is a leaf node + /// + private bool leaf; + + /// + /// Number of pixels in this node + /// + private int pixelCount; + + /// + /// Red component + /// + private int red; + + /// + /// Green Component + /// + private int green; + + /// + /// Blue component + /// + private int blue; + + /// + /// Alpha component + /// + private int alpha; + + /// + /// The index of this node in the palette + /// + private int paletteIndex; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The level in the tree = 0 - 7 + /// + /// + /// The number of significant color bits in the image + /// + /// + /// The tree to which this node belongs + /// + public OctreeNode(int level, int colorBits, Octree octree) + { + // Construct the new node + this.leaf = level == colorBits; + + this.red = this.green = this.blue = this.alpha = 0; + this.pixelCount = 0; + + // If a leaf, increment the leaf count + if (this.leaf) + { + octree.Leaves++; + this.NextReducible = null; + this.children = null; + } + else + { + // Otherwise add this to the reducible nodes + this.NextReducible = octree.ReducibleNodes[level]; + octree.ReducibleNodes[level] = this; + this.children = new OctreeNode[8]; + } + } + + /// + /// Gets the next reducible node + /// + public OctreeNode NextReducible { get; } + + /// + /// Add a color into the tree + /// + /// The color + /// The number of significant color bits + /// The level in the tree + /// The tree to which this node belongs + public void AddColor(TColor pixel, int colorBits, int level, Octree octree) + { + // Update the color information if this is a leaf + if (this.leaf) + { + this.Increment(pixel); + + // Setup the previous node + octree.TrackPrevious(this); + } + else + { + // Go to the next level down in the tree + int shift = 7 - level; + Color color = new Color(pixel.ToVector4()); + int index = ((color.A & Mask[0]) >> (shift - 3)) | + ((color.B & Mask[level + 1]) >> (shift - 2)) | + ((color.G & Mask[level + 1]) >> (shift - 1)) | + ((color.R & Mask[level + 1]) >> shift); + + OctreeNode child = this.children[index]; + + if (child == null) + { + // Create a new child node and store it in the array + child = new OctreeNode(level + 1, colorBits, octree); + this.children[index] = child; + } + + // Add the color to the child node + child.AddColor(pixel, colorBits, level + 1, octree); + } + } + + /// + /// Reduce this node by removing all of its children + /// + /// The number of leaves removed + public int Reduce() + { + this.red = this.green = this.blue = this.alpha = 0; + int childNodes = 0; + + // Loop through all children and add their information to this node + for (int index = 0; index < 8; index++) + { + if (this.children[index] != null) + { + this.red += this.children[index].red; + this.green += this.children[index].green; + this.blue += this.children[index].blue; + this.alpha += this.children[index].alpha; + this.pixelCount += this.children[index].pixelCount; + ++childNodes; + this.children[index] = null; + } + } + + // Now change this to a leaf node + this.leaf = true; + + // Return the number of nodes to decrement the leaf count by + return childNodes - 1; + } + + /// + /// Traverse the tree, building up the color palette + /// + /// The palette + /// The current palette index + public void ConstructPalette(List palette, ref int index) + { + if (this.leaf) + { + // Consume the next palette index + this.paletteIndex = index++; + + byte r = (this.red / this.pixelCount).ToByte(); + byte g = (this.green / this.pixelCount).ToByte(); + byte b = (this.blue / this.pixelCount).ToByte(); + byte a = (this.alpha / this.pixelCount).ToByte(); + + // And set the color of the palette entry + TColor pixel = default(TColor); + pixel.PackFromVector4(new Color(r, g, b, a).ToVector4()); + palette.Add(pixel); + } + else + { + // Loop through children looking for leaves + for (int i = 0; i < 8; i++) + { + if (this.children[i] != null) + { + this.children[i].ConstructPalette(palette, ref index); + } + } + } + } + + /// + /// Return the palette index for the passed color + /// + /// The representing the pixel. + /// The level. + /// + /// The representing the index of the pixel in the palette. + /// + public int GetPaletteIndex(TColor pixel, int level) + { + int index = this.paletteIndex; + + if (!this.leaf) + { + int shift = 7 - level; + Color color = new Color(pixel.ToVector4()); + int pixelIndex = ((color.A & Mask[0]) >> (shift - 3)) | + ((color.B & Mask[level + 1]) >> (shift - 2)) | + ((color.G & Mask[level + 1]) >> (shift - 1)) | + ((color.R & Mask[level + 1]) >> shift); + + if (this.children[pixelIndex] != null) + { + index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1); + } + else + { + throw new Exception($"Cannot retrive a pixel at the given index {pixelIndex}."); + } + } + + return index; + } + + /// + /// Increment the pixel count and add to the color information + /// + /// + /// The pixel to add. + /// + public void Increment(TColor pixel) + { + this.pixelCount++; + Color color = new Color(pixel.ToVector4()); + this.red += color.R; + this.green += color.G; + this.blue += color.B; + this.alpha += color.A; + } + } + } + } +} diff --git a/src/ImageSharp46/Quantizers/Octree/Quantizer.cs b/src/ImageSharp46/Quantizers/Octree/Quantizer.cs new file mode 100644 index 000000000..d88832634 --- /dev/null +++ b/src/ImageSharp46/Quantizers/Octree/Quantizer.cs @@ -0,0 +1,143 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Quantizers +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// Encapsulates methods to calculate the color palette of an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class Quantizer : IQuantizer + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Flag used to indicate whether a single pass or two passes are needed for quantization. + /// + private readonly bool singlePass; + + /// + /// Initializes a new instance of the class. + /// + /// + /// If true, the quantization only needs to loop through the source pixels once + /// + /// + /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image, + /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' + /// and then 'QuantizeImage'. + /// + protected Quantizer(bool singlePass) + { + this.singlePass = singlePass; + } + + /// + public virtual QuantizedImage Quantize(ImageBase image, int maxColors) + { + Guard.NotNull(image, nameof(image)); + + // Get the size of the source image + int height = image.Height; + int width = image.Width; + byte[] quantizedPixels = new byte[width * height]; + List palette; + + using (PixelAccessor pixels = image.Lock()) + { + // Call the FirstPass function if not a single pass algorithm. + // For something like an Octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!this.singlePass) + { + this.FirstPass(pixels, width, height); + } + + // Get the palette + palette = this.GetPalette(); + + this.SecondPass(pixels, quantizedPixels, width, height); + } + + return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels); + } + + /// + /// Execute the first pass through the pixels in the image + /// + /// The source data + /// The width in pixels of the image. + /// The height in pixels of the image. + protected virtual void FirstPass(PixelAccessor source, int width, int height) + { + // Loop through each row + for (int y = 0; y < height; y++) + { + // And loop through each column + for (int x = 0; x < width; x++) + { + // Now I have the pixel, call the FirstPassQuantize function... + this.InitialQuantizePixel(source[x, y]); + } + } + } + + /// + /// Execute a second pass through the bitmap + /// + /// The source image. + /// The output pixel array + /// The width in pixels of the image + /// The height in pixels of the image + protected virtual void SecondPass(PixelAccessor source, byte[] output, int width, int height) + { + Parallel.For( + 0, + source.Height, + Bootstrapper.Instance.ParallelOptions, + y => + { + for (int x = 0; x < source.Width; x++) + { + TColor sourcePixel = source[x, y]; + output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel); + } + }); + } + + /// + /// Override this to process the pixel in the first pass of the algorithm + /// + /// The pixel to quantize + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected virtual void InitialQuantizePixel(TColor pixel) + { + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// + /// The quantized value + /// + protected abstract byte QuantizePixel(TColor pixel); + + /// + /// Retrieve the palette for the quantized image + /// + /// + /// The new color palette + /// + protected abstract List GetPalette(); + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Quantizers/Options/Quantization.cs b/src/ImageSharp46/Quantizers/Options/Quantization.cs new file mode 100644 index 000000000..428c8e801 --- /dev/null +++ b/src/ImageSharp46/Quantizers/Options/Quantization.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Provides enumeration over how an image should be quantized. + /// + public enum Quantization + { + /// + /// An adaptive Octree quantizer. Fast with good quality. + /// + Octree, + + /// + /// Xiaolin Wu's Color Quantizer which generates high quality output. + /// + Wu, + + /// + /// Palette based, Uses the collection of web-safe colors by default. + /// + Palette + } +} diff --git a/src/ImageSharp46/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp46/Quantizers/Palette/PaletteQuantizer.cs new file mode 100644 index 000000000..73b7f1af2 --- /dev/null +++ b/src/ImageSharp46/Quantizers/Palette/PaletteQuantizer.cs @@ -0,0 +1,130 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Quantizers +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + + /// + /// Encapsulates methods to create a quantized image based upon the given palette. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class PaletteQuantizer : Quantizer + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// A lookup table for colors + /// + private readonly ConcurrentDictionary colorMap = new ConcurrentDictionary(); + + /// + /// List of all colors in the palette + /// + private TColor[] colors; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The color palette. If none is given this will default to the web safe colors defined + /// in the CSS Color Module Level 4. + /// + public PaletteQuantizer(TColor[] palette = null) + : base(true) + { + if (palette == null) + { + Color[] constants = ColorConstants.WebSafeColors; + List safe = new List { default(TColor) }; + foreach (Color c in constants) + { + TColor packed = default(TColor); + packed.PackFromVector4(c.ToVector4()); + safe.Add(packed); + } + + this.colors = safe.ToArray(); + } + else + { + this.colors = palette; + } + } + + /// + public override QuantizedImage Quantize(ImageBase image, int maxColors) + { + Array.Resize(ref this.colors, maxColors.Clamp(1, 256)); + return base.Quantize(image, maxColors); + } + + /// + protected override byte QuantizePixel(TColor pixel) + { + byte colorIndex = 0; + string colorHash = pixel.ToString(); + + // Check if the color is in the lookup table + if (this.colorMap.ContainsKey(colorHash)) + { + colorIndex = this.colorMap[colorHash]; + } + else + { + // Not found - loop through the palette and find the nearest match. + Color color = new Color(pixel.ToVector4()); + + int leastDistance = int.MaxValue; + int red = color.R; + int green = color.G; + int blue = color.B; + int alpha = color.A; + + for (int index = 0; index < this.colors.Length; index++) + { + Color paletteColor = new Color(this.colors[index].ToVector4()); + int redDistance = paletteColor.R - red; + int greenDistance = paletteColor.G - green; + int blueDistance = paletteColor.B - blue; + int alphaDistance = paletteColor.A - alpha; + + int distance = (redDistance * redDistance) + + (greenDistance * greenDistance) + + (blueDistance * blueDistance) + + (alphaDistance * alphaDistance); + + if (distance < leastDistance) + { + colorIndex = (byte)index; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (distance == 0) + { + break; + } + } + } + + // Now I have the color, pop it into the cache for next time + this.colorMap.TryAdd(colorHash, colorIndex); + } + + return colorIndex; + } + + /// + protected override List GetPalette() + { + return this.colors.ToList(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Quantizers/Quantize.cs b/src/ImageSharp46/Quantizers/Quantize.cs new file mode 100644 index 000000000..0a339a527 --- /dev/null +++ b/src/ImageSharp46/Quantizers/Quantize.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using ImageSharp.Quantizers; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies quantization to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The quantization mode to apply to perform the operation. + /// The maximum number of colors to return. Defaults to 256. + /// The . + public static Image Quantize(this Image source, Quantization mode = Quantization.Octree, int maxColors = 256) + where TColor : struct, IPackedPixel + where TPacked : struct + { + IQuantizer quantizer; + switch (mode) + { + case Quantization.Wu: + quantizer = new WuQuantizer(); + break; + + case Quantization.Palette: + quantizer = new PaletteQuantizer(); + break; + + default: + quantizer = new OctreeQuantizer(); + break; + } + + return Quantize(source, quantizer, maxColors); + } + + /// + /// Applies quantization to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// The maximum number of colors to return. + /// The . + public static Image Quantize(this Image source, IQuantizer quantizer, int maxColors) + where TColor : struct, IPackedPixel + where TPacked : struct + { + QuantizedImage quantizedImage = quantizer.Quantize(source, maxColors); + source.SetPixels(source.Width, source.Height, quantizedImage.ToImage().Pixels); + return source; + } + } +} diff --git a/src/ImageSharp46/Quantizers/QuantizedImage.cs b/src/ImageSharp46/Quantizers/QuantizedImage.cs new file mode 100644 index 000000000..956557c62 --- /dev/null +++ b/src/ImageSharp46/Quantizers/QuantizedImage.cs @@ -0,0 +1,94 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Quantizers +{ + using System; + using System.Threading.Tasks; + + /// + /// Represents a quantized image where the pixels indexed by a color palette. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class QuantizedImage + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The image width. + /// The image height. + /// The color palette. + /// The quantized pixels. + public QuantizedImage(int width, int height, TColor[] palette, byte[] pixels) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.NotNull(palette, nameof(palette)); + Guard.NotNull(pixels, nameof(pixels)); + + if (pixels.Length != width * height) + { + throw new ArgumentException( + $"Pixel array size must be {nameof(width)} * {nameof(height)}", nameof(pixels)); + } + + this.Width = width; + this.Height = height; + this.Palette = palette; + this.Pixels = pixels; + } + + /// + /// Gets the width of this . + /// + public int Width { get; } + + /// + /// Gets the height of this . + /// + public int Height { get; } + + /// + /// Gets the color palette of this . + /// + public TColor[] Palette { get; } + + /// + /// Gets the pixels of this . + /// + public byte[] Pixels { get; } + + /// + /// Converts this quantized image to a normal image. + /// + /// + /// The + /// + public Image ToImage() + { + Image image = new Image(); + + int pixelCount = this.Pixels.Length; + int palletCount = this.Palette.Length - 1; + TColor[] pixels = new TColor[pixelCount]; + + Parallel.For( + 0, + pixelCount, + Bootstrapper.Instance.ParallelOptions, + i => + { + TColor color = this.Palette[Math.Min(palletCount, this.Pixels[i])]; + pixels[i] = color; + }); + + image.SetPixels(this.Width, this.Height, pixels); + return image; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Quantizers/Wu/Box.cs b/src/ImageSharp46/Quantizers/Wu/Box.cs new file mode 100644 index 000000000..e715c7839 --- /dev/null +++ b/src/ImageSharp46/Quantizers/Wu/Box.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Quantizers +{ + /// + /// Represents a box color cube. + /// + internal sealed class Box + { + /// + /// Gets or sets the min red value, exclusive. + /// + public int R0 { get; set; } + + /// + /// Gets or sets the max red value, inclusive. + /// + public int R1 { get; set; } + + /// + /// Gets or sets the min green value, exclusive. + /// + public int G0 { get; set; } + + /// + /// Gets or sets the max green value, inclusive. + /// + public int G1 { get; set; } + + /// + /// Gets or sets the min blue value, exclusive. + /// + public int B0 { get; set; } + + /// + /// Gets or sets the max blue value, inclusive. + /// + public int B1 { get; set; } + + /// + /// Gets or sets the min alpha value, exclusive. + /// + public int A0 { get; set; } + + /// + /// Gets or sets the max alpha value, inclusive. + /// + public int A1 { get; set; } + + /// + /// Gets or sets the volume. + /// + public int Volume { get; set; } + } +} diff --git a/src/ImageSharp46/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp46/Quantizers/Wu/WuQuantizer.cs new file mode 100644 index 000000000..8eabe38e6 --- /dev/null +++ b/src/ImageSharp46/Quantizers/Wu/WuQuantizer.cs @@ -0,0 +1,777 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Quantizers +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An implementation of Wu's color quantizer with alpha channel. + /// + /// + /// + /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) + /// (see Graphics Gems volume II, pages 126-133) + /// (). + /// + /// + /// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel + /// + /// + /// + /// Algorithm: Greedy orthogonal bipartition of RGB space for variance + /// minimization aided by inclusion-exclusion tricks. + /// For speed no nearest neighbor search is done. Slightly + /// better performance can be expected by more sophisticated + /// but more expensive versions. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + public sealed class WuQuantizer : IQuantizer + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The index bits. + /// + private const int IndexBits = 6; + + /// + /// The index alpha bits. + /// + private const int IndexAlphaBits = 3; + + /// + /// The index count. + /// + private const int IndexCount = (1 << IndexBits) + 1; + + /// + /// The index alpha count. + /// + private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; + + /// + /// The table length. + /// + private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; + + /// + /// Moment of P(c). + /// + private readonly long[] vwt; + + /// + /// Moment of r*P(c). + /// + private readonly long[] vmr; + + /// + /// Moment of g*P(c). + /// + private readonly long[] vmg; + + /// + /// Moment of b*P(c). + /// + private readonly long[] vmb; + + /// + /// Moment of a*P(c). + /// + private readonly long[] vma; + + /// + /// Moment of c^2*P(c). + /// + private readonly double[] m2; + + /// + /// Color space tag. + /// + private readonly byte[] tag; + + /// + /// Initializes a new instance of the class. + /// + public WuQuantizer() + { + this.vwt = new long[TableLength]; + this.vmr = new long[TableLength]; + this.vmg = new long[TableLength]; + this.vmb = new long[TableLength]; + this.vma = new long[TableLength]; + this.m2 = new double[TableLength]; + this.tag = new byte[TableLength]; + } + + /// + public QuantizedImage Quantize(ImageBase image, int maxColors) + { + Guard.NotNull(image, nameof(image)); + + int colorCount = maxColors.Clamp(1, 256); + + this.Clear(); + + using (PixelAccessor imagePixels = image.Lock()) + { + this.Build3DHistogram(imagePixels); + this.Get3DMoments(); + + Box[] cube; + this.BuildCube(out cube, ref colorCount); + + return this.GenerateResult(imagePixels, colorCount, cube); + } + } + + /// + /// Gets an index. + /// + /// The red value. + /// The green value. + /// The blue value. + /// The alpha value. + /// The index. + private static int GetPaletteIndex(int r, int g, int b, int a) + { + return (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + + r + g + b + a; + } + + /// + /// Computes sum over a box of any given statistic. + /// + /// The cube. + /// The moment. + /// The result. + private static double Volume(Box cube, long[] moment) + { + return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + } + + /// + /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (depending on direction). + /// + /// The cube. + /// The direction. + /// The moment. + /// The result. + private static long Bottom(Box cube, int direction, long[] moment) + { + switch (direction) + { + // Red + case 0: + return -moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Green + case 1: + return -moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Blue + case 2: + return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Alpha + case 3: + return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + default: + throw new ArgumentOutOfRangeException(nameof(direction)); + } + } + + /// + /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction). + /// + /// The cube. + /// The direction. + /// The position. + /// The moment. + /// The result. + private static long Top(Box cube, int direction, int position, long[] moment) + { + switch (direction) + { + // Red + case 0: + return moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A1)] + - moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A0)] + - moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A1)] + + moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A0)] + - moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A1)] + + moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A0)] + + moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A1)] + - moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A0)]; + + // Green + case 1: + return moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A0)]; + + // Blue + case 2: + return moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A0)] + - moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A0)]; + + // Alpha + case 3: + return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, position)] + - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, position)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, position)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, position)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, position)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, position)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, position)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, position)]; + + default: + throw new ArgumentOutOfRangeException(nameof(direction)); + } + } + + /// + /// Clears the tables. + /// + private void Clear() + { + Array.Clear(this.vwt, 0, TableLength); + Array.Clear(this.vmr, 0, TableLength); + Array.Clear(this.vmg, 0, TableLength); + Array.Clear(this.vmb, 0, TableLength); + Array.Clear(this.vma, 0, TableLength); + Array.Clear(this.m2, 0, TableLength); + + Array.Clear(this.tag, 0, TableLength); + } + + /// + /// Builds a 3-D color histogram of counts, r/g/b, c^2. + /// + /// The pixel accessor. + private void Build3DHistogram(PixelAccessor pixels) + { + for (int y = 0; y < pixels.Height; y++) + { + for (int x = 0; x < pixels.Width; x++) + { + // Colors are expected in r->g->b->a format + Color color = new Color(pixels[x, y].ToVector4()); + + byte r = color.R; + byte g = color.G; + byte b = color.B; + byte a = color.A; + + int inr = r >> (8 - IndexBits); + int ing = g >> (8 - IndexBits); + int inb = b >> (8 - IndexBits); + int ina = a >> (8 - IndexAlphaBits); + + int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 1); + + this.vwt[ind]++; + this.vmr[ind] += r; + this.vmg[ind] += g; + this.vmb[ind] += b; + this.vma[ind] += a; + this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a); + } + } + } + + /// + /// Converts the histogram into moments so that we can rapidly calculate + /// the sums of the above quantities over any desired box. + /// + private void Get3DMoments() + { + long[] volume = new long[IndexCount * IndexAlphaCount]; + long[] volumeR = new long[IndexCount * IndexAlphaCount]; + long[] volumeG = new long[IndexCount * IndexAlphaCount]; + long[] volumeB = new long[IndexCount * IndexAlphaCount]; + long[] volumeA = new long[IndexCount * IndexAlphaCount]; + double[] volume2 = new double[IndexCount * IndexAlphaCount]; + + long[] area = new long[IndexAlphaCount]; + long[] areaR = new long[IndexAlphaCount]; + long[] areaG = new long[IndexAlphaCount]; + long[] areaB = new long[IndexAlphaCount]; + long[] areaA = new long[IndexAlphaCount]; + double[] area2 = new double[IndexAlphaCount]; + + for (int r = 1; r < IndexCount; r++) + { + Array.Clear(volume, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount); + Array.Clear(volume2, 0, IndexCount * IndexAlphaCount); + + for (int g = 1; g < IndexCount; g++) + { + Array.Clear(area, 0, IndexAlphaCount); + Array.Clear(areaR, 0, IndexAlphaCount); + Array.Clear(areaG, 0, IndexAlphaCount); + Array.Clear(areaB, 0, IndexAlphaCount); + Array.Clear(areaA, 0, IndexAlphaCount); + Array.Clear(area2, 0, IndexAlphaCount); + + for (int b = 1; b < IndexCount; b++) + { + long line = 0; + long lineR = 0; + long lineG = 0; + long lineB = 0; + long lineA = 0; + double line2 = 0; + + for (int a = 1; a < IndexAlphaCount; a++) + { + int ind1 = GetPaletteIndex(r, g, b, a); + + line += this.vwt[ind1]; + lineR += this.vmr[ind1]; + lineG += this.vmg[ind1]; + lineB += this.vmb[ind1]; + lineA += this.vma[ind1]; + line2 += this.m2[ind1]; + + area[a] += line; + areaR[a] += lineR; + areaG[a] += lineG; + areaB[a] += lineB; + areaA[a] += lineA; + area2[a] += line2; + + int inv = (b * IndexAlphaCount) + a; + + volume[inv] += area[a]; + volumeR[inv] += areaR[a]; + volumeG[inv] += areaG[a]; + volumeB[inv] += areaB[a]; + volumeA[inv] += areaA[a]; + volume2[inv] += area2[a]; + + int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); + + this.vwt[ind1] = this.vwt[ind2] + volume[inv]; + this.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; + this.vmg[ind1] = this.vmg[ind2] + volumeG[inv]; + this.vmb[ind1] = this.vmb[ind2] + volumeB[inv]; + this.vma[ind1] = this.vma[ind2] + volumeA[inv]; + this.m2[ind1] = this.m2[ind2] + volume2[inv]; + } + } + } + } + } + + /// + /// Computes the weighted variance of a box cube. + /// + /// The cube. + /// The . + private double Variance(Box cube) + { + double dr = Volume(cube, this.vmr); + double dg = Volume(cube, this.vmg); + double db = Volume(cube, this.vmb); + double da = Volume(cube, this.vma); + + double xx = + this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] + - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + + this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(cube, this.vwt)); + } + + /// + /// We want to minimize the sum of the variances of two sub-boxes. + /// The sum(c^2) terms can be ignored since their sum over both sub-boxes + /// is the same (the sum for the whole box) no matter where we split. + /// The remaining terms have a minus sign in the variance formula, + /// so we drop the minus sign and maximize the sum of the two terms. + /// + /// The cube. + /// The direction. + /// The first position. + /// The last position. + /// The cutting point. + /// The whole red. + /// The whole green. + /// The whole blue. + /// The whole alpha. + /// The whole weight. + /// The . + private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW) + { + long baseR = Bottom(cube, direction, this.vmr); + long baseG = Bottom(cube, direction, this.vmg); + long baseB = Bottom(cube, direction, this.vmb); + long baseA = Bottom(cube, direction, this.vma); + long baseW = Bottom(cube, direction, this.vwt); + + double max = 0.0; + cut = -1; + + for (int i = first; i < last; i++) + { + double halfR = baseR + Top(cube, direction, i, this.vmr); + double halfG = baseG + Top(cube, direction, i, this.vmg); + double halfB = baseB + Top(cube, direction, i, this.vmb); + double halfA = baseA + Top(cube, direction, i, this.vma); + double halfW = baseW + Top(cube, direction, i, this.vwt); + + double temp; + + if (Math.Abs(halfW) < Epsilon) + { + continue; + } + + temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; + + halfR = wholeR - halfR; + halfG = wholeG - halfG; + halfB = wholeB - halfB; + halfA = wholeA - halfA; + halfW = wholeW - halfW; + + if (Math.Abs(halfW) < Epsilon) + { + continue; + } + + temp += ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; + + if (temp > max) + { + max = temp; + cut = i; + } + } + + return max; + } + + /// + /// Cuts a box. + /// + /// The first set. + /// The second set. + /// Returns a value indicating whether the box has been split. + private bool Cut(Box set1, Box set2) + { + double wholeR = Volume(set1, this.vmr); + double wholeG = Volume(set1, this.vmg); + double wholeB = Volume(set1, this.vmb); + double wholeA = Volume(set1, this.vma); + double wholeW = Volume(set1, this.vwt); + + int cutr; + int cutg; + int cutb; + int cuta; + + double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW); + + int dir; + + if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) + { + dir = 0; + + if (cutr < 0) + { + return false; + } + } + else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) + { + dir = 1; + } + else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) + { + dir = 2; + } + else + { + dir = 3; + } + + set2.R1 = set1.R1; + set2.G1 = set1.G1; + set2.B1 = set1.B1; + set2.A1 = set1.A1; + + switch (dir) + { + // Red + case 0: + set2.R0 = set1.R1 = cutr; + set2.G0 = set1.G0; + set2.B0 = set1.B0; + set2.A0 = set1.A0; + break; + + // Green + case 1: + set2.G0 = set1.G1 = cutg; + set2.R0 = set1.R0; + set2.B0 = set1.B0; + set2.A0 = set1.A0; + break; + + // Blue + case 2: + set2.B0 = set1.B1 = cutb; + set2.R0 = set1.R0; + set2.G0 = set1.G0; + set2.A0 = set1.A0; + break; + + // Alpha + case 3: + set2.A0 = set1.A1 = cuta; + set2.R0 = set1.R0; + set2.G0 = set1.G0; + set2.B0 = set1.B0; + break; + } + + set1.Volume = (set1.R1 - set1.R0) * (set1.G1 - set1.G0) * (set1.B1 - set1.B0) * (set1.A1 - set1.A0); + set2.Volume = (set2.R1 - set2.R0) * (set2.G1 - set2.G0) * (set2.B1 - set2.B0) * (set2.A1 - set2.A0); + + return true; + } + + /// + /// Marks a color space tag. + /// + /// The cube. + /// A label. + private void Mark(Box cube, byte label) + { + for (int r = cube.R0 + 1; r <= cube.R1; r++) + { + for (int g = cube.G0 + 1; g <= cube.G1; g++) + { + for (int b = cube.B0 + 1; b <= cube.B1; b++) + { + for (int a = cube.A0 + 1; a <= cube.A1; a++) + { + this.tag[GetPaletteIndex(r, g, b, a)] = label; + } + } + } + } + } + + /// + /// Builds the cube. + /// + /// The cube. + /// The color count. + private void BuildCube(out Box[] cube, ref int colorCount) + { + cube = new Box[colorCount]; + double[] vv = new double[colorCount]; + + for (int i = 0; i < colorCount; i++) + { + cube[i] = new Box(); + } + + cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; + cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1; + cube[0].A1 = IndexAlphaCount - 1; + + int next = 0; + + for (int i = 1; i < colorCount; i++) + { + if (this.Cut(cube[next], cube[i])) + { + vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0; + vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0; + } + else + { + vv[next] = 0.0; + i--; + } + + next = 0; + + double temp = vv[0]; + for (int k = 1; k <= i; k++) + { + if (vv[k] > temp) + { + temp = vv[k]; + next = k; + } + } + + if (temp <= 0.0) + { + colorCount = i + 1; + break; + } + } + } + + /// + /// Generates the quantized result. + /// + /// The image pixels. + /// The color count. + /// The cube. + /// The result. + private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube) + { + List pallette = new List(); + byte[] pixels = new byte[imagePixels.Width * imagePixels.Height]; + int width = imagePixels.Width; + int height = imagePixels.Height; + + for (int k = 0; k < colorCount; k++) + { + this.Mark(cube[k], (byte)k); + + double weight = Volume(cube[k], this.vwt); + + if (Math.Abs(weight) > Epsilon) + { + float r = (float)(Volume(cube[k], this.vmr) / weight); + float g = (float)(Volume(cube[k], this.vmg) / weight); + float b = (float)(Volume(cube[k], this.vmb) / weight); + float a = (float)(Volume(cube[k], this.vma) / weight); + + TColor color = default(TColor); + color.PackFromVector4(new Vector4(r, g, b, a) / 255F); + pallette.Add(color); + } + else + { + pallette.Add(default(TColor)); + } + } + + Parallel.For( + 0, + height, + Bootstrapper.Instance.ParallelOptions, + y => + { + for (int x = 0; x < width; x++) + { + // Expected order r->g->b->a + Color color = new Color(imagePixels[x, y].ToVector4()); + int r = color.R >> (8 - IndexBits); + int g = color.G >> (8 - IndexBits); + int b = color.B >> (8 - IndexBits); + int a = color.A >> (8 - IndexAlphaBits); + + int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + pixels[(y * width) + x] = this.tag[ind]; + } + }); + + return new QuantizedImage(width, height, pallette.ToArray(), pixels); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/AutoOrient.cs b/src/ImageSharp46/Samplers/AutoOrient.cs new file mode 100644 index 000000000..6195a2066 --- /dev/null +++ b/src/ImageSharp46/Samplers/AutoOrient.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Adjusts an image so that its orientation is suitable for viewing. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to auto rotate. + /// The + public static Image AutoOrient(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Orientation orientation = GetExifOrientation(source); + + switch (orientation) + { + case Orientation.TopRight: + return source.Flip(FlipType.Horizontal); + + case Orientation.BottomRight: + return source.Rotate(RotateType.Rotate180); + + case Orientation.BottomLeft: + return source.Flip(FlipType.Vertical); + + case Orientation.LeftTop: + return source.Rotate(RotateType.Rotate90) + .Flip(FlipType.Horizontal); + + case Orientation.RightTop: + return source.Rotate(RotateType.Rotate90); + + case Orientation.RightBottom: + return source.Flip(FlipType.Vertical) + .Rotate(RotateType.Rotate270); + + case Orientation.LeftBottom: + return source.Rotate(RotateType.Rotate270); + + case Orientation.Unknown: + case Orientation.TopLeft: + default: + return source; + } + } + + /// + /// Returns the current EXIF orientation + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to auto rotate. + /// The + private static Orientation GetExifOrientation(Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + if (source.ExifProfile == null) + { + return Orientation.Unknown; + } + + ExifValue value = source.ExifProfile.GetValue(ExifTag.Orientation); + if (value == null) + { + return Orientation.Unknown; + } + + Orientation orientation = (Orientation)value.Value; + + source.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); + + return orientation; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/BoxBlur.cs b/src/ImageSharp46/Samplers/BoxBlur.cs new file mode 100644 index 000000000..4c0aa8d1f --- /dev/null +++ b/src/ImageSharp46/Samplers/BoxBlur.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies a box blur to the image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The . + public static Image BoxBlur(this Image source, int radius = 7) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return BoxBlur(source, radius, source.Bounds); + } + + /// + /// Applies a box blur to the image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image BoxBlur(this Image source, int radius, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new BoxBlurProcessor(radius)); + } + } +} diff --git a/src/ImageSharp46/Samplers/Crop.cs b/src/ImageSharp46/Samplers/Crop.cs new file mode 100644 index 000000000..a266db3b2 --- /dev/null +++ b/src/ImageSharp46/Samplers/Crop.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Crops an image to the given width and height. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The + public static Image Crop(this Image source, int width, int height) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Crop(source, width, height, source.Bounds); + } + + /// + /// Crops an image to the given width and height with the given source rectangle. + /// + /// If the source rectangle is smaller than the target dimensions then the + /// area within the source is resized performing a zoomed crop. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to crop. + /// The target image width. + /// The target image height. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The + public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + if (sourceRectangle.Width < width || sourceRectangle.Height < height) + { + // If the source rectangle is smaller than the target perform a + // cropped zoom. + source = source.Resize(sourceRectangle.Width, sourceRectangle.Height); + } + + CropProcessor processor = new CropProcessor(); + return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor); + } + } +} diff --git a/src/ImageSharp46/Samplers/DetectEdges.cs b/src/ImageSharp46/Samplers/DetectEdges.cs new file mode 100644 index 000000000..c28bb3c81 --- /dev/null +++ b/src/ImageSharp46/Samplers/DetectEdges.cs @@ -0,0 +1,161 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Detects any edges within the image. Uses the filter + /// operating in Grayscale mode. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The . + public static Image DetectEdges(this Image source) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return DetectEdges(source, source.Bounds, new SobelProcessor { Grayscale = true }); + } + + /// + /// Detects any edges within the image. Uses the filter + /// operating in Grayscale mode. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image DetectEdges(this Image source, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return DetectEdges(source, rectangle, new SobelProcessor { Grayscale = true }); + } + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The filter for detecting edges. + /// Whether to convert the image to Grayscale first. Defaults to true. + /// The . + public static Image DetectEdges(this Image source, EdgeDetection filter, bool grayscale = true) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return DetectEdges(source, filter, source.Bounds, grayscale); + } + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The filter for detecting edges. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// Whether to convert the image to Grayscale first. Defaults to true. + /// The . + public static Image DetectEdges(this Image source, EdgeDetection filter, Rectangle rectangle, bool grayscale = true) + where TColor : struct, IPackedPixel + where TPacked : struct + { + IEdgeDetectorFilter processor; + + switch (filter) + { + case EdgeDetection.Kayyali: + processor = new KayyaliProcessor { Grayscale = grayscale }; + break; + + case EdgeDetection.Kirsch: + processor = new KirschProcessor { Grayscale = grayscale }; + break; + + case EdgeDetection.Lapacian3X3: + processor = new Laplacian3X3Processor { Grayscale = grayscale }; + break; + + case EdgeDetection.Lapacian5X5: + processor = new Laplacian5X5Processor { Grayscale = grayscale }; + break; + + case EdgeDetection.LaplacianOfGaussian: + processor = new LaplacianOfGaussianProcessor { Grayscale = grayscale }; + break; + + case EdgeDetection.Prewitt: + processor = new PrewittProcessor { Grayscale = grayscale }; + break; + + case EdgeDetection.RobertsCross: + processor = new RobertsCrossProcessor { Grayscale = grayscale }; + break; + + case EdgeDetection.Robinson: + processor = new RobinsonProcessor { Grayscale = grayscale }; + break; + + case EdgeDetection.Scharr: + processor = new ScharrProcessor { Grayscale = grayscale }; + break; + + default: + processor = new SobelProcessor { Grayscale = grayscale }; + break; + } + + return DetectEdges(source, rectangle, processor); + } + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The filter for detecting edges. + /// The . + public static Image DetectEdges(this Image source, IEdgeDetectorFilter filter) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return DetectEdges(source, source.Bounds, filter); + } + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The filter for detecting edges. + /// The . + public static Image DetectEdges(this Image source, Rectangle rectangle, IEdgeDetectorFilter filter) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, filter); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/EntropyCrop.cs b/src/ImageSharp46/Samplers/EntropyCrop.cs new file mode 100644 index 000000000..28a2ebf48 --- /dev/null +++ b/src/ImageSharp46/Samplers/EntropyCrop.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Crops an image to the area of greatest entropy. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to crop. + /// The threshold for entropic density. + /// The + public static Image EntropyCrop(this Image source, float threshold = .5f) + where TColor : struct, IPackedPixel + where TPacked : struct + { + EntropyCropProcessor processor = new EntropyCropProcessor(threshold); + return source.Process(source.Width, source.Height, source.Bounds, Rectangle.Empty, processor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Flip.cs b/src/ImageSharp46/Samplers/Flip.cs new file mode 100644 index 000000000..0e50ad8ac --- /dev/null +++ b/src/ImageSharp46/Samplers/Flip.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Flips an image by the given instructions. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to rotate, flip, or both. + /// The to perform the flip. + /// The + public static Image Flip(this Image source, FlipType flipType) + where TColor : struct, IPackedPixel + where TPacked : struct + { + FlipProcessor processor = new FlipProcessor(flipType); + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/GuassianBlur.cs b/src/ImageSharp46/Samplers/GuassianBlur.cs new file mode 100644 index 000000000..ef09a18f5 --- /dev/null +++ b/src/ImageSharp46/Samplers/GuassianBlur.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies a Guassian blur to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The . + public static Image GuassianBlur(this Image source, float sigma = 3f) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return GuassianBlur(source, sigma, source.Bounds); + } + + /// + /// Applies a Guassian blur to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image GuassianBlur(this Image source, float sigma, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new GuassianBlurProcessor(sigma)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/GuassianSharpen.cs b/src/ImageSharp46/Samplers/GuassianSharpen.cs new file mode 100644 index 000000000..6b433f6ae --- /dev/null +++ b/src/ImageSharp46/Samplers/GuassianSharpen.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies a Guassian sharpening filter to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The . + public static Image GuassianSharpen(this Image source, float sigma = 3f) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return GuassianSharpen(source, sigma, source.Bounds); + } + + /// + /// Applies a Guassian sharpening filter to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image GuassianSharpen(this Image source, float sigma, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Process(rectangle, new GuassianSharpenProcessor(sigma)); + } + } +} diff --git a/src/ImageSharp46/Samplers/OilPainting.cs b/src/ImageSharp46/Samplers/OilPainting.cs new file mode 100644 index 000000000..a938b2325 --- /dev/null +++ b/src/ImageSharp46/Samplers/OilPainting.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the colors of the image recreating an oil painting effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image. + /// The number of neighbouring pixels used in calculating each individual pixel value. + /// The . + public static Image OilPaint(this Image source, int levels = 10, int brushSize = 15) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return OilPaint(source, levels, brushSize, source.Bounds); + } + + /// + /// Alters the colors of the image recreating an oil painting effect. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image. + /// The number of neighbouring pixels used in calculating each individual pixel value. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image OilPaint(this Image source, int levels, int brushSize, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + Guard.MustBeGreaterThan(levels, 0, nameof(levels)); + + if (brushSize <= 0 || brushSize > source.Height || brushSize > source.Width) + { + throw new ArgumentOutOfRangeException(nameof(brushSize)); + } + + return source.Process(rectangle, new OilPaintingProcessor(levels, brushSize)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Options/AnchorPosition.cs b/src/ImageSharp46/Samplers/Options/AnchorPosition.cs new file mode 100644 index 000000000..c0adb4da4 --- /dev/null +++ b/src/ImageSharp46/Samplers/Options/AnchorPosition.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerated anchor positions to apply to resized images. + /// + public enum AnchorPosition + { + /// + /// Anchors the position of the image to the center of it's bounding container. + /// + Center, + + /// + /// Anchors the position of the image to the top of it's bounding container. + /// + Top, + + /// + /// Anchors the position of the image to the bottom of it's bounding container. + /// + Bottom, + + /// + /// Anchors the position of the image to the left of it's bounding container. + /// + Left, + + /// + /// Anchors the position of the image to the right of it's bounding container. + /// + Right, + + /// + /// Anchors the position of the image to the top left side of it's bounding container. + /// + TopLeft, + + /// + /// Anchors the position of the image to the top right side of it's bounding container. + /// + TopRight, + + /// + /// Anchors the position of the image to the bottom right side of it's bounding container. + /// + BottomRight, + + /// + /// Anchors the position of the image to the bottom left side of it's bounding container. + /// + BottomLeft + } +} diff --git a/src/ImageSharp46/Samplers/Options/FlipType.cs b/src/ImageSharp46/Samplers/Options/FlipType.cs new file mode 100644 index 000000000..5a9668316 --- /dev/null +++ b/src/ImageSharp46/Samplers/Options/FlipType.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Provides enumeration over how a image should be flipped. + /// + public enum FlipType + { + /// + /// Don't flip the image. + /// + None, + + /// + /// Flip the image horizontally. + /// + Horizontal, + + /// + /// Flip the image vertically. + /// + Vertical, + } +} diff --git a/src/ImageSharp46/Samplers/Options/Orientation.cs b/src/ImageSharp46/Samplers/Options/Orientation.cs new file mode 100644 index 000000000..dfe57a4b0 --- /dev/null +++ b/src/ImageSharp46/Samplers/Options/Orientation.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + internal enum Orientation : ushort + { + Unknown = 0, + TopLeft = 1, + TopRight = 2, + BottomRight = 3, + BottomLeft = 4, + LeftTop = 5, + RightTop = 6, + RightBottom = 7, + LeftBottom = 8 + } +} diff --git a/src/ImageSharp46/Samplers/Options/ResizeHelper.cs b/src/ImageSharp46/Samplers/Options/ResizeHelper.cs new file mode 100644 index 000000000..33aa32528 --- /dev/null +++ b/src/ImageSharp46/Samplers/Options/ResizeHelper.cs @@ -0,0 +1,441 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// Provides methods to help calculate the target rectangle when resizing using the + /// enumeration. + /// + internal static class ResizeHelper + { + /// + /// Calculates the target location and bounds to perform the resize operation against. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The source image. + /// The resize options. + /// + /// The . + /// + public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + where TColor : struct, IPackedPixel + where TPacked : struct + { + switch (options.Mode) + { + case ResizeMode.Crop: + return CalculateCropRectangle(source, options); + case ResizeMode.Pad: + return CalculatePadRectangle(source, options); + case ResizeMode.BoxPad: + return CalculateBoxPadRectangle(source, options); + case ResizeMode.Max: + return CalculateMaxRectangle(source, options); + case ResizeMode.Min: + return CalculateMinRectangle(source, options); + + // Last case ResizeMode.Stretch: + default: + return new Rectangle(0, 0, options.Size.Width, options.Size.Height); + } + } + + /// + /// Calculates the target rectangle for crop mode. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + double ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentWidth; + + if (options.CenterCoordinates.Any()) + { + double center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); + destinationY = (int)center + (height / 2); + + if (destinationY > 0) + { + destinationY = 0; + } + + if (destinationY < (int)(height - (sourceHeight * ratio))) + { + destinationY = (int)(height - (sourceHeight * ratio)); + } + } + else + { + switch (options.Position) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + destinationY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + destinationY = (int)(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)((height - (sourceHeight * ratio)) / 2); + break; + } + } + + destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); + } + else + { + ratio = percentHeight; + + if (options.CenterCoordinates.Any()) + { + double center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; + destinationX = (int)center + (width / 2); + + if (destinationX > 0) + { + destinationX = 0; + } + + if (destinationX < (int)(width - (sourceWidth * ratio))) + { + destinationX = (int)(width - (sourceWidth * ratio)); + } + } + else + { + switch (options.Position) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + destinationX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + destinationX = (int)(width - (sourceWidth * ratio)); + break; + default: + destinationX = (int)((width - (sourceWidth * ratio)) / 2); + break; + } + } + + destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for pad mode. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + double ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentHeight; + destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); + + switch (options.Position) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + destinationX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + destinationX = (int)(width - (sourceWidth * ratio)); + break; + default: + destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); + break; + } + } + else + { + ratio = percentWidth; + destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); + + switch (options.Position) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + destinationY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + destinationY = (int)(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)((height - (sourceHeight * ratio)) / 2); + break; + } + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for box pad mode. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); + + // Only calculate if upscaling. + if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) + { + int destinationX; + int destinationY; + int destinationWidth = sourceWidth; + int destinationHeight = sourceHeight; + width = boxPadWidth; + height = boxPadHeight; + + switch (options.Position) + { + case AnchorPosition.Left: + destinationY = (height - sourceHeight) / 2; + destinationX = 0; + break; + case AnchorPosition.Right: + destinationY = (height - sourceHeight) / 2; + destinationX = width - sourceWidth; + break; + case AnchorPosition.TopRight: + destinationY = 0; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Top: + destinationY = 0; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.TopLeft: + destinationY = 0; + destinationX = 0; + break; + case AnchorPosition.BottomRight: + destinationY = height - sourceHeight; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Bottom: + destinationY = height - sourceHeight; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.BottomLeft: + destinationY = height - sourceHeight; + destinationX = 0; + break; + default: + destinationY = (height - sourceHeight) / 2; + destinationX = (width - sourceWidth) / 2; + break; + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + // Switch to pad mode to downscale and calculate from there. + return CalculatePadRectangle(source, options); + } + + /// + /// Calculates the target rectangle for max mode. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int width = options.Size.Width; + int height = options.Size.Height; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)source.Height); + double percentWidth = Math.Abs(width / (double)source.Width); + + // Integers must be cast to doubles to get needed precision + double ratio = (double)options.Size.Height / options.Size.Width; + double sourceRatio = (double)source.Height / source.Width; + + if (sourceRatio < ratio) + { + destinationHeight = Convert.ToInt32(source.Height * percentWidth); + height = destinationHeight; + } + else + { + destinationWidth = Convert.ToInt32(source.Width * percentHeight); + width = destinationWidth; + } + + // Replace the size to match the rectangle. + options.Size = new Size(width, height); + return new Rectangle(0, 0, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for min mode. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + where TColor : struct, IPackedPixel + where TPacked : struct + { + int width = options.Size.Width; + int height = options.Size.Height; + int destinationWidth; + int destinationHeight; + + // Don't upscale + if (width > source.Width || height > source.Height) + { + options.Size = new Size(source.Width, source.Height); + return new Rectangle(0, 0, source.Width, source.Height); + } + + double sourceRatio = (double)source.Height / source.Width; + + // Find the shortest distance to go. + int widthDiff = source.Width - width; + int heightDiff = source.Height - height; + + if (widthDiff < heightDiff) + { + destinationHeight = Convert.ToInt32(width * sourceRatio); + height = destinationHeight; + destinationWidth = width; + } + else if (widthDiff > heightDiff) + { + destinationWidth = Convert.ToInt32(height / sourceRatio); + destinationHeight = height; + width = destinationWidth; + } + else + { + destinationWidth = width; + destinationHeight = height; + } + + // Replace the size to match the rectangle. + options.Size = new Size(width, height); + return new Rectangle(0, 0, destinationWidth, destinationHeight); + } + } +} diff --git a/src/ImageSharp46/Samplers/Options/ResizeMode.cs b/src/ImageSharp46/Samplers/Options/ResizeMode.cs new file mode 100644 index 000000000..7a1cc3c94 --- /dev/null +++ b/src/ImageSharp46/Samplers/Options/ResizeMode.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerated resize modes to apply to resized images. + /// + public enum ResizeMode + { + /// + /// Crops the resized image to fit the bounds of its container. + /// + Crop, + + /// + /// Pads the resized image to fit the bounds of its container. + /// If only one dimension is passed, will maintain the original aspect ratio. + /// + Pad, + + /// + /// Pads the image to fit the bound of the container without resizing the + /// original source. + /// When downscaling, performs the same functionality as + /// + BoxPad, + + /// + /// Constrains the resized image to fit the bounds of its container maintaining + /// the original aspect ratio. + /// + Max, + + /// + /// Resizes the image until the shortest side reaches the set given dimension. + /// Upscaling is disabled in this mode and the original image will be returned + /// if attempted. + /// + Min, + + /// + /// Stretches the resized image to fit the bounds of its container. + /// + Stretch + } +} diff --git a/src/ImageSharp46/Samplers/Options/ResizeOptions.cs b/src/ImageSharp46/Samplers/Options/ResizeOptions.cs new file mode 100644 index 000000000..3cfb3d6c1 --- /dev/null +++ b/src/ImageSharp46/Samplers/Options/ResizeOptions.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// The resize options for resizing images against certain modes. + /// + public class ResizeOptions + { + /// + /// Gets or sets the resize mode. + /// + public ResizeMode Mode { get; set; } = ResizeMode.Crop; + + /// + /// Gets or sets the anchor position. + /// + public AnchorPosition Position { get; set; } = AnchorPosition.Center; + + /// + /// Gets or sets the center coordinates. + /// + public IEnumerable CenterCoordinates { get; set; } = Enumerable.Empty(); + + /// + /// Gets or sets the target size. + /// + public Size Size { get; set; } + + /// + /// Gets or sets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; set; } = new BicubicResampler(); + + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + public bool Compand { get; set; } + } +} diff --git a/src/ImageSharp46/Samplers/Options/RotateType.cs b/src/ImageSharp46/Samplers/Options/RotateType.cs new file mode 100644 index 000000000..0545aa910 --- /dev/null +++ b/src/ImageSharp46/Samplers/Options/RotateType.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Provides enumeration over how the image should be rotated. + /// + public enum RotateType + { + /// + /// Do not rotate the image. + /// + None, + + /// + /// Rotate the image by 90 degrees clockwise. + /// + Rotate90 = 90, + + /// + /// Rotate the image by 180 degrees clockwise. + /// + Rotate180 = 180, + + /// + /// Rotate the image by 270 degrees clockwise. + /// + Rotate270 = 270 + } +} diff --git a/src/ImageSharp46/Samplers/Pad.cs b/src/ImageSharp46/Samplers/Pad.cs new file mode 100644 index 000000000..3c4db20bd --- /dev/null +++ b/src/ImageSharp46/Samplers/Pad.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Evenly pads an image to fit the new dimensions. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The source image to pad. + /// The new width. + /// The new height. + /// The . + public static Image Pad(this Image source, int width, int height) + where TColor : struct, IPackedPixel + where TPacked : struct + { + ResizeOptions options = new ResizeOptions + { + Size = new Size(width, height), + Mode = ResizeMode.BoxPad, + Sampler = new NearestNeighborResampler() + }; + + return Resize(source, options); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Pixelate.cs b/src/ImageSharp46/Samplers/Pixelate.cs new file mode 100644 index 000000000..e7bd84c31 --- /dev/null +++ b/src/ImageSharp46/Samplers/Pixelate.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Pixelates an image with the given pixel size. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The size of the pixels. + /// The . + public static Image Pixelate(this Image source, int size = 4) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Pixelate(source, size, source.Bounds); + } + + /// + /// Pixelates an image with the given pixel size. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image this method extends. + /// The size of the pixels. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Pixelate(this Image source, int size, Rectangle rectangle) + where TColor : struct, IPackedPixel + where TPacked : struct + { + if (size <= 0 || size > source.Height || size > source.Width) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + return source.Process(rectangle, new PixelateProcessor(size)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/CompandingResizeProcessor.cs b/src/ImageSharp46/Samplers/Processors/CompandingResizeProcessor.cs new file mode 100644 index 000000000..2e7a8ec9a --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/CompandingResizeProcessor.cs @@ -0,0 +1,159 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the resizing of images using various algorithms. + /// This version will expand and compress the image to and from a linear color space during processing. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class CompandingResizeProcessor : ResamplingWeightedProcessor + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The sampler to perform the resize operation. + /// + public CompandingResizeProcessor(IResampler sampler) + : base(sampler) + { + } + + /// + public override bool Compand { get; set; } = true; + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + // Jump out, we'll deal with that later. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + return; + } + + int width = target.Width; + int height = target.Height; + int sourceHeight = sourceRectangle.Height; + int targetX = target.Bounds.X; + int targetY = target.Bounds.Y; + int targetRight = target.Bounds.Right; + int targetBottom = target.Bounds.Bottom; + int startX = targetRectangle.X; + int endX = targetRectangle.Right; + + int minX = Math.Max(targetX, startX); + int maxX = Math.Min(targetRight, endX); + int minY = Math.Max(targetY, startY); + int maxY = Math.Min(targetBottom, endY); + + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); + + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; + } + }); + } + + // Break out now. + return; + } + + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + Image firstPass = new Image(target.Width, source.Height); + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor firstPassPixels = firstPass.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + minX = Math.Max(0, startX); + maxX = Math.Min(width, endX); + minY = Math.Max(0, startY); + maxY = Math.Min(height, endY); + + Parallel.For( + 0, + sourceHeight, + this.ParallelOptions, + y => + { + for (int x = minX; x < maxX; x++) + { + // Ensure offsets are normalised for cropping and padding. + Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; + + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < horizontalValues.Length; i++) + { + Weight xw = horizontalValues[i]; + destination += sourcePixels[xw.Index, y].ToVector4().Expand() * xw.Value; + } + + TColor d = default(TColor); + d.PackFromVector4(destination.Compress()); + firstPassPixels[x, y] = d; + } + }); + + // Now process the rows. + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Ensure offsets are normalised for cropping and padding. + Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + + for (int x = 0; x < width; x++) + { + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < verticalValues.Length; i++) + { + Weight yw = verticalValues[i]; + destination += firstPassPixels[x, yw.Index].ToVector4().Expand() * yw.Value; + } + + TColor d = default(TColor); + d.PackFromVector4(destination.Compress()); + targetPixels[x, y] = d; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/BoxBlurProcessor.cs new file mode 100644 index 000000000..7b87a3d1b --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/BoxBlurProcessor.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + /// + /// Applies a Box blur filter to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class BoxBlurProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The maximum size of the kernel in either direction. + /// + private readonly int kernelSize; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public BoxBlurProcessor(int radius = 7) + { + this.kernelSize = (radius * 2) + 1; + this.KernelX = this.CreateBoxKernel(true); + this.KernelY = this.CreateBoxKernel(false); + } + + /// + /// Gets the horizontal gradient operator. + /// + public float[][] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public float[][] KernelY { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + new Convolution2PassFilter(this.KernelX, this.KernelY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); + } + + /// + /// Create a 1 dimensional Box kernel. + /// + /// Whether to calculate a horizontal kernel. + /// The + private float[][] CreateBoxKernel(bool horizontal) + { + int size = this.kernelSize; + float[][] kernel = horizontal ? new float[1][] : new float[size][]; + + if (horizontal) + { + kernel[0] = new float[size]; + } + + float sum = 0.0f; + + for (int i = 0; i < size; i++) + { + float x = 1; + sum += x; + if (horizontal) + { + kernel[0][i] = x; + } + else + { + kernel[i] = new[] { x }; + } + } + + // Normalise kernel so that the sum of all weights equals 1 + if (horizontal) + { + for (int i = 0; i < size; i++) + { + kernel[0][i] = kernel[0][i] / sum; + } + } + else + { + for (int i = 0; i < size; i++) + { + kernel[i][0] = kernel[i][0] / sum; + } + } + + return kernel; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2DFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2DFilter.cs new file mode 100644 index 000000000..1b77e41eb --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2DFilter.cs @@ -0,0 +1,129 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Defines a filter that uses two one-dimensional matrices to perform convolution against an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class Convolution2DFilter : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + public Convolution2DFilter(float[][] kernelX, float[][] kernelY) + { + this.KernelX = kernelX; + this.KernelY = kernelY; + } + + /// + /// Gets the horizontal gradient operator. + /// + public float[][] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public float[][] KernelY { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int kernelYHeight = this.KernelY.Length; + int kernelYWidth = this.KernelY[0].Length; + int kernelXHeight = this.KernelX.Length; + int kernelXWidth = this.KernelX[0].Length; + int radiusY = kernelYHeight >> 1; + int radiusX = kernelXWidth >> 1; + + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = sourceBottom - 1; + int maxX = endX - 1; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + float rX = 0; + float gX = 0; + float bX = 0; + float rY = 0; + float gY = 0; + float bY = 0; + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelYHeight; fy++) + { + int fyr = fy - radiusY; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelXWidth; fx++) + { + int fxr = fx - radiusX; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + float r = currentColor.X; + float g = currentColor.Y; + float b = currentColor.Z; + + if (fy < kernelXHeight) + { + rX += this.KernelX[fy][fx] * r; + gX += this.KernelX[fy][fx] * g; + bX += this.KernelX[fy][fx] * b; + } + + if (fx < kernelYWidth) + { + rY += this.KernelY[fy][fx] * r; + gY += this.KernelY[fy][fx] * g; + bY += this.KernelY[fy][fx] * b; + } + } + } + + float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); + float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); + float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); + + Vector4 targetColor = targetPixels[x, y].ToVector4(); + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, targetColor.Z)); + targetPixels[x, y] = packed; + } + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2PassFilter.cs new file mode 100644 index 000000000..4cab6f0b2 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2PassFilter.cs @@ -0,0 +1,118 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Defines a filter that uses two one-dimensional matrices to perform two-pass convolution against an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class Convolution2PassFilter : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + public Convolution2PassFilter(float[][] kernelX, float[][] kernelY) + { + this.KernelX = kernelX; + this.KernelY = kernelY; + } + + /// + /// Gets the horizontal gradient operator. + /// + public float[][] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public float[][] KernelY { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + float[][] kernelX = this.KernelX; + float[][] kernelY = this.KernelY; + + ImageBase firstPass = new Image(source.Width, source.Height); + this.ApplyConvolution(firstPass, source, sourceRectangle, startY, endY, kernelX); + this.ApplyConvolution(target, firstPass, sourceRectangle, startY, endY, kernelY); + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The index of the row within the source image to start processing. + /// The index of the row within the source image to end processing. + /// The kernel operator. + private void ApplyConvolution(ImageBase target, ImageBase source, Rectangle sourceRectangle, int startY, int endY, float[][] kernel) + { + int kernelHeight = kernel.Length; + int kernelWidth = kernel[0].Length; + int radiusY = kernelHeight >> 1; + int radiusX = kernelWidth >> 1; + + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = sourceBottom - 1; + int maxX = endX - 1; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => + { + for (int x = startX; x < endX; x++) + { + Vector4 destination = default(Vector4); + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelHeight; fy++) + { + int fyr = fy - radiusY; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelWidth; fx++) + { + int fxr = fx - radiusX; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + destination += kernel[fy][fx] * currentColor; + } + } + + TColor packed = default(TColor); + packed.PackFromVector4(destination); + targetPixels[x, y] = packed; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/ConvolutionFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/ConvolutionFilter.cs new file mode 100644 index 000000000..c94bee764 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/ConvolutionFilter.cs @@ -0,0 +1,105 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Defines a filter that uses a 2 dimensional matrix to perform convolution against an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class ConvolutionFilter : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The 2d gradient operator. + public ConvolutionFilter(float[][] kernelXY) + { + this.KernelXY = kernelXY; + } + + /// + /// Gets the 2d gradient operator. + /// + public virtual float[][] KernelXY { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + float[][] kernelX = this.KernelXY; + int kernelLength = kernelX.GetLength(0); + int radius = kernelLength >> 1; + + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = sourceBottom - 1; + int maxX = endX - 1; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + float rX = 0; + float gX = 0; + float bX = 0; + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelLength; fy++) + { + int fyr = fy - radius; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelLength; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + float r = currentColor.X; + float g = currentColor.Y; + float b = currentColor.Z; + + rX += kernelX[fy][fx] * r; + gX += kernelX[fy][fx] * g; + bX += kernelX[fy][fx] * b; + } + } + + float red = rX; + float green = gX; + float blue = bX; + + Vector4 targetColor = targetPixels[x, y].ToVector4(); + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, targetColor.Z)); + targetPixels[x, y] = packed; + } + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs new file mode 100644 index 000000000..a689415f6 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + /// + /// Defines a filter that detects edges within an image using two + /// one-dimensional matrices. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class EdgeDetector2DFilter : ImageSampler, IEdgeDetectorFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Gets the horizontal gradient operator. + /// + public abstract float[][] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public abstract float[][] KernelY { get; } + + /// + public bool Grayscale { get; set; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + // TODO: Figure out a way to pass event handlers to child classes. + new Convolution2DFilter(this.KernelX, this.KernelY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); + } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (this.Grayscale) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorCompassFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorCompassFilter.cs new file mode 100644 index 000000000..6d5053111 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorCompassFilter.cs @@ -0,0 +1,139 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Defines a filter that detects edges within an image using a eight two dimensional matrices. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class EdgeDetectorCompassFilter : ImageSampler, IEdgeDetectorFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Gets the North gradient operator + /// + public abstract float[][] North { get; } + + /// + /// Gets the NorthWest gradient operator + /// + public abstract float[][] NorthWest { get; } + + /// + /// Gets the West gradient operator + /// + public abstract float[][] West { get; } + + /// + /// Gets the SouthWest gradient operator + /// + public abstract float[][] SouthWest { get; } + + /// + /// Gets the South gradient operator + /// + public abstract float[][] South { get; } + + /// + /// Gets the SouthEast gradient operator + /// + public abstract float[][] SouthEast { get; } + + /// + /// Gets the East gradient operator + /// + public abstract float[][] East { get; } + + /// + /// Gets the NorthEast gradient operator + /// + public abstract float[][] NorthEast { get; } + + /// + public bool Grayscale { get; set; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + float[][][] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; + + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // First run. + new ConvolutionFilter(kernels[0]).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); + + if (kernels.Length == 1) + { + return; + } + + int shiftY = startY; + int shiftX = startX; + + // Reset offset if necessary. + if (minX > 0) + { + shiftX = 0; + } + + if (minY > 0) + { + shiftY = 0; + } + + // Additional runs. + for (int i = 1; i < kernels.Length; i++) + { + ImageBase pass = new Image(source.Width, source.Height); + new ConvolutionFilter(kernels[i]).Apply(pass, source, sourceRectangle, targetRectangle, startY, endY); + + using (PixelAccessor passPixels = pass.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - shiftY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - shiftX; + + // Grab the max components of the two pixels + TColor packed = default(TColor); + packed.PackFromVector4(Vector4.Max(passPixels[offsetX, offsetY].ToVector4(), targetPixels[offsetX, offsetY].ToVector4())); + targetPixels[offsetX, offsetY] = packed; + } + }); + } + } + } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (this.Grayscale) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs new file mode 100644 index 000000000..a565dbfc1 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + /// + /// Defines a filter that detects edges within an image using a single two dimensional matrix. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class EdgeDetectorFilter : ImageSampler, IEdgeDetectorFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public bool Grayscale { get; set; } + + /// + /// Gets the 2d gradient operator. + /// + public abstract float[][] KernelXY { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + new ConvolutionFilter(this.KernelXY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); + } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (this.Grayscale) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + } + } +} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs new file mode 100644 index 000000000..700201ea4 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + /// + /// Provides properties and methods allowing the detection of edges within an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public interface IEdgeDetectorFilter : IImageSampler, IEdgeDetectorFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + } + + /// + /// Provides properties and methods allowing the detection of edges within an image. + /// + public interface IEdgeDetectorFilter + { + /// + /// Gets or sets a value indicating whether to convert the image to grayscale before performing edge detection. + /// + bool Grayscale { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs new file mode 100644 index 000000000..cbe45f8b6 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Kayyali operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class KayyaliProcessor : EdgeDetector2DFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The horizontal gradient operator. + /// + private static readonly float[][] KayyaliX = + { + new float[] { 6, 0, -6 }, + new float[] { 0, 0, 0 }, + new float[] { -6, 0, 6 } + }; + + /// + /// The vertical gradient operator. + /// + private static readonly float[][] KayyaliY = + { + new float[] { -6, 0, 6 }, + new float[] { 0, 0, 0 }, + new float[] { 6, 0, -6 } + }; + + /// + public override float[][] KernelX => KayyaliX; + + /// + public override float[][] KernelY => KayyaliY; + } +} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KirschProcessor.cs new file mode 100644 index 000000000..b8ead4192 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KirschProcessor.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Kirsch operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class KirschProcessor : EdgeDetectorCompassFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The North gradient operator + /// + private static readonly float[][] KirschNorth = + { + new float[] { 5, 5, 5 }, + new float[] { -3, 0, -3 }, + new float[] { -3, -3, -3 } + }; + + /// + /// The NorthWest gradient operator + /// + private static readonly float[][] KirschNorthWest = + { + new float[] { 5, 5, -3 }, + new float[] { 5, 0, -3 }, + new float[] { -3, -3, -3 } + }; + + /// + /// The West gradient operator + /// + private static readonly float[][] KirschWest = + { + new float[] { 5, -3, -3 }, + new float[] { 5, 0, -3 }, + new float[] { 5, -3, -3 } + }; + + /// + /// The SouthWest gradient operator + /// + private static readonly float[][] KirschSouthWest = + { + new float[] { -3, -3, -3 }, + new float[] { 5, 0, -3 }, + new float[] { 5, 5, -3 } + }; + + /// + /// The South gradient operator + /// + private static readonly float[][] KirschSouth = + { + new float[] { -3, -3, -3 }, + new float[] { -3, 0, -3 }, + new float[] { 5, 5, 5 } + }; + + /// + /// The SouthEast gradient operator + /// + private static readonly float[][] KirschSouthEast = + { + new float[] { -3, -3, -3 }, + new float[] { -3, 0, 5 }, + new float[] { -3, 5, 5 } + }; + + /// + /// The East gradient operator + /// + private static readonly float[][] KirschEast = + { + new float[] { -3, -3, 5 }, + new float[] { -3, 0, 5 }, + new float[] { -3, -3, 5 } + }; + + /// + /// The NorthEast gradient operator + /// + private static readonly float[][] KirschNorthEast = + { + new float[] { -3, 5, 5 }, + new float[] { -3, 0, 5 }, + new float[] { -3, -3, -3 } + }; + + /// + public override float[][] North => KirschNorth; + + /// + public override float[][] NorthWest => KirschNorthWest; + + /// + public override float[][] West => KirschWest; + + /// + public override float[][] SouthWest => KirschSouthWest; + + /// + public override float[][] South => KirschSouth; + + /// + public override float[][] SouthEast => KirschSouthEast; + + /// + public override float[][] East => KirschEast; + + /// + public override float[][] NorthEast => KirschNorthEast; + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs new file mode 100644 index 000000000..9fbf71674 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Laplacian 3 x 3 operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class Laplacian3X3Processor : EdgeDetectorFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The 2d gradient operator. + /// + private static readonly float[][] Laplacian3X3XY = new float[][] + { + new float[] { -1, -1, -1 }, + new float[] { -1, 8, -1 }, + new float[] { -1, -1, -1 } + }; + + /// + public override float[][] KernelXY => Laplacian3X3XY; + } +} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs new file mode 100644 index 000000000..fdff0ec3a --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Laplacian 5 x 5 operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class Laplacian5X5Processor : EdgeDetectorFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The 2d gradient operator. + /// + private static readonly float[][] Laplacian5X5XY = + { + new float[] { -1, -1, -1, -1, -1 }, + new float[] { -1, -1, -1, -1, -1 }, + new float[] { -1, -1, 24, -1, -1 }, + new float[] { -1, -1, -1, -1, -1 }, + new float[] { -1, -1, -1, -1, -1 } + }; + + /// + public override float[][] KernelXY => Laplacian5X5XY; + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs new file mode 100644 index 000000000..75e0efbd2 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Laplacian of Gaussian operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class LaplacianOfGaussianProcessor : EdgeDetectorFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The 2d gradient operator. + /// + private static readonly float[][] LaplacianOfGaussianXY = + { + new float[] { 0, 0, -1, 0, 0 }, + new float[] { 0, -1, -2, -1, 0 }, + new float[] { -1, -2, 16, -2, -1 }, + new float[] { 0, -1, -2, -1, 0 }, + new float[] { 0, 0, -1, 0, 0 } + }; + + /// + public override float[][] KernelXY => LaplacianOfGaussianXY; + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/PrewittProcessor.cs new file mode 100644 index 000000000..70b763e63 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/PrewittProcessor.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Prewitt operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class PrewittProcessor : EdgeDetector2DFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The horizontal gradient operator. + /// + private static readonly float[][] PrewittX = + { + new float[] { -1, 0, 1 }, + new float[] { -1, 0, 1 }, + new float[] { -1, 0, 1 } + }; + + /// + /// The vertical gradient operator. + /// + private static readonly float[][] PrewittY = + { + new float[] { 1, 1, 1 }, + new float[] { 0, 0, 0 }, + new float[] { -1, -1, -1 } + }; + + /// + public override float[][] KernelX => PrewittX; + + /// + public override float[][] KernelY => PrewittY; + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs new file mode 100644 index 000000000..8eb3aac3b --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Roberts Cross operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class RobertsCrossProcessor : EdgeDetector2DFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The horizontal gradient operator. + /// + private static readonly float[][] RobertsCrossX = + { + new float[] { 1, 0 }, + new float[] { 0, -1 } + }; + + /// + /// The vertical gradient operator. + /// + private static readonly float[][] RobertsCrossY = + { + new float[] { 0, 1 }, + new float[] { -1, 0 } + }; + + /// + public override float[][] KernelX => RobertsCrossX; + + /// + public override float[][] KernelY => RobertsCrossY; + } +} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs new file mode 100644 index 000000000..189215f08 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Kirsch operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class RobinsonProcessor : EdgeDetectorCompassFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The North gradient operator + /// + private static readonly float[][] RobinsonNorth = + { + new float[] { 1, 2, 1 }, + new float[] { 0, 0, 0 }, + new float[] { -1, -2, -1 } + }; + + /// + /// The NorthWest gradient operator + /// + private static readonly float[][] RobinsonNorthWest = + { + new float[] { 2, 1, 0 }, + new float[] { 1, 0, -1 }, + new float[] { 0, -1, -2 } + }; + + /// + /// The West gradient operator + /// + private static readonly float[][] RobinsonWest = + { + new float[] { 1, 0, -1 }, + new float[] { 2, 0, -2 }, + new float[] { 1, 0, -1 } + }; + + /// + /// The SouthWest gradient operator + /// + private static readonly float[][] RobinsonSouthWest = + { + new float[] { 0, -1, -2 }, + new float[] { 1, 0, -1 }, + new float[] { 2, 1, 0 } + }; + + /// + /// The South gradient operator + /// + private static readonly float[][] RobinsonSouth = + { + new float[] { -1, -2, -1 }, + new float[] { 0, 0, 0 }, + new float[] { 1, 2, 1 } + }; + + /// + /// The SouthEast gradient operator + /// + private static readonly float[][] RobinsonSouthEast = + { + new float[] { -2, -1, 0 }, + new float[] { -1, 0, 1 }, + new float[] { 0, 1, 2 } + }; + + /// + /// The East gradient operator + /// + private static readonly float[][] RobinsonEast = + { + new float[] { -1, 0, 1 }, + new float[] { -2, 0, 2 }, + new float[] { -1, 0, 1 } + }; + + /// + /// The NorthEast gradient operator + /// + private static readonly float[][] RobinsonNorthEast = + { + new float[] { 0, 1, 2 }, + new float[] { -1, 0, 1 }, + new float[] { -2, -1, 0 } + }; + + /// + public override float[][] North => RobinsonNorth; + + /// + public override float[][] NorthWest => RobinsonNorthWest; + + /// + public override float[][] West => RobinsonWest; + + /// + public override float[][] SouthWest => RobinsonSouthWest; + + /// + public override float[][] South => RobinsonSouth; + + /// + public override float[][] SouthEast => RobinsonSouthEast; + + /// + public override float[][] East => RobinsonEast; + + /// + public override float[][] NorthEast => RobinsonNorthEast; + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/ScharrProcessor.cs new file mode 100644 index 000000000..22e7d8084 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/ScharrProcessor.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Scharr operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class ScharrProcessor : EdgeDetector2DFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The horizontal gradient operator. + /// + private static readonly float[][] ScharrX = new float[3][] + { + new float[] { -3, 0, 3 }, + new float[] { -10, 0, 10 }, + new float[] { -3, 0, 3 } + }; + + /// + /// The vertical gradient operator. + /// + private static readonly float[][] ScharrY = new float[3][] + { + new float[] { 3, 10, 3 }, + new float[] { 0, 0, 0 }, + new float[] { -3, -10, -3 } + }; + + /// + public override float[][] KernelX => ScharrX; + + /// + public override float[][] KernelY => ScharrY; + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/SobelProcessor.cs new file mode 100644 index 000000000..7d9521c69 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// The Sobel operator filter. + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] + public class SobelProcessor : EdgeDetector2DFilter + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The horizontal gradient operator. + /// + private static readonly float[][] SobelX = + { + new float[] { -1, 0, 1 }, + new float[] { -2, 0, 2 }, + new float[] { -1, 0, 1 } + }; + + /// + /// The vertical gradient operator. + /// + private static readonly float[][] SobelY = + { + new float[] { -1, -2, -1 }, + new float[] { 0, 0, 0 }, + new float[] { 1, 2, 1 } + }; + + /// + public override float[][] KernelX => SobelX; + + /// + public override float[][] KernelY => SobelY; + } +} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/GuassianBlurProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/GuassianBlurProcessor.cs new file mode 100644 index 000000000..856239385 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/GuassianBlurProcessor.cs @@ -0,0 +1,142 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + + /// + /// Applies a Gaussian blur filter to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class GuassianBlurProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The maximum size of the kernel in either direction. + /// + private readonly int kernelSize; + + /// + /// The spread of the blur. + /// + private readonly float sigma; + + /// + /// Initializes a new instance of the class. + /// + /// The 'sigma' value representing the weight of the blur. + public GuassianBlurProcessor(float sigma = 3f) + { + this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; + this.sigma = sigma; + this.KernelX = this.CreateGaussianKernel(true); + this.KernelY = this.CreateGaussianKernel(false); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public GuassianBlurProcessor(int radius) + { + this.kernelSize = (radius * 2) + 1; + this.sigma = radius; + this.KernelX = this.CreateGaussianKernel(true); + this.KernelY = this.CreateGaussianKernel(false); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the blur. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + public GuassianBlurProcessor(float sigma, int radius) + { + this.kernelSize = (radius * 2) + 1; + this.sigma = sigma; + this.KernelX = this.CreateGaussianKernel(true); + this.KernelY = this.CreateGaussianKernel(false); + } + + /// + /// Gets the horizontal gradient operator. + /// + public float[][] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public float[][] KernelY { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + new Convolution2PassFilter(this.KernelX, this.KernelY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); + } + + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function + /// + /// Whether to calculate a horizontal kernel. + /// The + private float[][] CreateGaussianKernel(bool horizontal) + { + int size = this.kernelSize; + float weight = this.sigma; + float[][] kernel = horizontal ? new float[1][] : new float[size][]; + + if (horizontal) + { + kernel[0] = new float[size]; + } + + float sum = 0.0f; + + float midpoint = (size - 1) / 2f; + for (int i = 0; i < size; i++) + { + float x = i - midpoint; + float gx = ImageMaths.Gaussian(x, weight); + sum += gx; + if (horizontal) + { + kernel[0][i] = gx; + } + else + { + kernel[i] = new[] { gx }; + } + } + + // Normalise kernel so that the sum of all weights equals 1 + if (horizontal) + { + for (int i = 0; i < size; i++) + { + kernel[0][i] = kernel[0][i] / sum; + } + } + else + { + for (int i = 0; i < size; i++) + { + kernel[i][0] = kernel[i][0] / sum; + } + } + + return kernel; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/GuassianSharpenProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/GuassianSharpenProcessor.cs new file mode 100644 index 000000000..3c86fcfee --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Convolution/GuassianSharpenProcessor.cs @@ -0,0 +1,180 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class GuassianSharpenProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The maximum size of the kernel in either direction. + /// + private readonly int kernelSize; + + /// + /// The spread of the blur. + /// + private readonly float sigma; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the sharpening. + /// + public GuassianSharpenProcessor(float sigma = 3f) + { + this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; + this.sigma = sigma; + this.KernelX = this.CreateGaussianKernel(true); + this.KernelY = this.CreateGaussianKernel(false); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public GuassianSharpenProcessor(int radius) + { + this.kernelSize = (radius * 2) + 1; + this.sigma = radius; + this.KernelX = this.CreateGaussianKernel(true); + this.KernelY = this.CreateGaussianKernel(false); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the sharpen. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + public GuassianSharpenProcessor(float sigma, int radius) + { + this.kernelSize = (radius * 2) + 1; + this.sigma = sigma; + this.KernelX = this.CreateGaussianKernel(true); + this.KernelY = this.CreateGaussianKernel(false); + } + + /// + /// Gets the horizontal gradient operator. + /// + public float[][] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public float[][] KernelY { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + new Convolution2PassFilter(this.KernelX, this.KernelY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); + } + + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function + /// + /// Whether to calculate a horizontal kernel. + /// The + private float[][] CreateGaussianKernel(bool horizontal) + { + int size = this.kernelSize; + float weight = this.sigma; + float[][] kernel = horizontal ? new float[1][] : new float[size][]; + + if (horizontal) + { + kernel[0] = new float[size]; + } + + float sum = 0; + + float midpoint = (size - 1) / 2f; + for (int i = 0; i < size; i++) + { + float x = i - midpoint; + float gx = ImageMaths.Gaussian(x, weight); + sum += gx; + if (horizontal) + { + kernel[0][i] = gx; + } + else + { + kernel[i] = new[] { gx }; + } + } + + // Invert the kernel for sharpening. + int midpointRounded = (int)midpoint; + + if (horizontal) + { + for (int i = 0; i < size; i++) + { + if (i == midpointRounded) + { + // Calculate central value + kernel[0][i] = (2f * sum) - kernel[0][i]; + } + else + { + // invert value + kernel[0][i] = -kernel[0][i]; + } + } + } + else + { + for (int i = 0; i < size; i++) + { + if (i == midpointRounded) + { + // Calculate central value + kernel[i][0] = (2 * sum) - kernel[i][0]; + } + else + { + // invert value + kernel[i][0] = -kernel[i][0]; + } + } + } + + // Normalise kernel so that the sum of all weights equals 1 + if (horizontal) + { + for (int i = 0; i < size; i++) + { + kernel[0][i] = kernel[0][i] / sum; + } + } + else + { + for (int i = 0; i < size; i++) + { + kernel[i][0] = kernel[i][0] / sum; + } + } + + return kernel; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/CropProcessor.cs b/src/ImageSharp46/Samplers/Processors/CropProcessor.cs new file mode 100644 index 000000000..0e8510837 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/CropProcessor.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Threading.Tasks; + + /// + /// Provides methods to allow the cropping of an image. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class CropProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int startX = targetRectangle.X; + int endX = targetRectangle.Right; + int sourceX = sourceRectangle.X; + int sourceY = sourceRectangle.Y; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => + { + for (int x = startX; x < endX; x++) + { + targetPixels[x, y] = sourcePixels[x + sourceX, y + sourceY]; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageSharp46/Samplers/Processors/EntropyCropProcessor.cs new file mode 100644 index 000000000..dbb99d1c4 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/EntropyCropProcessor.cs @@ -0,0 +1,107 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Threading.Tasks; + + /// + /// Provides methods to allow the cropping of an image to preserve areas of highest + /// entropy. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class EntropyCropProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The rectangle for cropping + /// + private Rectangle cropRectangle; + + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// + /// is less than 0 or is greater than 1. + /// + public EntropyCropProcessor(float threshold) + { + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + this.Value = threshold; + } + + /// + /// Gets the threshold value. + /// + public float Value { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + // Jump out, we'll deal with that later. + if (source.Bounds == target.Bounds) + { + return; + } + + int targetY = this.cropRectangle.Y; + int targetBottom = this.cropRectangle.Bottom; + int startX = this.cropRectangle.X; + int endX = this.cropRectangle.Right; + + int minY = Math.Max(targetY, startY); + int maxY = Math.Min(targetBottom, endY); + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + for (int x = startX; x < endX; x++) + { + targetPixels[x - startX, y - targetY] = sourcePixels[x, y]; + } + }); + } + } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + ImageBase temp = new Image(source.Width, source.Height); + + // Detect the edges. + new SobelProcessor().Apply(temp, source, sourceRectangle); + + // Apply threshold binarization filter. + new BinaryThresholdProcessor(.5f).Apply(temp, sourceRectangle); + + // Search for the first white pixels + Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); + + // Reset the target pixel to the correct size. + target.SetPixels(rectangle.Width, rectangle.Height, new TColor[rectangle.Width * rectangle.Height]); + this.cropRectangle = rectangle; + } + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + // Copy the pixels over. + if (source.Bounds == target.Bounds) + { + target.ClonePixels(target.Width, target.Height, source.Pixels); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/FlipProcessor.cs b/src/ImageSharp46/Samplers/Processors/FlipProcessor.cs new file mode 100644 index 000000000..d6efccec7 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/FlipProcessor.cs @@ -0,0 +1,115 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the flipping of an image around its center point. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class FlipProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The used to perform flipping. + public FlipProcessor(FlipType flipType) + { + this.FlipType = flipType; + } + + /// + /// Gets the used to perform flipping. + /// + public FlipType FlipType { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + target.ClonePixels(target.Width, target.Height, source.Pixels); + + switch (this.FlipType) + { + // No default needed as we have already set the pixels. + case FlipType.Vertical: + this.FlipX(target); + break; + case FlipType.Horizontal: + this.FlipY(target); + break; + } + } + + /// + /// Swaps the image at the X-axis, which goes horizontally through the middle + /// at half the height of the image. + /// + /// Target image to apply the process to. + private void FlipX(ImageBase target) + { + int width = target.Width; + int height = target.Height; + int halfHeight = (int)Math.Ceiling(target.Height * .5F); + Image temp = new Image(width, height); + temp.ClonePixels(width, height, target.Pixels); + + using (PixelAccessor targetPixels = target.Lock()) + using (PixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + halfHeight, + this.ParallelOptions, + y => + { + for (int x = 0; x < width; x++) + { + int newY = height - y - 1; + targetPixels[x, y] = tempPixels[x, newY]; + targetPixels[x, newY] = tempPixels[x, y]; + } + }); + } + } + + /// + /// Swaps the image at the Y-axis, which goes vertically through the middle + /// at half of the width of the image. + /// + /// Target image to apply the process to. + private void FlipY(ImageBase target) + { + int width = target.Width; + int height = target.Height; + int halfWidth = (int)Math.Ceiling(width * .5F); + Image temp = new Image(width, height); + temp.ClonePixels(width, height, target.Pixels); + + using (PixelAccessor targetPixels = target.Lock()) + using (PixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + for (int x = 0; x < halfWidth; x++) + { + int newX = width - x - 1; + targetPixels[x, y] = tempPixels[newX, y]; + targetPixels[newX, y] = tempPixels[x, y]; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/IImageSampler.cs b/src/ImageSharp46/Samplers/Processors/IImageSampler.cs new file mode 100644 index 000000000..891a810d1 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/IImageSampler.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + /// + /// Encapsulates methods to alter the pixels of an image. The processor creates a copy of the original image to operate on. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public interface IImageSampler : IImageProcessor + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Applies the process to the specified portion of the specified . + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image processing filter as new image. + /// + /// + /// is null or is null. + /// + /// + /// doesnt fit the dimension of the image. + /// + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle); + + /// + /// Applies the process to the specified portion of the specified at the specified + /// location and with the specified size. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// The target width. + /// The target height. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image process as new image. + /// + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle); + } +} diff --git a/src/ImageSharp46/Samplers/Processors/ImageSampler.cs b/src/ImageSharp46/Samplers/Processors/ImageSampler.cs new file mode 100644 index 000000000..98ffe3b88 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/ImageSampler.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + + /// + /// Encapsulates methods to alter the pixels of an image. The processor creates a copy of the original image to operate on. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class ImageSampler : ImageProcessor, IImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + { + try + { + this.OnApply(target, source, target.Bounds, sourceRectangle); + + this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); + + this.AfterApply(target, source, target.Bounds, sourceRectangle); + } + catch (Exception ex) + { + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) + { + try + { + TColor[] pixels = new TColor[width * height]; + target.SetPixels(width, height, pixels); + + // Ensure we always have bounds. + if (sourceRectangle == Rectangle.Empty) + { + sourceRectangle = source.Bounds; + } + + if (targetRectangle == Rectangle.Empty) + { + targetRectangle = target.Bounds; + } + + this.OnApply(target, source, targetRectangle, sourceRectangle); + + this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); + + this.AfterApply(target, source, target.Bounds, sourceRectangle); + } + catch (Exception ex) + { + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The index of the row within the source image to start processing. + /// The index of the row within the source image to end processing. + /// + /// The method keeps the source image unchanged and returns the the result of image process as new image. + /// + public abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY); + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + } + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Matrix3x2Processor.cs b/src/ImageSharp46/Samplers/Processors/Matrix3x2Processor.cs new file mode 100644 index 000000000..a9b460d43 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/Matrix3x2Processor.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System.Numerics; + + /// + /// Provides methods to transform an image using a . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class Matrix3x2Processor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Creates a new target to contain the results of the matrix transform. + /// + /// Target image to apply the process to. + /// The source rectangle. + /// The processing matrix. + protected static void CreateNewTarget(ImageBase target, Rectangle sourceRectangle, Matrix3x2 processMatrix) + { + Matrix3x2 sizeMatrix; + if (Matrix3x2.Invert(processMatrix, out sizeMatrix)) + { + Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix); + target.SetPixels(rectangle.Width, rectangle.Height, new TColor[rectangle.Width * rectangle.Height]); + } + } + + /// + /// Gets a transform matrix adjusted to center upon the target image bounds. + /// + /// Target image to apply the process to. + /// The source image. + /// The transform matrix. + /// + /// The . + /// + protected static Matrix3x2 GetCenteredMatrix(ImageBase target, ImageBase source, Matrix3x2 matrix) + { + Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-target.Width * .5F, -target.Height * .5F); + Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); + return (translationToTargetCenter * matrix) * translateToSourceCenter; + } + } +} diff --git a/src/ImageSharp46/Samplers/Processors/OilPaintingProcessor.cs b/src/ImageSharp46/Samplers/Processors/OilPaintingProcessor.cs new file mode 100644 index 000000000..ee0d4121a --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/OilPaintingProcessor.cs @@ -0,0 +1,151 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to apply an oil painting effect to an . + /// + /// Adapted from by Dewald Esterhuizen. + /// The pixel format. + /// The packed format. uint, long, float. + public class OilPaintingProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image. + /// The number of neighbouring pixels used in calculating each individual pixel value. + public OilPaintingProcessor(int levels, int brushSize) + { + Guard.MustBeGreaterThan(levels, 0, nameof(levels)); + Guard.MustBeGreaterThan(brushSize, 0, nameof(brushSize)); + + this.Levels = levels; + this.BrushSize = brushSize; + } + + /// + /// Gets the intensity levels + /// + public int Levels { get; } + + /// + /// Gets the brush size + /// + public int BrushSize { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int radius = this.BrushSize >> 1; + int levels = this.Levels; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + for (int x = startX; x < endX; x++) + { + int maxIntensity = 0; + int maxIndex = 0; + + int[] intensityBin = new int[levels]; + float[] redBin = new float[levels]; + float[] blueBin = new float[levels]; + float[] greenBin = new float[levels]; + + for (int fy = 0; fy <= radius; fy++) + { + int fyr = fy - radius; + int offsetY = y + fyr; + + // Skip the current row + if (offsetY < minY) + { + continue; + } + + // Outwith the current bounds so break. + if (offsetY >= maxY) + { + break; + } + + for (int fx = 0; fx <= radius; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; + + // Skip the column + if (offsetX < 0) + { + continue; + } + + if (offsetX < maxX) + { + // ReSharper disable once AccessToDisposedClosure + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + + float sourceRed = color.X; + float sourceBlue = color.Z; + float sourceGreen = color.Y; + + int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); + + intensityBin[currentIntensity] += 1; + blueBin[currentIntensity] += sourceBlue; + greenBin[currentIntensity] += sourceGreen; + redBin[currentIntensity] += sourceRed; + + if (intensityBin[currentIntensity] > maxIntensity) + { + maxIntensity = intensityBin[currentIntensity]; + maxIndex = currentIntensity; + } + } + } + + float red = Math.Abs(redBin[maxIndex] / maxIntensity); + float green = Math.Abs(greenBin[maxIndex] / maxIntensity); + float blue = Math.Abs(blueBin[maxIndex] / maxIntensity); + + Vector4 targetColor = targetPixels[x, y].ToVector4(); + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, targetColor.Z)); + targetPixels[x, y] = packed; + } + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/PixelateProcessor.cs b/src/ImageSharp46/Samplers/Processors/PixelateProcessor.cs new file mode 100644 index 000000000..2c1875909 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/PixelateProcessor.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// An to pixelate the colors of an . + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class PixelateProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The size of the pixels. Must be greater than 0. + /// + /// is less than 0 or equal to 0. + /// + public PixelateProcessor(int size) + { + Guard.MustBeGreaterThan(size, 0, nameof(size)); + this.Value = size; + } + + /// + /// Gets or the pixel size. + /// + public int Value { get; } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int size = this.Value; + int offset = this.Value / 2; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + // Get the range on the y-plane to choose from. + IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.ForEach( + range, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + int offsetPy = offset; + + for (int x = minX; x < maxX; x += size) + { + int offsetX = x - startX; + int offsetPx = offset; + + // Make sure that the offset is within the boundary of the image. + while (offsetY + offsetPy >= maxY) + { + offsetPy--; + } + + while (x + offsetPx >= maxX) + { + offsetPx--; + } + + // Get the pixel color in the centre of the soon to be pixelated area. + // ReSharper disable AccessToDisposedClosure + TColor pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; + + // For each pixel in the pixelate size, set it to the centre color. + for (int l = offsetY; l < offsetY + size && l < maxY; l++) + { + for (int k = offsetX; k < offsetX + size && k < maxX; k++) + { + targetPixels[k, l] = pixel; + } + } + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/ResamplingWeightedProcessor.cs b/src/ImageSharp46/Samplers/Processors/ResamplingWeightedProcessor.cs new file mode 100644 index 000000000..1d9d3da35 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/ResamplingWeightedProcessor.cs @@ -0,0 +1,171 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + + /// + /// Provides methods that allow the resizing of images using various algorithms. + /// Adapted from + /// + /// The pixel format. + /// The packed format. uint, long, float. + public abstract class ResamplingWeightedProcessor : ImageSampler + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The sampler to perform the resize operation. + /// + protected ResamplingWeightedProcessor(IResampler sampler) + { + Guard.NotNull(sampler, nameof(sampler)); + + this.Sampler = sampler; + } + + /// + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets or sets the horizontal weights. + /// + protected Weights[] HorizontalWeights { get; set; } + + /// + /// Gets or sets the vertical weights. + /// + protected Weights[] VerticalWeights { get; set; } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (!(this.Sampler is NearestNeighborResampler)) + { + this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); + this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); + } + } + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + // Copy the pixels over. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + target.ClonePixels(target.Width, target.Height, source.Pixels); + } + } + + /// + /// Computes the weights to apply at each pixel when resizing. + /// + /// The destination section size. + /// The source section size. + /// + /// The . + /// + protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) + { + float ratio = (float)sourceSize / destinationSize; + float scale = ratio; + + if (scale < 1F) + { + scale = 1F; + } + + IResampler sampler = this.Sampler; + float radius = (float)Math.Ceiling(scale * sampler.Radius); + Weights[] result = new Weights[destinationSize]; + + for (int i = 0; i < destinationSize; i++) + { + float center = ((i + .5F) * ratio) - .5F; + + // Keep inside bounds. + int left = (int)Math.Ceiling(center - radius); + if (left < 0) + { + left = 0; + } + + int right = (int)Math.Floor(center + radius); + if (right > sourceSize - 1) + { + right = sourceSize - 1; + } + + float sum = 0; + result[i] = new Weights(); + Weight[] weights = new Weight[right - left + 1]; + + for (int j = left; j <= right; j++) + { + float weight = sampler.GetValue((j - center) / scale); + sum += weight; + weights[j - left] = new Weight(j, weight); + } + + // Normalise, best to do it here rather than in the pixel loop later on. + if (sum > 0) + { + for (int w = 0; w < weights.Length; w++) + { + weights[w].Value = weights[w].Value / sum; + } + } + + result[i].Values = weights; + } + + return result; + } + + /// + /// Represents the weight to be added to a scaled pixel. + /// + protected class Weight + { + /// + /// Initializes a new instance of the class. + /// + /// The index. + /// The value. + public Weight(int index, float value) + { + this.Index = index; + this.Value = value; + } + + /// + /// Gets the pixel index. + /// + public int Index { get; } + + /// + /// Gets or sets the result of the interpolation algorithm. + /// + public float Value { get; set; } + } + + /// + /// Represents a collection of weights and their sum. + /// + protected class Weights + { + /// + /// Gets or sets the values. + /// + public Weight[] Values { get; set; } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/ResizeProcessor.cs b/src/ImageSharp46/Samplers/Processors/ResizeProcessor.cs new file mode 100644 index 000000000..82e949148 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/ResizeProcessor.cs @@ -0,0 +1,158 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the resizing of images using various algorithms. + /// + /// + /// This version and the have been separated out to improve performance. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class ResizeProcessor : ResamplingWeightedProcessor + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The sampler to perform the resize operation. + /// + public ResizeProcessor(IResampler sampler) + : base(sampler) + { + } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + // Jump out, we'll deal with that later. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + return; + } + + int width = target.Width; + int height = target.Height; + int sourceHeight = sourceRectangle.Height; + int targetX = target.Bounds.X; + int targetY = target.Bounds.Y; + int targetRight = target.Bounds.Right; + int targetBottom = target.Bounds.Bottom; + int startX = targetRectangle.X; + int endX = targetRectangle.Right; + + int minX = Math.Max(targetX, startX); + int maxX = Math.Min(targetRight, endX); + int minY = Math.Max(targetY, startY); + int maxY = Math.Min(targetBottom, endY); + + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); + + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; + } + }); + } + + // Break out now. + return; + } + + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + Image firstPass = new Image(target.Width, source.Height); + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor firstPassPixels = firstPass.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + minX = Math.Max(0, startX); + maxX = Math.Min(width, endX); + minY = Math.Max(0, startY); + maxY = Math.Min(height, endY); + + Parallel.For( + 0, + sourceHeight, + this.ParallelOptions, + y => + { + for (int x = minX; x < maxX; x++) + { + // Ensure offsets are normalised for cropping and padding. + Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; + + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < horizontalValues.Length; i++) + { + Weight xw = horizontalValues[i]; + destination += sourcePixels[xw.Index, y].ToVector4() * xw.Value; + } + + TColor d = default(TColor); + d.PackFromVector4(destination); + firstPassPixels[x, y] = d; + } + }); + + // Now process the rows. + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Ensure offsets are normalised for cropping and padding. + Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + + for (int x = 0; x < width; x++) + { + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < verticalValues.Length; i++) + { + Weight yw = verticalValues[i]; + destination += firstPassPixels[x, yw.Index].ToVector4() * yw.Value; + } + + TColor d = default(TColor); + d.PackFromVector4(destination); + targetPixels[x, y] = d; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/RotateProcessor.cs b/src/ImageSharp46/Samplers/Processors/RotateProcessor.cs new file mode 100644 index 000000000..0fdc187bd --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/RotateProcessor.cs @@ -0,0 +1,215 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the rotating of images. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class RotateProcessor : Matrix3x2Processor + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The tranform matrix to apply. + /// + private Matrix3x2 processMatrix; + + /// + /// Gets or sets the angle of processMatrix in degrees. + /// + public float Angle { get; set; } + + /// + /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image. + /// + public bool Expand { get; set; } = true; + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + const float Epsilon = .0001F; + + if (Math.Abs(this.Angle) < Epsilon || Math.Abs(this.Angle - 90) < Epsilon || Math.Abs(this.Angle - 180) < Epsilon || Math.Abs(this.Angle - 270) < Epsilon) + { + return; + } + + this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle); + if (this.Expand) + { + CreateNewTarget(target, sourceRectangle, this.processMatrix); + } + } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + if (this.OptimizedApply(target, source)) + { + return; + } + + int height = target.Height; + int width = target.Width; + Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + for (int x = 0; x < width; x++) + { + Point transformedPoint = Point.Rotate(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + { + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + } + } + }); + } + } + + /// + /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. + /// + /// The target image. + /// The source image. + /// + private bool OptimizedApply(ImageBase target, ImageBase source) + { + const float Epsilon = .0001F; + if (Math.Abs(this.Angle) < Epsilon) + { + target.ClonePixels(target.Width, target.Height, source.Pixels); + return true; + } + + if (Math.Abs(this.Angle - 90) < Epsilon) + { + this.Rotate90(target, source); + return true; + } + + if (Math.Abs(this.Angle - 180) < Epsilon) + { + this.Rotate180(target, source); + return true; + } + + if (Math.Abs(this.Angle - 270) < Epsilon) + { + this.Rotate270(target, source); + return true; + } + + return false; + } + + /// + /// Rotates the image 270 degrees clockwise at the centre point. + /// + /// The target image. + /// The source image. + private void Rotate270(ImageBase target, ImageBase source) + { + int width = source.Width; + int height = source.Height; + Image temp = new Image(height, width); + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + newX = height - newX - 1; + int newY = width - x - 1; + tempPixels[newX, newY] = sourcePixels[x, y]; + } + }); + } + + target.SetPixels(height, width, temp.Pixels); + } + + /// + /// Rotates the image 180 degrees clockwise at the centre point. + /// + /// The target image. + /// The source image. + private void Rotate180(ImageBase target, ImageBase source) + { + int width = source.Width; + int height = source.Height; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + for (int x = 0; x < width; x++) + { + int newX = width - x - 1; + int newY = height - y - 1; + targetPixels[newX, newY] = sourcePixels[x, y]; + } + }); + } + } + + /// + /// Rotates the image 90 degrees clockwise at the centre point. + /// + /// The target image. + /// The source image. + private void Rotate90(ImageBase target, ImageBase source) + { + int width = source.Width; + int height = source.Height; + Image temp = new Image(height, width); + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + tempPixels[newX, x] = sourcePixels[x, y]; + } + }); + } + + target.SetPixels(height, width, temp.Pixels); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/SkewProcessor.cs b/src/ImageSharp46/Samplers/Processors/SkewProcessor.cs new file mode 100644 index 000000000..b21301a87 --- /dev/null +++ b/src/ImageSharp46/Samplers/Processors/SkewProcessor.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the skewing of images. + /// + /// The pixel format. + /// The packed format. uint, long, float. + public class SkewProcessor : Matrix3x2Processor + where TColor : struct, IPackedPixel + where TPacked : struct + { + /// + /// The tranform matrix to apply. + /// + private Matrix3x2 processMatrix; + + /// + /// Gets or sets the angle of rotation along the x-axis in degrees. + /// + public float AngleX { get; set; } + + /// + /// Gets or sets the angle of rotation along the y-axis in degrees. + /// + public float AngleY { get; set; } + + /// + /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image. + /// + public bool Expand { get; set; } = true; + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY); + if (this.Expand) + { + CreateNewTarget(target, sourceRectangle, this.processMatrix); + } + } + + /// + public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int height = target.Height; + int width = target.Width; + Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + for (int x = 0; x < width; x++) + { + Point transformedPoint = Point.Skew(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + { + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + } + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Resamplers/BicubicResampler.cs b/src/ImageSharp46/Samplers/Resamplers/BicubicResampler.cs new file mode 100644 index 000000000..a0bfc1413 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/BicubicResampler.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the bicubic kernel algorithm W(x) as described on + /// Wikipedia + /// A commonly used algorithm within imageprocessing that preserves sharpness better than triangle interpolation. + /// + public class BicubicResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + float result = 0; + + // Given the coefficient "a" as -0.5F. + if (x <= 1F) + { + // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; + result = (((1.5F * x) - 2.5F) * x * x) + 1; + } + else if (x < 2F) + { + // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); + result = (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; + } + + return result; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/BoxResampler.cs b/src/ImageSharp46/Samplers/Resamplers/BoxResampler.cs new file mode 100644 index 000000000..adb238a19 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/BoxResampler.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the box algorithm. Similar to nearest neighbour when upscaling. + /// When downscaling the pixels will average, merging together. + /// + public class BoxResampler : IResampler + { + /// + public float Radius => 0.5F; + + /// + public float GetValue(float x) + { + if (x > -0.5F && x <= 0.5F) + { + return 1; + } + + return 0; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/CatmullRomResampler.cs b/src/ImageSharp46/Samplers/Resamplers/CatmullRomResampler.cs new file mode 100644 index 000000000..7751aa08b --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/CatmullRomResampler.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. + /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large + /// scale image enlargements that a 'Lagrange' filter can produce. + /// + /// + public class CatmullRomResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0; + const float C = 0.5F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/HermiteResampler.cs b/src/ImageSharp46/Samplers/Resamplers/HermiteResampler.cs new file mode 100644 index 000000000..9af35e8e8 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/HermiteResampler.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processors +{ + /// + /// The Hermite filter is type of smoothed triangular interpolation Filter, + /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. + /// + /// + public class HermiteResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0F; + const float C = 0F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/IResampler.cs b/src/ImageSharp46/Samplers/Resamplers/IResampler.cs new file mode 100644 index 000000000..ce100a8c7 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/IResampler.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates an interpolation algorithm for resampling images. + /// + public interface IResampler + { + /// + /// Gets the radius in which to sample pixels. + /// + float Radius { get; } + + /// + /// Gets the result of the interpolation algorithm. + /// + /// The value to process. + /// + /// The + /// + float GetValue(float x); + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp46/Samplers/Resamplers/Lanczos2Resampler.cs new file mode 100644 index 000000000..3d25cf859 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/Lanczos2Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 2 pixels. + /// + public class Lanczos2Resampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 2F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F); + } + + return 0F; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp46/Samplers/Resamplers/Lanczos3Resampler.cs new file mode 100644 index 000000000..f771de1a5 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/Lanczos3Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 3 pixels. + /// + public class Lanczos3Resampler : IResampler + { + /// + public float Radius => 3; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 3F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F); + } + + return 0F; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp46/Samplers/Resamplers/Lanczos5Resampler.cs new file mode 100644 index 000000000..4584b258f --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/Lanczos5Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 5 pixels. + /// + public class Lanczos5Resampler : IResampler + { + /// + public float Radius => 5; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 5F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F); + } + + return 0F; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp46/Samplers/Resamplers/Lanczos8Resampler.cs new file mode 100644 index 000000000..03f5a6d7b --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/Lanczos8Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 8 pixels. + /// + public class Lanczos8Resampler : IResampler + { + /// + public float Radius => 8; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 8F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F); + } + + return 0F; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp46/Samplers/Resamplers/MitchellNetravaliResampler.cs new file mode 100644 index 000000000..f4bc3a6f2 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/MitchellNetravaliResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the mitchell algorithm as described on + /// Wikipedia + /// + public class MitchellNetravaliResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.3333333F; + const float C = 0.3333333F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp46/Samplers/Resamplers/NearestNeighborResampler.cs new file mode 100644 index 000000000..ec2417f9c --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/NearestNeighborResampler.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the nearest neighbour algorithm. This uses an unscaled filter + /// which will select the closest pixel to the new pixels position. + /// + public class NearestNeighborResampler : IResampler + { + /// + public float Radius => 1; + + /// + public float GetValue(float x) + { + return x; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageSharp46/Samplers/Resamplers/RobidouxResampler.cs new file mode 100644 index 000000000..6ef90bda4 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/RobidouxResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the Robidoux algorithm. + /// + /// + public class RobidouxResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.37821575509399867F; + const float C = 0.31089212245300067F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp46/Samplers/Resamplers/RobidouxSharpResampler.cs new file mode 100644 index 000000000..8755bf3e4 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/RobidouxSharpResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public class RobidouxSharpResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.2620145123990142F; + const float C = 0.3689927438004929F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/SplineResampler.cs b/src/ImageSharp46/Samplers/Resamplers/SplineResampler.cs new file mode 100644 index 000000000..921ea23a8 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/SplineResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the spline algorithm. + /// + /// + public class SplineResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 1F; + const float C = 0F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/TriangleResampler.cs b/src/ImageSharp46/Samplers/Resamplers/TriangleResampler.cs new file mode 100644 index 000000000..c53d5a146 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/TriangleResampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the triangle (bilinear) algorithm. + /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, + /// so that one can calculate and assign appropriate intensity values to pixels. + /// + public class TriangleResampler : IResampler + { + /// + public float Radius => 1; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 1F) + { + return 1F - x; + } + + return 0F; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resamplers/WelchResampler.cs b/src/ImageSharp46/Samplers/Resamplers/WelchResampler.cs new file mode 100644 index 000000000..eb5506151 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resamplers/WelchResampler.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// The function implements the welch algorithm. + /// + /// + public class WelchResampler : IResampler + { + /// + public float Radius => 3; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 3F) + { + return ImageMaths.SinC(x) * (1F - (x * x / 9.0F)); + } + + return 0F; + } + } +} diff --git a/src/ImageSharp46/Samplers/Resize.cs b/src/ImageSharp46/Samplers/Resize.cs new file mode 100644 index 000000000..ee3da5ec6 --- /dev/null +++ b/src/ImageSharp46/Samplers/Resize.cs @@ -0,0 +1,168 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Resizes an image in accordance with the given . + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to resize. + /// The resize options. + /// The + /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, ResizeOptions options) + where TColor : struct, IPackedPixel + where TPacked : struct + { + // Ensure size is populated across both dimensions. + if (options.Size.Width == 0 && options.Size.Height > 0) + { + options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height); + } + + if (options.Size.Height == 0 && options.Size.Width > 0) + { + options.Size = new Size(options.Size.Width, source.Height * options.Size.Width / source.Width); + } + + Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(source, options); + + return Resize(source, options.Size.Width, options.Size.Height, options.Sampler, source.Bounds, targetRectangle, options.Compand); + } + + /// + /// Resizes an image to the given width and height. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Resize(source, width, height, new BicubicResampler(), false); + } + + /// + /// Resizes an image to the given width and height. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to resize. + /// The target image width. + /// The target image height. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, bool compand) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Resize(source, width, height, new BicubicResampler(), compand); + } + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, IResampler sampler) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Resize(source, width, height, sampler, false); + } + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand); + } + + /// + /// Resizes an image to the given width and height with the given sampler and + /// source rectangle. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false) + where TColor : struct, IPackedPixel + where TPacked : struct + { + if (width == 0 && height > 0) + { + width = source.Width * height / source.Height; + targetRectangle.Width = width; + } + + if (height == 0 && width > 0) + { + height = source.Height * width / source.Width; + targetRectangle.Height = height; + } + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + ResamplingWeightedProcessor processor; + + if (compand) + { + processor = new CompandingResizeProcessor(sampler); + } + else + { + processor = new ResizeProcessor(sampler); + } + + return source.Process(width, height, sourceRectangle, targetRectangle, processor); + } + } +} diff --git a/src/ImageSharp46/Samplers/Rotate.cs b/src/ImageSharp46/Samplers/Rotate.cs new file mode 100644 index 000000000..f8f0a59e8 --- /dev/null +++ b/src/ImageSharp46/Samplers/Rotate.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to rotate. + /// The angle in degrees to perform the rotation. + /// The + public static Image Rotate(this Image source, float degrees) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Rotate(source, degrees, true); + } + + /// + /// Rotates and flips an image by the given instructions. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to rotate. + /// The to perform the rotation. + /// The + public static Image Rotate(this Image source, RotateType rotateType) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Rotate(source, (float)rotateType, false); + } + + /// + /// Rotates an image by the given angle in degrees. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to rotate. + /// The angle in degrees to perform the rotation. + /// Whether to expand the image to fit the rotated result. + /// The + public static Image Rotate(this Image source, float degrees, bool expand) + where TColor : struct, IPackedPixel + where TPacked : struct + { + RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); + } + } +} diff --git a/src/ImageSharp46/Samplers/RotateFlip.cs b/src/ImageSharp46/Samplers/RotateFlip.cs new file mode 100644 index 000000000..5adef987c --- /dev/null +++ b/src/ImageSharp46/Samplers/RotateFlip.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Rotates and flips an image by the given instructions. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to rotate, flip, or both. + /// The to perform the rotation. + /// The to perform the flip. + /// The + public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return source.Rotate(rotateType).Flip(flipType); + } + } +} diff --git a/src/ImageSharp46/Samplers/Skew.cs b/src/ImageSharp46/Samplers/Skew.cs new file mode 100644 index 000000000..4aa756842 --- /dev/null +++ b/src/ImageSharp46/Samplers/Skew.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Skews an image by the given angles in degrees, expanding the image to fit the skewed result. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to skew. + /// The angle in degrees to perform the rotation along the x-axis. + /// The angle in degrees to perform the rotation along the y-axis. + /// The + public static Image Skew(this Image source, float degreesX, float degreesY) + where TColor : struct, IPackedPixel + where TPacked : struct + { + return Skew(source, degreesX, degreesY, true); + } + + /// + /// Skews an image by the given angles in degrees. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to skew. + /// The angle in degrees to perform the rotation along the x-axis. + /// The angle in degrees to perform the rotation along the y-axis. + /// Whether to expand the image to fit the skewed result. + /// The + public static Image Skew(this Image source, float degreesX, float degreesY, bool expand) + where TColor : struct, IPackedPixel + where TPacked : struct + { + SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); + } + } +} diff --git a/src/ImageSharp46/app.config b/src/ImageSharp46/app.config new file mode 100644 index 000000000..5e95024db --- /dev/null +++ b/src/ImageSharp46/app.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ImageSharp46/packages.config b/src/ImageSharp46/packages.config new file mode 100644 index 000000000..9faeca551 --- /dev/null +++ b/src/ImageSharp46/packages.config @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs b/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs new file mode 100644 index 000000000..cd32e7829 --- /dev/null +++ b/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using Xunit; +using Xunit.Abstractions; + +namespace ImageSharp.Tests46.Benchmark +{ + using System.Drawing; + using System.IO; + + using CoreImage = ImageSharp.Image; + using CoreSize = ImageSharp.Size; + + public class DecodeJpeg + { + private static byte[] jpegBytes = File.ReadAllBytes("../../TestImages/Formats/Jpg/Calliphora.jpg"); + + private ITestOutputHelper _output; + + public DecodeJpeg(ITestOutputHelper output) + { + _output = output; + } + + private void DoBenchmark(int times, Action action, [CallerMemberName]string method = null) + { + _output.WriteLine($"Starting {method}.. "); + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < times; i++) + { + using (MemoryStream memoryStream = new MemoryStream(jpegBytes)) + { + action(memoryStream); + } + } + sw.Stop(); + var millis = sw.ElapsedMilliseconds; + + _output.WriteLine($"{method} finished in {millis}ms"); + } + + [Theory] + [InlineData(10)] + [InlineData(100)] + public void JpegSystemDrawing(int times) + { + DoBenchmark(times, memoryStream => + { + Image image = Image.FromStream(memoryStream); + image.Dispose(); + }); + } + + [Theory] + [InlineData(10)] + [InlineData(100)] + public void JpegCore(int times) + { + DoBenchmark(times, memoryStream => + { + CoreImage image = new CoreImage(memoryStream); ; + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Colors/ColorConversionTests.cs b/tests/ImageSharp.Tests46/Colors/ColorConversionTests.cs new file mode 100644 index 000000000..f1b6d5f9b --- /dev/null +++ b/tests/ImageSharp.Tests46/Colors/ColorConversionTests.cs @@ -0,0 +1,511 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Diagnostics.CodeAnalysis; + + using Xunit; + + /// + /// Test conversion between the various color structs. + /// + /// + /// Output values have been compared with + /// and for accuracy. + /// + public class ColorConversionTests + { + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void ColorToYCbCr() + { + // White + Color color = Color.White; + YCbCr yCbCr = color; + + Assert.Equal(255, yCbCr.Y); + Assert.Equal(128, yCbCr.Cb); + Assert.Equal(128, yCbCr.Cr); + + // Black + Color color2 = Color.Black; + YCbCr yCbCr2 = color2; + Assert.Equal(0, yCbCr2.Y); + Assert.Equal(128, yCbCr2.Cb); + Assert.Equal(128, yCbCr2.Cr); + + // Gray + Color color3 = Color.Gray; + YCbCr yCbCr3 = color3; + Assert.Equal(128, yCbCr3.Y); + Assert.Equal(128, yCbCr3.Cb); + Assert.Equal(128, yCbCr3.Cr); + + //Assert.Equal(255, yCbCr.Y, 0); + //Assert.Equal(128, yCbCr.Cb, 0); + //Assert.Equal(128, yCbCr.Cr, 0); + + //// Black + //Color color2 = Color.Black; + //YCbCr yCbCr2 = color2; + //Assert.Equal(0, yCbCr2.Y, 0); + //Assert.Equal(128, yCbCr2.Cb, 0); + //Assert.Equal(128, yCbCr2.Cr, 0); + + //// Gray + //Color color3 = Color.Gray; + //YCbCr yCbCr3 = color3; + //Assert.Equal(128, yCbCr3.Y, 0); + //Assert.Equal(128, yCbCr3.Cb, 0); + //Assert.Equal(128, yCbCr3.Cr, 0); + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void YCbCrToColor() + { + // White + YCbCr yCbCr = new YCbCr(255, 128, 128); + Color color = yCbCr; + + Assert.Equal(255, color.R); + Assert.Equal(255, color.G); + Assert.Equal(255, color.B); + Assert.Equal(255, color.A); + + // Black + YCbCr yCbCr2 = new YCbCr(0, 128, 128); + Color color2 = yCbCr2; + + Assert.Equal(0, color2.R); + Assert.Equal(0, color2.G); + Assert.Equal(0, color2.B); + Assert.Equal(255, color2.A); + + // Gray + YCbCr yCbCr3 = new YCbCr(128, 128, 128); + Color color3 = yCbCr3; + + Assert.Equal(128, color3.R); + Assert.Equal(128, color3.G); + Assert.Equal(128, color3.B); + Assert.Equal(255, color3.A); + } + + /// + /// Tests the implicit conversion from to . + /// Comparison values obtained from + /// http://colormine.org/convert/rgb-to-xyz + /// + [Fact] + public void ColorToCieXyz() + { + // White + Color color = Color.White; + CieXyz ciexyz = color; + + Assert.Equal(95.05f, ciexyz.X, 3); + Assert.Equal(100.0f, ciexyz.Y, 3); + Assert.Equal(108.900f, ciexyz.Z, 3); + + // Black + Color color2 = Color.Black; + CieXyz ciexyz2 = color2; + Assert.Equal(0, ciexyz2.X, 3); + Assert.Equal(0, ciexyz2.Y, 3); + Assert.Equal(0, ciexyz2.Z, 3); + + // Gray + Color color3 = Color.Gray; + CieXyz ciexyz3 = color3; + Assert.Equal(20.518, ciexyz3.X, 3); + Assert.Equal(21.586, ciexyz3.Y, 3); + Assert.Equal(23.507, ciexyz3.Z, 3); + + // Cyan + Color color4 = Color.Cyan; + CieXyz ciexyz4 = color4; + Assert.Equal(53.810f, ciexyz4.X, 3); + Assert.Equal(78.740f, ciexyz4.Y, 3); + Assert.Equal(106.970f, ciexyz4.Z, 3); + } + + /// + /// Tests the implicit conversion from to . + /// Comparison values obtained from + /// http://colormine.org/convert/rgb-to-xyz + /// + [Fact] + public void CieXyzToColor() + { + // Dark moderate pink. + CieXyz ciexyz = new CieXyz(13.337f, 9.297f, 14.727f); + Color color = ciexyz; + + Assert.Equal(128, color.R); + Assert.Equal(64, color.G); + Assert.Equal(106, color.B); + + // Ochre + CieXyz ciexyz2 = new CieXyz(31.787f, 26.147f, 4.885f); + Color color2 = ciexyz2; + + Assert.Equal(204, color2.R); + Assert.Equal(119, color2.G); + Assert.Equal(34, color2.B); + + // Black + CieXyz ciexyz3 = new CieXyz(0, 0, 0); + Color color3 = ciexyz3; + + Assert.Equal(0, color3.R); + Assert.Equal(0, color3.G); + Assert.Equal(0, color3.B); + + //// Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // CieXyz ciexyz4 = color4; + // Assert.Equal(color4, (Color)ciexyz4); + //} + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void ColorToHsv() + { + // Black + Color b = Color.Black; + Hsv h = b; + + Assert.Equal(0, h.H, 1); + Assert.Equal(0, h.S, 1); + Assert.Equal(0, h.V, 1); + + // White + Color color = Color.White; + Hsv hsv = color; + + Assert.Equal(0f, hsv.H, 1); + Assert.Equal(0f, hsv.S, 1); + Assert.Equal(1f, hsv.V, 1); + + // Dark moderate pink. + Color color2 = new Color(128, 64, 106); + Hsv hsv2 = color2; + + Assert.Equal(320.6f, hsv2.H, 1); + Assert.Equal(0.5f, hsv2.S, 1); + Assert.Equal(0.502f, hsv2.V, 2); + + // Ochre. + Color color3 = new Color(204, 119, 34); + Hsv hsv3 = color3; + + Assert.Equal(30f, hsv3.H, 1); + Assert.Equal(0.833f, hsv3.S, 3); + Assert.Equal(0.8f, hsv3.V, 1); + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + public void HsvToColor() + { + // Dark moderate pink. + Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f); + Color color = hsv; + + Assert.Equal(color.R, 128); + Assert.Equal(color.G, 64); + Assert.Equal(color.B, 106); + + // Ochre + Hsv hsv2 = new Hsv(30, 0.833f, 0.8f); + Color color2 = hsv2; + + Assert.Equal(color2.R, 204); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); + + // White + Hsv hsv3 = new Hsv(0, 0, 1); + Color color3 = hsv3; + + Assert.Equal(color3.B, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.R, 255); + + // Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // Hsv hsv4 = color4; + // Assert.Equal(color4, (Color)hsv4); + //} + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void ColorToHsl() + { + // Black + Color b = Color.Black; + Hsl h = b; + + Assert.Equal(0, h.H, 1); + Assert.Equal(0, h.S, 1); + Assert.Equal(0, h.L, 1); + + // White + Color color = Color.White; + Hsl hsl = color; + + Assert.Equal(0f, hsl.H, 1); + Assert.Equal(0f, hsl.S, 1); + Assert.Equal(1f, hsl.L, 1); + + // Dark moderate pink. + Color color2 = new Color(128, 64, 106); + Hsl hsl2 = color2; + + Assert.Equal(320.6f, hsl2.H, 1); + Assert.Equal(0.33f, hsl2.S, 1); + Assert.Equal(0.376f, hsl2.L, 2); + + // Ochre. + Color color3 = new Color(204, 119, 34); + Hsl hsl3 = color3; + + Assert.Equal(30f, hsl3.H, 1); + Assert.Equal(0.714f, hsl3.S, 3); + Assert.Equal(0.467f, hsl3.L, 3); + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + public void HslToColor() + { + // Dark moderate pink. + Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f); + Color color = hsl; + + Assert.Equal(color.R, 128); + Assert.Equal(color.G, 64); + Assert.Equal(color.B, 106); + + // Ochre + Hsl hsl2 = new Hsl(30, 0.714f, 0.467f); + Color color2 = hsl2; + + Assert.Equal(color2.R, 204); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); + + // White + Hsl hsl3 = new Hsl(0, 0, 1); + Color color3 = hsl3; + + Assert.Equal(color3.R, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.B, 255); + + // Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // Hsl hsl4 = color4; + // Assert.Equal(color4, (Color)hsl4); + //} + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void ColorToCmyk() + { + // White + Color color = Color.White; + Cmyk cmyk = color; + + Assert.Equal(0, cmyk.C, 1); + Assert.Equal(0, cmyk.M, 1); + Assert.Equal(0, cmyk.Y, 1); + Assert.Equal(0, cmyk.K, 1); + + // Black + Color color2 = Color.Black; + Cmyk cmyk2 = color2; + Assert.Equal(0, cmyk2.C, 1); + Assert.Equal(0, cmyk2.M, 1); + Assert.Equal(0, cmyk2.Y, 1); + Assert.Equal(1, cmyk2.K, 1); + + // Gray + Color color3 = Color.Gray; + Cmyk cmyk3 = color3; + Assert.Equal(0f, cmyk3.C, 1); + Assert.Equal(0f, cmyk3.M, 1); + Assert.Equal(0f, cmyk3.Y, 1); + Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters. + + // Cyan + Color color4 = Color.Cyan; + Cmyk cmyk4 = color4; + Assert.Equal(1, cmyk4.C, 1); + Assert.Equal(0f, cmyk4.M, 1); + Assert.Equal(0f, cmyk4.Y, 1); + Assert.Equal(0f, cmyk4.K, 1); + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + public void CmykToColor() + { + // Dark moderate pink. + Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f); + Color color = cmyk; + + Assert.Equal(color.R, 128); + Assert.Equal(color.G, 64); + Assert.Equal(color.B, 106); + + // Ochre + Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f); + Color color2 = cmyk2; + + Assert.Equal(color2.R, 204); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); + + // White + Cmyk cmyk3 = new Cmyk(0, 0, 0, 0); + Color color3 = cmyk3; + + Assert.Equal(color3.R, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.B, 255); + + // Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // Cmyk cmyk4 = color4; + // Assert.Equal(color4, (Color)cmyk4); + //} + } + + /// + /// Tests the implicit conversion from to . + /// Comparison values obtained from + /// http://colormine.org/convert/rgb-to-lab + /// + [Fact] + public void ColorToCieLab() + { + // White + Color color = Color.White; + CieLab cielab = color; + + Assert.Equal(100, cielab.L, 3); + Assert.Equal(0.005, cielab.A, 3); + Assert.Equal(-0.010, cielab.B, 3); + + // Black + Color color2 = Color.Black; + CieLab cielab2 = color2; + Assert.Equal(0, cielab2.L, 3); + Assert.Equal(0, cielab2.A, 3); + Assert.Equal(0, cielab2.B, 3); + + // Gray + Color color3 = Color.Gray; + CieLab cielab3 = color3; + Assert.Equal(53.585, cielab3.L, 3); + Assert.Equal(0.003, cielab3.A, 3); + Assert.Equal(-0.006, cielab3.B, 3); + + // Cyan + Color color4 = Color.Cyan; + CieLab cielab4 = color4; + Assert.Equal(91.117, cielab4.L, 3); + Assert.Equal(-48.080, cielab4.A, 3); + Assert.Equal(-14.138, cielab4.B, 3); + } + + /// + /// Tests the implicit conversion from to . + /// + /// Comparison values obtained from + /// http://colormine.org/convert/rgb-to-lab + [Fact] + public void CieLabToColor() + { + // Dark moderate pink. + CieLab cielab = new CieLab(36.5492f, 33.3173f, -12.0615f); + Color color = cielab; + + Assert.Equal(color.R, 128); + Assert.Equal(color.G, 64); + Assert.Equal(color.B, 106); + + // Ochre + CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f); + Color color2 = cielab2; + + Assert.Equal(color2.R, 204); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); + + // Black + CieLab cielab3 = new CieLab(0, 0, 0); + Color color3 = cielab3; + + Assert.Equal(color3.R, 0); + Assert.Equal(color3.G, 0); + Assert.Equal(color3.B, 0); + + // Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // CieLab cielab4 = color4; + // Assert.Equal(color4, (Color)cielab4); + //} + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Colors/ColorTests.cs b/tests/ImageSharp.Tests46/Colors/ColorTests.cs new file mode 100644 index 000000000..23ebbec98 --- /dev/null +++ b/tests/ImageSharp.Tests46/Colors/ColorTests.cs @@ -0,0 +1,135 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System.Numerics; + +namespace ImageSharp.Tests +{ + using System; + using Xunit; + + /// + /// Tests the struct. + /// + public class ColorTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Color color1 = new Color(0, 0, 0); + Color color2 = new Color(0, 0, 0, 1F); + Color color3 = new Color("#000"); + Color color4 = new Color("#000F"); + Color color5 = new Color("#000000"); + Color color6 = new Color("#000000FF"); + + Assert.Equal(color1, color2); + Assert.Equal(color1, color3); + Assert.Equal(color1, color4); + Assert.Equal(color1, color5); + Assert.Equal(color1, color6); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Color color1 = new Color(255, 0, 0, 255); + Color color2 = new Color(0, 0, 0, 255); + Color color3 = new Color("#000"); + Color color4 = new Color("#000000"); + Color color5 = new Color("#FF000000"); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color1, color3); + Assert.NotEqual(color1, color4); + Assert.NotEqual(color1, color5); + } + + /// + /// Tests whether the color constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Color color1 = new Color(1, .1f, .133f, .864f); + Assert.Equal(255, color1.R); + Assert.Equal((byte)Math.Round(.1f * 255), color1.G); + Assert.Equal((byte)Math.Round(.133f * 255), color1.B); + Assert.Equal((byte)Math.Round(.864f * 255), color1.A); + + Color color2 = new Color(1, .1f, .133f); + Assert.Equal(255, color2.R); + Assert.Equal(Math.Round(.1f * 255), color2.G); + Assert.Equal(Math.Round(.133f * 255), color2.B); + Assert.Equal(255, color2.A); + + Color color3 = new Color("#FF0000"); + Assert.Equal(255, color3.R); + Assert.Equal(0, color3.G); + Assert.Equal(0, color3.B); + Assert.Equal(255, color3.A); + + Color color4 = new Color(new Vector3(1, .1f, .133f)); + Assert.Equal(255, color4.R); + Assert.Equal(Math.Round(.1f * 255), color4.G); + Assert.Equal(Math.Round(.133f * 255), color4.B); + Assert.Equal(255, color4.A); + + Color color5 = new Color(new Vector4(1, .1f, .133f, .5f)); + Assert.Equal(255, color5.R); + Assert.Equal(Math.Round(.1f * 255), color5.G); + Assert.Equal(Math.Round(.133f * 255), color5.B); + Assert.Equal(Math.Round(.5f * 255), color5.A); + } + + /// + /// Tests whether FromHex and ToHex work correctly. + /// + [Fact] + public void FromAndToHex() + { + Color color = Color.FromHex("#AABBCCDD"); + Assert.Equal(170, color.R); + Assert.Equal(187, color.G); + Assert.Equal(204, color.B); + Assert.Equal(221, color.A); + + color.A = 170; + color.B = 187; + color.G = 204; + color.R = 221; + + Assert.Equal("DDCCBBAA", color.ToHex()); + + color.R = 0; + + Assert.Equal("00CCBBAA", color.ToHex()); + + color.A = 255; + + Assert.Equal("00CCBBFF", color.ToHex()); + } + + /// + /// Tests that the individual byte elements are layed out in RGBA order. + /// + [Fact] + public unsafe void ByteLayout() + { + Color color = new Color(1, 2, 3, 4); + byte* colorBase = (byte*)&color; + Assert.Equal(1, colorBase[0]); + Assert.Equal(2, colorBase[1]); + Assert.Equal(3, colorBase[2]); + Assert.Equal(4, colorBase[3]); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/FileTestBase.cs b/tests/ImageSharp.Tests46/FileTestBase.cs new file mode 100644 index 000000000..b9a226fc8 --- /dev/null +++ b/tests/ImageSharp.Tests46/FileTestBase.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Collections.Generic; + using System.IO; + + /// + /// The test base class for reading and writing to files. + /// + public abstract class FileTestBase + { + /// + /// The collection of image files to test against. + /// + protected static readonly List Files = new List + { + // new TestFile(TestImages.Png.P1), // Perf: Enable for local testing only + // new TestFile(TestImages.Png.Pd), // Perf: Enable for local testing only + // new TestFile(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only + new TestFile(TestImages.Jpeg.Calliphora), + // new TestFile(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only + new TestFile(TestImages.Jpeg.Turtle), + // new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only + // new TestFile(TestImages.Jpeg.Progress), // Perf: Enable for local testing only + // new TestFile(TestImages.Jpeg.GammaDalaiLamaGray), // Perf: Enable for local testing only + new TestFile(TestImages.Bmp.Car), + // new TestFile(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only + // new TestFile(TestImages.Png.Blur), // Perf: Enable for local testing only + // new TestFile(TestImages.Png.Indexed), // Perf: Enable for local testing only + new TestFile(TestImages.Png.Splash), + new TestFile(TestImages.Gif.Rings), + // new TestFile(TestImages.Gif.Giphy) // Perf: Enable for local testing only + }; + + protected string CreateOutputDirectory(string path) + { + path = "TestOutput/" + path; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + return path; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests46/Formats/Bmp/BitmapTests.cs new file mode 100644 index 000000000..549ac05ef --- /dev/null +++ b/tests/ImageSharp.Tests46/Formats/Bmp/BitmapTests.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Formats; + + using Xunit; + + public class BitmapTests : FileTestBase + { + public static readonly TheoryData BitsPerPixel + = new TheoryData + { + BmpBitsPerPixel.Pixel24 , + BmpBitsPerPixel.Pixel32 + }; + + [Theory] + [MemberData("BitsPerPixel")] + public void BitmapCanEncodeDifferentBitRates(BmpBitsPerPixel bitsPerPixel) + { + string path = CreateOutputDirectory("Bmp"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileNameWithoutExtension(bitsPerPixel); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}.bmp")) + { + image.Save(output, new BmpEncoder { BitsPerPixel = bitsPerPixel }); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests46/Formats/GeneralFormatTests.cs new file mode 100644 index 000000000..7d88ca6e1 --- /dev/null +++ b/tests/ImageSharp.Tests46/Formats/GeneralFormatTests.cs @@ -0,0 +1,158 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + + using Xunit; + + public class GeneralFormatTests : FileTestBase + { + [Fact] + public void ResolutionShouldChange() + { + string path = CreateOutputDirectory("Resolution"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.VerticalResolution = 150; + image.HorizontalResolution = 150; + image.Save(output); + } + } + } + + [Fact] + public void ImageCanEncodeToString() + { + string path = CreateOutputDirectory("ToString"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; + File.WriteAllText(filename, image.ToBase64String()); + } + } + + [Fact] + public void DecodeThenEncodeImageFromStreamShouldSucceed() + { + string path = CreateOutputDirectory("Encode"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Save(output); + } + } + } + + [Fact] + public void QuantizeImageShouldPreserveMaximumColorPrecision() + { + string path = CreateOutputDirectory("Quantize"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + // Copy the original pixels to save decoding time. + Color[] pixels = new Color[image.Width * image.Height]; + Array.Copy(image.Pixels, pixels, image.Pixels.Length); + + using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) + { + image.Quantize(Quantization.Octree) + .Save(output, image.CurrentImageFormat); + + } + + image.SetPixels(image.Width, image.Height, pixels); + using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) + { + image.Quantize(Quantization.Wu) + .Save(output, image.CurrentImageFormat); + } + + image.SetPixels(image.Width, image.Height, pixels); + using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) + { + image.Quantize(Quantization.Palette) + .Save(output, image.CurrentImageFormat); + } + } + } + + [Fact] + public void ImageCanConvertFormat() + { + string path = CreateOutputDirectory("Format"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) + { + image.SaveAsGif(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) + { + image.SaveAsBmp(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) + { + image.SaveAsJpeg(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.SaveAsPng(output); + } + } + } + + [Fact] + public void ImageShouldPreservePixelByteOrderWhenSerialized() + { + string path = CreateOutputDirectory("Serialized"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + byte[] serialized; + using (MemoryStream memoryStream = new MemoryStream()) + { + image.Save(memoryStream); + memoryStream.Flush(); + serialized = memoryStream.ToArray(); + } + + using (MemoryStream memoryStream = new MemoryStream(serialized)) + { + Image image2 = new Image(memoryStream); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image2.Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs new file mode 100644 index 000000000..c442b71c9 --- /dev/null +++ b/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Formats; + + using Xunit; + + public class PngTests : FileTestBase + { + [Fact] + public void ImageCanSaveIndexedPng() + { + string path = CreateOutputDirectory("Png"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.Quality = 256; + image.Save(output, new PngFormat()); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Helpers/GuardTests.cs b/tests/ImageSharp.Tests46/Helpers/GuardTests.cs new file mode 100644 index 000000000..ba6d5687c --- /dev/null +++ b/tests/ImageSharp.Tests46/Helpers/GuardTests.cs @@ -0,0 +1,242 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Helpers +{ + using System; + using System.Diagnostics.CodeAnalysis; + + using Xunit; + + /// + /// Tests the helper. + /// + public class GuardTests + { + /// + /// Tests that the method throws when the argument is null. + /// + [Fact] + public void NotNullThrowsWhenArgIsNull() + { + Assert.Throws(() => Guard.NotNull(null, "foo")); + } + + /// + /// Tests that the method throws when the argument name is empty. + /// + [Fact] + public void NotNullThrowsWhenArgNameEmpty() + { + Assert.Throws(() => Guard.NotNull(null, string.Empty)); + } + + /// + /// Tests that the method throws when the argument is empty. + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1122:UseStringEmptyForEmptyStrings", Justification = "Reviewed. Suppression is OK here.")] + public void NotEmptyThrowsWhenEmpty() + { + Assert.Throws(() => Guard.NotNullOrEmpty("", string.Empty)); + } + + /// + /// Tests that the method throws when the argument is whitespace. + /// + [Fact] + public void NotEmptyThrowsWhenWhitespace() + { + Assert.Throws(() => Guard.NotNullOrEmpty(" ", string.Empty)); + } + + /// + /// Tests that the method throws when the argument name is null. + /// + [Fact] + public void NotEmptyThrowsWhenParameterNameNull() + { + Assert.Throws(() => Guard.NotNullOrEmpty(null, null)); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void LessThanThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeLessThan(1, 0, "foo")); + } + + /// + /// Tests that the method throws when the argument is equal. + /// + [Fact] + public void LessThanThrowsWhenArgIsEqual() + { + Assert.Throws(() => Guard.MustBeLessThan(1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void LessThanOrEqualToThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeLessThanOrEqualTo(1, 0, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is less. + /// + [Fact] + public void LessThanOrEqualToDoesNotThrowWhenArgIsLess() + { + Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(0, 1, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void LessThanOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(1, 1, "foo")); + Assert.Equal(1, 1); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void GreaterThanThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeGreaterThan(0, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void GreaterThanThrowsWhenArgIsEqual() + { + Assert.Throws(() => Guard.MustBeGreaterThan(1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument name is greater. + /// + [Fact] + public void GreaterThanOrEqualToThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeGreaterThanOrEqualTo(0, 1, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is less. + /// + [Fact] + public void GreaterThanOrEqualToDoesNotThrowWhenArgIsGreater() + { + Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 0, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void GreaterThanOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 1, "foo")); + Assert.Equal(1, 1); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is less. + /// + [Fact] + public void BetweenOrEqualToThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(-2, -1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void BetweenOrEqualToThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(2, -1, 1, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void BetweenOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(1, 1, 1, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void BetweenOrEqualToDoesNotThrowWhenArgIsBetween() + { + Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(0, -1, 1, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is false. + /// + [Fact] + public void IsTrueThrowsWhenArgIsFalse() + { + Assert.Throws(() => Guard.IsTrue(false, "foo", "message")); + } + + /// + /// Tests that the method does not throw when the argument is true. + /// + [Fact] + public void IsTrueDoesThrowsWhenArgIsTrue() + { + Exception ex = Record.Exception(() => Guard.IsTrue(true, "foo", "message")); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is true. + /// + [Fact] + public void IsFalseThrowsWhenArgIsFalse() + { + Assert.Throws(() => Guard.IsFalse(true, "foo", "message")); + } + + /// + /// Tests that the method does not throw when the argument is false. + /// + [Fact] + public void IsFalseDoesThrowsWhenArgIsTrue() + { + Exception ex = Record.Exception(() => Guard.IsFalse(false, "foo", "message")); + Assert.Null(ex); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Image/ImagePropertyTests.cs b/tests/ImageSharp.Tests46/Image/ImagePropertyTests.cs new file mode 100644 index 000000000..8b4c6ea10 --- /dev/null +++ b/tests/ImageSharp.Tests46/Image/ImagePropertyTests.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using Xunit; + + /// + /// Tests the class. + /// + public class ImagePropertyTests + { + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreEqual() + { + ImageProperty property1 = new ImageProperty("Foo", "Bar"); + ImageProperty property2 = new ImageProperty("Foo", "Bar"); + ImageProperty property3 = null; + + Assert.Equal(property1, property2); + Assert.True(property1 == property2); + Assert.Equal(property3, null); + } + + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreNotEqual() + { + ImageProperty property1 = new ImageProperty("Foo", "Bar"); + ImageProperty property2 = new ImageProperty("Foo", "Foo"); + ImageProperty property3 = new ImageProperty("Bar", "Bar"); + ImageProperty property4 = new ImageProperty("Foo", null); + + Assert.False(property1.Equals("Foo")); + + Assert.NotEqual(property1, null); + + Assert.NotEqual(property1, property2); + Assert.True(property1 != property2); + + Assert.NotEqual(property1, property3); + Assert.NotEqual(property1, property4); + } + + /// + /// Tests whether the constructor throws an exception when the property name is null or empty. + /// + [Fact] + public void ConstructorThrowsWhenNameIsNullOrEmpty() + { + Assert.Throws(() => new ImageProperty(null, "Foo")); + + Assert.Throws(() => new ImageProperty(string.Empty, "Foo")); + } + + /// + /// Tests whether the constructor correctly assigns properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + ImageProperty property = new ImageProperty("Foo", null); + Assert.Equal("Foo", property.Name); + Assert.Equal(null, property.Value); + + property = new ImageProperty("Foo", string.Empty); + Assert.Equal(string.Empty, property.Value); + } + } +} diff --git a/tests/ImageSharp.Tests46/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests46/Image/PixelAccessorTests.cs new file mode 100644 index 000000000..ab71d8de7 --- /dev/null +++ b/tests/ImageSharp.Tests46/Image/PixelAccessorTests.cs @@ -0,0 +1,168 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the class. + /// + public class PixelAccessorTests + { + [Fact] + public void CopyFromZYX() + { + CopyFromZYX(new Image(1, 1)); + } + + [Fact] + public void CopyFromZYXOptimized() + { + CopyFromZYX(new Image(1, 1)); + } + + [Fact] + public void CopyFromZYXW() + { + CopyFromZYXW(new Image(1, 1)); + } + + [Fact] + public void CopyFromZYXWOptimized() + { + CopyFromZYXW(new Image(1, 1)); + } + + [Fact] + public void CopyToZYX() + { + CopyToZYX(new Image(1, 1)); + } + + [Fact] + public void CopyToZYXOptimized() + { + CopyToZYX(new Image(1, 1)); + } + + [Fact] + public void CopyToZYXW() + { + CopyToZYXW(new Image(1, 1)); + } + + [Fact] + public void CopyToZYXWOptimized() + { + CopyToZYXW(new Image(1, 1)); + } + + private static void CopyFromZYX(Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + using (PixelAccessor pixels = image.Lock()) + { + byte red = 1; + byte green = 2; + byte blue = 3; + byte alpha = 255; + + using (PixelRow row = new PixelRow(1, ComponentOrder.ZYX)) + { + row.Bytes[0] = blue; + row.Bytes[1] = green; + row.Bytes[2] = red; + + pixels.CopyFrom(row, 0); + + Color color = (Color) (object) pixels[0, 0]; + Assert.Equal(red, color.R); + Assert.Equal(green, color.G); + Assert.Equal(blue, color.B); + Assert.Equal(alpha, color.A); + } + } + } + + private static void CopyFromZYXW(Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + using (PixelAccessor pixels = image.Lock()) + { + byte red = 1; + byte green = 2; + byte blue = 3; + byte alpha = 4; + + using (PixelRow row = new PixelRow(1, ComponentOrder.ZYXW)) + { + row.Bytes[0] = blue; + row.Bytes[1] = green; + row.Bytes[2] = red; + row.Bytes[3] = alpha; + + pixels.CopyFrom(row, 0); + + Color color = (Color) (object) pixels[0, 0]; + Assert.Equal(red, color.R); + Assert.Equal(green, color.G); + Assert.Equal(blue, color.B); + Assert.Equal(alpha, color.A); + } + } + } + + private static void CopyToZYX(Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + using (PixelAccessor pixels = image.Lock()) + { + byte red = 1; + byte green = 2; + byte blue = 3; + + using (PixelRow row = new PixelRow(1, ComponentOrder.ZYX)) + { + pixels[0, 0] = (TColor) (object) new Color(red, green, blue); + + pixels.CopyTo(row, 0); + + Assert.Equal(blue, row.Bytes[0]); + Assert.Equal(green, row.Bytes[1]); + Assert.Equal(red, row.Bytes[2]); + } + } + } + + private static void CopyToZYXW(Image image) + where TColor : struct, IPackedPixel + where TPacked : struct + { + using (PixelAccessor pixels = image.Lock()) + { + byte red = 1; + byte green = 2; + byte blue = 3; + byte alpha = 4; + + using (PixelRow row = new PixelRow(1, ComponentOrder.ZYXW)) + { + pixels[0, 0] = (TColor) (object) new Color(red, green, blue, alpha); + + pixels.CopyTo(row, 0); + + Assert.Equal(blue, row.Bytes[0]); + Assert.Equal(green, row.Bytes[1]); + Assert.Equal(red, row.Bytes[2]); + Assert.Equal(alpha, row.Bytes[3]); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj new file mode 100644 index 000000000..2fd552efe --- /dev/null +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -0,0 +1,158 @@ + + + + + Debug + AnyCPU + {635E0A15-3893-4763-A7F6-FCCFF85BCCA4} + Library + Properties + ImageSharp.Tests46 + ImageSharp.Tests46 + v4.6.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll + True + + + ..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll + True + + + ..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {fba0b5f6-09c2-4317-8ef6-6adb9b20e6b1} + ImageSharp46 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/PointTests.cs b/tests/ImageSharp.Tests46/Numerics/PointTests.cs new file mode 100644 index 000000000..82b26b946 --- /dev/null +++ b/tests/ImageSharp.Tests46/Numerics/PointTests.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the struct. + /// + public class PointTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Point first = new Point(100, 100); + Point second = new Point(100, 100); + + Assert.Equal(first, second); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Point first = new Point(0, 100); + Point second = new Point(100, 100); + + Assert.NotEqual(first, second); + } + + /// + /// Tests whether the point constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Point first = new Point(4, 5); + Assert.Equal(4, first.X); + Assert.Equal(5, first.Y); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/RationalTests.cs b/tests/ImageSharp.Tests46/Numerics/RationalTests.cs new file mode 100644 index 000000000..3d80b88fe --- /dev/null +++ b/tests/ImageSharp.Tests46/Numerics/RationalTests.cs @@ -0,0 +1,115 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the struct. + /// + public class RationalTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Rational r1 = new Rational(3, 2); + Rational r2 = new Rational(3, 2); + + Assert.Equal(r1, r2); + Assert.True(r1 == r2); + + Rational r3 = new Rational(7.55); + Rational r4 = new Rational(755, 100); + Rational r5 = new Rational(151, 20); + + Assert.Equal(r3, r4); + Assert.Equal(r4, r5); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Rational first = new Rational(0, 100); + Rational second = new Rational(100, 100); + + Assert.NotEqual(first, second); + Assert.True(first != second); + } + + /// + /// Tests whether the Rational constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Rational rational = new Rational(7, 55); + Assert.Equal(7U, rational.Numerator); + Assert.Equal(55U, rational.Denominator); + + rational = new Rational(755, 100); + Assert.Equal(151U, rational.Numerator); + Assert.Equal(20U, rational.Denominator); + + rational = new Rational(755, 100, false); + Assert.Equal(755U, rational.Numerator); + Assert.Equal(100U, rational.Denominator); + + rational = new Rational(-7.55); + Assert.Equal(151U, rational.Numerator); + Assert.Equal(20U, rational.Denominator); + + rational = new Rational(7); + Assert.Equal(7U, rational.Numerator); + Assert.Equal(1U, rational.Denominator); + } + + [Fact] + public void Fraction() + { + Rational first = new Rational(1.0 / 1600); + Rational second = new Rational(1.0 / 1600, true); + Assert.False(first.Equals(second)); + } + + [Fact] + public void ToDouble() + { + Rational rational = new Rational(0, 0); + Assert.Equal(double.NaN, rational.ToDouble()); + + rational = new Rational(2, 0); + Assert.Equal(double.PositiveInfinity, rational.ToDouble()); + } + + [Fact] + public void ToStringRepresention() + { + Rational rational = new Rational(0, 0); + Assert.Equal("[ Indeterminate ]", rational.ToString()); + + rational = new Rational(double.PositiveInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new Rational(double.NegativeInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new Rational(0, 1); + Assert.Equal("0", rational.ToString()); + + rational = new Rational(2, 1); + Assert.Equal("2", rational.ToString()); + + rational = new Rational(1, 2); + Assert.Equal("1/2", rational.ToString()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/RectangleTests.cs b/tests/ImageSharp.Tests46/Numerics/RectangleTests.cs new file mode 100644 index 000000000..2f9ad3d37 --- /dev/null +++ b/tests/ImageSharp.Tests46/Numerics/RectangleTests.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the struct. + /// + public class RectangleTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Rectangle first = new Rectangle(1, 1, 100, 100); + Rectangle second = new Rectangle(1, 1, 100, 100); + + Assert.Equal(first, second); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Rectangle first = new Rectangle(1, 1, 0, 100); + Rectangle second = new Rectangle(1, 1, 100, 100); + + Assert.NotEqual(first, second); + } + + /// + /// Tests whether the rectangle constructors correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Rectangle first = new Rectangle(1, 1, 50, 100); + Assert.Equal(1, first.X); + Assert.Equal(1, first.Y); + Assert.Equal(50, first.Width); + Assert.Equal(100, first.Height); + Assert.Equal(1, first.Top); + Assert.Equal(51, first.Right); + Assert.Equal(101, first.Bottom); + Assert.Equal(1, first.Left); + + Rectangle second = new Rectangle(new Point(1, 1), new Size(50, 100)); + Assert.Equal(1, second.X); + Assert.Equal(1, second.Y); + Assert.Equal(50, second.Width); + Assert.Equal(100, second.Height); + Assert.Equal(1, second.Top); + Assert.Equal(51, second.Right); + Assert.Equal(101, second.Bottom); + Assert.Equal(1, second.Left); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/SignedRationalTests.cs b/tests/ImageSharp.Tests46/Numerics/SignedRationalTests.cs new file mode 100644 index 000000000..cb7e21db0 --- /dev/null +++ b/tests/ImageSharp.Tests46/Numerics/SignedRationalTests.cs @@ -0,0 +1,122 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the struct. + /// + public class SignedRationalTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + SignedRational r1 = new SignedRational(3, 2); + SignedRational r2 = new SignedRational(3, 2); + + Assert.Equal(r1, r2); + Assert.True(r1 == r2); + + SignedRational r3 = new SignedRational(7.55); + SignedRational r4 = new SignedRational(755, 100); + SignedRational r5 = new SignedRational(151, 20); + + Assert.Equal(r3, r4); + Assert.Equal(r4, r5); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + SignedRational first = new SignedRational(0, 100); + SignedRational second = new SignedRational(100, 100); + + Assert.NotEqual(first, second); + Assert.True(first != second); + } + + /// + /// Tests whether the Rational constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + SignedRational rational = new SignedRational(7, -55); + Assert.Equal(7, rational.Numerator); + Assert.Equal(-55, rational.Denominator); + + rational = new SignedRational(-755, 100); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(20, rational.Denominator); + + rational = new SignedRational(-755, -100, false); + Assert.Equal(-755, rational.Numerator); + Assert.Equal(-100, rational.Denominator); + + rational = new SignedRational(-151, -20); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(-20, rational.Denominator); + + rational = new SignedRational(-7.55); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(20, rational.Denominator); + + rational = new SignedRational(7); + Assert.Equal(7, rational.Numerator); + Assert.Equal(1, rational.Denominator); + } + + [Fact] + public void Fraction() + { + SignedRational first = new SignedRational(1.0 / 1600); + SignedRational second = new SignedRational(1.0 / 1600, true); + Assert.False(first.Equals(second)); + } + + [Fact] + public void ToDouble() + { + SignedRational rational = new SignedRational(0, 0); + Assert.Equal(double.NaN, rational.ToDouble()); + + rational = new SignedRational(2, 0); + Assert.Equal(double.PositiveInfinity, rational.ToDouble()); + + rational = new SignedRational(-2, 0); + Assert.Equal(double.NegativeInfinity, rational.ToDouble()); + } + + [Fact] + public void ToStringRepresention() + { + SignedRational rational = new SignedRational(0, 0); + Assert.Equal("[ Indeterminate ]", rational.ToString()); + + rational = new SignedRational(double.PositiveInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new SignedRational(double.NegativeInfinity); + Assert.Equal("[ NegativeInfinity ]", rational.ToString()); + + rational = new SignedRational(0, 1); + Assert.Equal("0", rational.ToString()); + + rational = new SignedRational(2, 1); + Assert.Equal("2", rational.ToString()); + + rational = new SignedRational(1, 2); + Assert.Equal("1/2", rational.ToString()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/SizeTests.cs b/tests/ImageSharp.Tests46/Numerics/SizeTests.cs new file mode 100644 index 000000000..29eb768d9 --- /dev/null +++ b/tests/ImageSharp.Tests46/Numerics/SizeTests.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the struct. + /// + public class SizeTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Size first = new Size(100, 100); + Size second = new Size(100, 100); + + Assert.Equal(first, second); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Size first = new Size(0, 100); + Size second = new Size(100, 100); + + Assert.NotEqual(first, second); + } + + /// + /// Tests whether the size constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Size first = new Size(4, 5); + Assert.Equal(4, first.Width); + Assert.Equal(5, first.Height); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/AlphaTest.cs new file mode 100644 index 000000000..efbfe75a8 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/AlphaTest.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class AlphaTest : FileTestBase + { + public static readonly TheoryData AlphaValues + = new TheoryData + { + 20 , + 80 + }; + + [Theory] + [MemberData("AlphaValues")] + public void ImageShouldApplyAlphaFilter(int value) + { + string path = CreateOutputDirectory("Alpha"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Alpha(value) + .Save(output); + } + } + } + + [Theory] + [MemberData("AlphaValues")] + public void ImageShouldApplyAlphaFilterInBox(int value) + { + string path = CreateOutputDirectory("Alpha"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Alpha(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BackgroundColorTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BackgroundColorTest.cs new file mode 100644 index 000000000..a7ecf6c08 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/BackgroundColorTest.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class BackgroundColorTest : FileTestBase + { + [Fact] + public void ImageShouldApplyBackgroundColorFilter() + { + string path = CreateOutputDirectory("BackgroundColor"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.BackgroundColor(Color.HotPink) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BinaryThreshold.cs b/tests/ImageSharp.Tests46/Processors/Filters/BinaryThreshold.cs new file mode 100644 index 000000000..10b174cd5 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/BinaryThreshold.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class BinaryThresholdTest : FileTestBase + { + public static readonly TheoryData BinaryThresholdValues + = new TheoryData + { + .25f , + .75f , + }; + + [Theory] + [MemberData("BinaryThresholdValues")] + public void ImageShouldApplyBinaryThresholdFilter(float value) + { + string path = CreateOutputDirectory("BinaryThreshold"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.BinaryThreshold(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BlackWhiteTest.cs new file mode 100644 index 000000000..d4af4ad38 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/BlackWhiteTest.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class BlackWhiteTest : FileTestBase + { + [Fact] + public void ImageShouldApplyBlackWhiteFilter() + { + string path = CreateOutputDirectory("BlackWhite"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.BlackWhite() + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BlendTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BlendTest.cs new file mode 100644 index 000000000..653524e20 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/BlendTest.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class BlendTest : FileTestBase + { + [Fact] + public void ImageShouldApplyBlendFilter() + { + string path = CreateOutputDirectory("Blend"); + + Image blend; + using (FileStream stream = File.OpenRead(TestImages.Bmp.Car)) + { + blend = new Image(stream); + } + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Blend(blend) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs new file mode 100644 index 000000000..4755acb1e --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class BoxBlurTest : FileTestBase + { + public static readonly TheoryData BoxBlurValues + = new TheoryData + { + 3 , + 5 , + }; + + [Theory] + [MemberData("BoxBlurValues")] + public void ImageShouldApplyBoxBlurFilter(int value) + { + string path = CreateOutputDirectory("BoxBlur"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.BoxBlur(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BrightnessTest.cs new file mode 100644 index 000000000..e32c7d35e --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/BrightnessTest.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class BrightnessTest : FileTestBase + { + public static readonly TheoryData BrightnessValues + = new TheoryData + { + 50 , + -50 , + }; + + [Theory] + [MemberData("BrightnessValues")] + public void ImageShouldApplyBrightnessFilter(int value) + { + string path = CreateOutputDirectory("Brightness"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Brightness(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/ColorBlindnessTest.cs new file mode 100644 index 000000000..966514577 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/ColorBlindnessTest.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class ColorBlindnessTest : FileTestBase + { + public static readonly TheoryData ColorBlindnessFilters + = new TheoryData + { + ColorBlindness.Achromatomaly, + ColorBlindness.Achromatopsia, + ColorBlindness.Deuteranomaly, + ColorBlindness.Deuteranopia, + ColorBlindness.Protanomaly, + ColorBlindness.Protanopia, + ColorBlindness.Tritanomaly, + ColorBlindness.Tritanopia + }; + + [Theory] + [MemberData("ColorBlindnessFilters")] + public void ImageShouldApplyColorBlindnessFilter(ColorBlindness colorBlindness) + { + string path = CreateOutputDirectory("ColorBlindness"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(colorBlindness); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.ColorBlindness(colorBlindness) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/ContrastTest.cs new file mode 100644 index 000000000..3c83fd892 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/ContrastTest.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class ContrastTest : FileTestBase + { + public static readonly TheoryData ContrastValues + = new TheoryData + { + 50 , + -50 , + }; + + [Theory] + [MemberData("ContrastValues")] + public void ImageShouldApplyContrastFilter(int value) + { + string path = CreateOutputDirectory("Contrast"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Contrast(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/GlowTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/GlowTest.cs new file mode 100644 index 000000000..1d317795d --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/GlowTest.cs @@ -0,0 +1,85 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class GlowTest : FileTestBase + { + [Fact] + public void ImageShouldApplyGlowFilter() + { + string path = CreateOutputDirectory("Glow"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Glow() + .Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyGlowFilterColor() + { + string path = CreateOutputDirectory("Glow"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("Color"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Glow(Color.HotPink) + .Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyGlowFilterRadius() + { + string path = CreateOutputDirectory("Glow"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("Radius"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Glow(image.Width / 4) + .Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyGlowFilterInBox() + { + string path = CreateOutputDirectory("Glow"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("InBox"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Glow(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/GrayscaleTest.cs new file mode 100644 index 000000000..6796ba617 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/GrayscaleTest.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Processors; + using System.IO; + + using Xunit; + + public class GrayscaleTest : FileTestBase + { + public static readonly TheoryData GrayscaleValues + = new TheoryData + { + GrayscaleMode.Bt709 , + GrayscaleMode.Bt601 , + }; + + [Theory] + [MemberData("GrayscaleValues")] + public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value) + { + string path = CreateOutputDirectory("Grayscale"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Grayscale(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/HueTest.cs new file mode 100644 index 000000000..a56aec9ec --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/HueTest.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class HueTest : FileTestBase + { + public static readonly TheoryData HueValues + = new TheoryData + { + 180 , + -180 , + }; + + [Theory] + [MemberData("HueValues")] + public void ImageShouldApplyHueFilter(int value) + { + string path = CreateOutputDirectory("Hue"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Hue(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/InvertTest.cs new file mode 100644 index 000000000..55bfa852b --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/InvertTest.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class InvertTest : FileTestBase + { + [Fact] + public void ImageShouldApplyInvertFilter() + { + string path = CreateOutputDirectory("Invert"); + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Invert() + .Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyInvertFilterInBox() + { + string path = CreateOutputDirectory("Invert"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("InBox"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Invert(new Rectangle(10, 10, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/KodachromeTest.cs new file mode 100644 index 000000000..adb7cb36d --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/KodachromeTest.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class KodachromeTest : FileTestBase + { + [Fact] + public void ImageShouldApplyKodachromeFilter() + { + string path = CreateOutputDirectory("Kodachrome"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Kodachrome() + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/LomographTest.cs new file mode 100644 index 000000000..79a7aa3ba --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/LomographTest.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class LomographTest : FileTestBase + { + [Fact] + public void ImageShouldApplyLomographFilter() + { + string path = CreateOutputDirectory("Lomograph"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Lomograph() + .Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyLomographFilterInBox() + { + string path = CreateOutputDirectory("Lomograph"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("InBox"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Lomograph(new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/PolaroidTest.cs new file mode 100644 index 000000000..dc9d3a150 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/PolaroidTest.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class PolaroidTest : FileTestBase + { + [Fact] + public void ImageShouldApplyPolaroidFilter() + { + string path = CreateOutputDirectory("Polaroid"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Polaroid() + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/SaturationTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/SaturationTest.cs new file mode 100644 index 000000000..5fe4c3e00 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/SaturationTest.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class SaturationTest : FileTestBase + { + public static readonly TheoryData SaturationValues + = new TheoryData + { + 50 , + -50 , + }; + + [Theory] + [MemberData("SaturationValues")] + public void ImageShouldApplySaturationFilter(int value) + { + string path = CreateOutputDirectory("Saturation"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Saturation(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/SepiaTest.cs new file mode 100644 index 000000000..b5e4d3105 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/SepiaTest.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class SepiaTest : FileTestBase + { + [Fact] + public void ImageShouldApplySepiaFilter() + { + string path = CreateOutputDirectory("Sepia"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Sepia() + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/VignetteTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/VignetteTest.cs new file mode 100644 index 000000000..3fddad1da --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Filters/VignetteTest.cs @@ -0,0 +1,85 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class VignetteTest : FileTestBase + { + [Fact] + public void ImageShouldApplyVignetteFilter() + { + string path = CreateOutputDirectory("Vignette"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Vignette() + .Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyVignetteFilterColor() + { + string path = CreateOutputDirectory("Vignette"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("Color"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Vignette(Color.HotPink) + .Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyVignetteFilterRadius() + { + string path = CreateOutputDirectory("Vignette"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("Radius"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Vignette(image.Width / 4, image.Height / 4) + .Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyVignetteFilterInBox() + { + string path = CreateOutputDirectory("Vignette"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("InBox"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Vignette(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/AutoOrientTests.cs b/tests/ImageSharp.Tests46/Processors/Samplers/AutoOrientTests.cs new file mode 100644 index 000000000..97cd71718 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/AutoOrientTests.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using Xunit; + + public class AutoOrientTests : FileTestBase + { + public static readonly TheoryData OrientationValues + = new TheoryData + { + { RotateType.None, FlipType.None, 0 }, + { RotateType.None, FlipType.None, 1 }, + { RotateType.None, FlipType.Horizontal, 2 }, + { RotateType.Rotate180, FlipType.None, 3 }, + { RotateType.Rotate180, FlipType.Horizontal, 4 }, + { RotateType.Rotate90, FlipType.Horizontal, 5 }, + { RotateType.Rotate270, FlipType.None, 6 }, + { RotateType.Rotate90, FlipType.Vertical, 7 }, + { RotateType.Rotate90, FlipType.None, 8 }, + }; + + [Theory] + [MemberData("OrientationValues")] + public void ImageShouldFlip(RotateType rotateType, FlipType flipType, ushort orientation) + { + string path = CreateOutputDirectory("AutoOrient"); + + string file = TestImages.Bmp.F; + + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + orientation + Path.GetExtension(file); + + Image image = new Image(stream); + image.ExifProfile = new ExifProfile(); + image.ExifProfile.SetValue(ExifTag.Orientation, orientation); + + using (FileStream before = File.OpenWrite($"{path}/before-{filename}")) + { + using (FileStream after = File.OpenWrite($"{path}/after-{filename}")) + { + image.RotateFlip(rotateType, flipType) + .Save(before) + .AutoOrient() + .Save(after); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/CropTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/CropTest.cs new file mode 100644 index 000000000..9e9dd34db --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/CropTest.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class CropTest : FileTestBase + { + [Fact] + public void ImageShouldApplyCropSampler() + { + string path = CreateOutputDirectory("Crop"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Crop(image.Width / 2, image.Height / 2) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/DetectEdgesTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/DetectEdgesTest.cs new file mode 100644 index 000000000..ce088eac4 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/DetectEdgesTest.cs @@ -0,0 +1,67 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class DetectEdgesTest : FileTestBase + { + public static readonly TheoryData DetectEdgesFilters + = new TheoryData + { + EdgeDetection.Kayyali, + EdgeDetection.Kirsch, + EdgeDetection.Lapacian3X3, + EdgeDetection.Lapacian5X5, + EdgeDetection.LaplacianOfGaussian, + EdgeDetection.Prewitt, + EdgeDetection.RobertsCross, + EdgeDetection.Robinson, + EdgeDetection.Scharr, + EdgeDetection.Sobel, + }; + + [Theory] + [MemberData(nameof(DetectEdgesFilters))] + public void ImageShouldApplyDetectEdgesFilter(EdgeDetection detector) + { + string path = CreateOutputDirectory("DetectEdges"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(detector); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.DetectEdges(detector) + .Save(output); + } + } + } + + [Theory] + [MemberData("DetectEdgesFilters")] + public void ImageShouldApplyDetectEdgesFilterInBox(EdgeDetection detector) + { + string path = CreateOutputDirectory("DetectEdges"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(detector + "-InBox"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.DetectEdges(detector, new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/EntropyCropTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/EntropyCropTest.cs new file mode 100644 index 000000000..fdbbc5cde --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/EntropyCropTest.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class EntropyCropTest : FileTestBase + { + public static readonly TheoryData EntropyCropValues + = new TheoryData + { + .25f , + .75f , + }; + + [Theory] + [MemberData("EntropyCropValues")] + public void ImageShouldApplyEntropyCropSampler(float value) + { + string path = CreateOutputDirectory("EntropyCrop"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.EntropyCrop(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/FlipTests.cs b/tests/ImageSharp.Tests46/Processors/Samplers/FlipTests.cs new file mode 100644 index 000000000..c07071e70 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/FlipTests.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class FlipTests : FileTestBase + { + public static readonly TheoryData FlipValues + = new TheoryData + { + { FlipType.None }, + { FlipType.Vertical }, + { FlipType.Horizontal }, + }; + + [Theory] + [MemberData("FlipValues")] + public void ImageShouldFlip(FlipType flipType) + { + string path = CreateOutputDirectory("Flip"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(flipType); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Flip(flipType) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/GuassianBlurTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/GuassianBlurTest.cs new file mode 100644 index 000000000..cd1cc5638 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/GuassianBlurTest.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class GuassianBlurTest : FileTestBase + { + public static readonly TheoryData GuassianBlurValues + = new TheoryData + { + 3 , + 5 , + }; + + [Theory] + [MemberData("GuassianBlurValues")] + public void ImageShouldApplyGuassianBlurFilter(int value) + { + string path = CreateOutputDirectory("GuassianBlur"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.GuassianBlur(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/GuassianSharpenTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/GuassianSharpenTest.cs new file mode 100644 index 000000000..5672b5bae --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/GuassianSharpenTest.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class GuassianSharpenTest : FileTestBase + { + public static readonly TheoryData GuassianSharpenValues + = new TheoryData + { + 3 , + 5 , + }; + + [Theory] + [MemberData("GuassianSharpenValues")] + public void ImageShouldApplyGuassianSharpenFilter(int value) + { + string path = CreateOutputDirectory("GuassianSharpen"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.GuassianSharpen(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/OilPaintTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/OilPaintTest.cs new file mode 100644 index 000000000..8b5ff3f51 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/OilPaintTest.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + + using Xunit; + + public class OilPaintTest : FileTestBase + { + public static readonly TheoryData> OilPaintValues + = new TheoryData> + { + new Tuple(15,10), + new Tuple(6,5) + }; + + [Theory] + [MemberData(nameof(OilPaintValues))] + public void ImageShouldApplyOilPaintFilter(Tuple value) + { + string path = CreateOutputDirectory("OilPaint"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.OilPaint(value.Item1, value.Item2) + .Save(output); + } + } + } + + [Theory] + [MemberData(nameof(OilPaintValues))] + public void ImageShouldApplyOilPaintFilterInBox(Tuple value) + { + string path = CreateOutputDirectory("OilPaint"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value + "-InBox"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.OilPaint(value.Item1, value.Item2, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/PadTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/PadTest.cs new file mode 100644 index 000000000..5a33aac2b --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/PadTest.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class PadTest : FileTestBase + { + [Fact] + public void ImageShouldApplyPadSampler() + { + string path = CreateOutputDirectory("Pad"); + + foreach (TestFile file in Files) + { + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Pad(image.Width + 50, image.Height + 50) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/PixelateTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/PixelateTest.cs new file mode 100644 index 000000000..397bdef06 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/PixelateTest.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class PixelateTest : FileTestBase + { + public static readonly TheoryData PixelateValues + = new TheoryData + { + 4 , + 8 + }; + + [Theory] + [MemberData(nameof(PixelateValues))] + public void ImageShouldApplyPixelateFilter(int value) + { + string path = CreateOutputDirectory("Pixelate"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Pixelate(value) + .Save(output); + } + } + } + + [Theory] + [MemberData(nameof(PixelateValues))] + public void ImageShouldApplyPixelateFilterInBox(int value) + { + string path = CreateOutputDirectory("Pixelate"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value + "-InBox"); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Pixelate(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/ResizeTests.cs b/tests/ImageSharp.Tests46/Processors/Samplers/ResizeTests.cs new file mode 100644 index 000000000..876ba7ebc --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/ResizeTests.cs @@ -0,0 +1,304 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class ResizeTests : FileTestBase + { + public static readonly TheoryData ReSamplers = + new TheoryData + { + { "Bicubic", new BicubicResampler() }, + { "Triangle", new TriangleResampler() }, + { "NearestNeighbor", new NearestNeighborResampler() }, + // Perf: Enable for local testing only + //{ "Box", new BoxResampler() }, + //{ "Lanczos3", new Lanczos3Resampler() }, + //{ "Lanczos5", new Lanczos5Resampler() }, + //{ "Lanczos8", new Lanczos8Resampler() }, + { "MitchellNetravali", new MitchellNetravaliResampler() }, + //{ "Hermite", new HermiteResampler() }, + //{ "Spline", new SplineResampler() }, + //{ "Robidoux", new RobidouxResampler() }, + //{ "RobidouxSharp", new RobidouxSharpResampler() }, + //{ "RobidouxSoft", new RobidouxSoftResampler() }, + //{ "Welch", new WelchResampler() } + }; + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResize(string name, IResampler sampler) + { + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Resize(image.Width / 2, image.Height / 2, sampler, true) + //image.Resize(555, 275, sampler, false) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeWidthAndKeepAspect(string name, IResampler sampler) + { + name = name + "-FixedWidth"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Resize(image.Width / 3, 0, sampler, false) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeHeightAndKeepAspect(string name, IResampler sampler) + { + name = name + "-FixedHeight"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Resize(0, image.Height / 3, sampler, false) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeWithCropWidthMode(string name, IResampler sampler) + { + name = name + "-CropWidth"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Sampler = sampler, + Size = new Size(image.Width / 2, image.Height) + }; + + image.Resize(options) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeWithCropHeightMode(string name, IResampler sampler) + { + name = name + "-CropHeight"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Sampler = sampler, + Size = new Size(image.Width, image.Height / 2) + }; + + image.Resize(options) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeWithPadMode(string name, IResampler sampler) + { + name = name + "-Pad"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Size = new Size(image.Width + 200, image.Height), + Mode = ResizeMode.Pad + }; + + image.Resize(options) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeWithBoxPadMode(string name, IResampler sampler) + { + name = name + "-BoxPad"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Sampler = sampler, + Size = new Size(image.Width + 200, image.Height + 200), + Mode = ResizeMode.BoxPad + }; + + image.Resize(options) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeWithMaxMode(string name, IResampler sampler) + { + name = name + "Max"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Sampler = sampler, + Size = new Size(300, 300), + Mode = ResizeMode.Max + }; + + image.Resize(options) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeWithMinMode(string name, IResampler sampler) + { + name = name + "-Min"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Sampler = sampler, + Size = new Size(image.Width - 50, image.Height - 25), + Mode = ResizeMode.Min + }; + + image.Resize(options) + .Save(output); + } + } + } + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResizeWithStretchMode(string name, IResampler sampler) + { + name = name + "Stretch"; + + string path = CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Sampler = sampler, + Size = new Size(image.Width - 200, image.Height), + Mode = ResizeMode.Stretch + }; + + image.Resize(options) + .Save(output); + } + } + } + + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + [InlineData(2, 0)] + public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) + { + Lanczos3Resampler sampler = new Lanczos3Resampler(); + float result = sampler.GetValue(x); + + Assert.Equal(result, expected); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/RotateFlipTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/RotateFlipTest.cs new file mode 100644 index 000000000..04704b672 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/RotateFlipTest.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class RotateFlipTest : FileTestBase + { + public static readonly TheoryData RotateFlipValues + = new TheoryData + { + { RotateType.None, FlipType.Vertical }, + { RotateType.None, FlipType.Horizontal }, + { RotateType.Rotate90, FlipType.None }, + { RotateType.Rotate180, FlipType.None }, + { RotateType.Rotate270, FlipType.None }, + }; + + [Theory] + [MemberData("RotateFlipValues")] + public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) + { + string path = CreateOutputDirectory("RotateFlip"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(rotateType + "-" + flipType); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.RotateFlip(rotateType, flipType) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/RotateTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/RotateTest.cs new file mode 100644 index 000000000..96fb39e07 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/RotateTest.cs @@ -0,0 +1,68 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class RotateTest : FileTestBase + { + public static readonly TheoryData RotateFloatValues + = new TheoryData + { + 170 , + -170 , + }; + + public static readonly TheoryData RotateEnumValues + = new TheoryData + { + RotateType.None, + RotateType.Rotate90, + RotateType.Rotate180, + RotateType.Rotate270 + }; + + [Theory] + [MemberData("RotateFloatValues")] + public void ImageShouldApplyRotateSampler(float value) + { + string path = CreateOutputDirectory("Rotate"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Rotate(value) + .Save(output); + } + } + } + + [Theory] + [MemberData("RotateEnumValues")] + public void ImageShouldApplyRotateSampler(RotateType value) + { + string path = CreateOutputDirectory("Rotate"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(value); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Rotate(value) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/SkewTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/SkewTest.cs new file mode 100644 index 000000000..11db7e691 --- /dev/null +++ b/tests/ImageSharp.Tests46/Processors/Samplers/SkewTest.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using Xunit; + + public class SkewTest : FileTestBase + { + public static readonly TheoryData SkewValues + = new TheoryData + { + { 20, 10 }, + { -20, -10 } + }; + + [Theory] + [MemberData("SkewValues")] + public void ImageShouldApplySkewSampler(float x, float y) + { + string path = CreateOutputDirectory("Skew"); + + // Matches live example + // http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew + foreach (TestFile file in Files) + { + string filename = file.GetFileName(x + "-" + y); + Image image = file.CreateImage(); + + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Skew(x, y) + .Save(output); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests46/Profiles/Exif/ExifProfileTests.cs new file mode 100644 index 000000000..fa7347a97 --- /dev/null +++ b/tests/ImageSharp.Tests46/Profiles/Exif/ExifProfileTests.cs @@ -0,0 +1,325 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections; + using System.IO; + using System.Linq; + using System.Text; + using Xunit; + + public class ExifProfileTests + { + [Fact] + public void Constructor() + { + using (FileStream stream = File.OpenRead(TestImages.Jpeg.Calliphora)) + { + Image image = new Image(stream); + + Assert.Null(image.ExifProfile); + + image.ExifProfile = new ExifProfile(); + image.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); + + image = WriteAndRead(image); + + Assert.NotNull(image.ExifProfile); + Assert.Equal(1, image.ExifProfile.Values.Count()); + + ExifValue value = image.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); + TestValue(value, "Dirk Lemstra"); + } + } + + [Fact] + public void ConstructorEmpty() + { + new ExifProfile((byte[])null); + new ExifProfile(new byte[] { }); + } + + [Fact] + public void ConstructorCopy() + { + Assert.Throws(() => { new ExifProfile((ExifProfile)null); }); + + ExifProfile profile = GetExifProfile(); + + ExifProfile clone = new ExifProfile(profile); + TestProfile(clone); + + profile.SetValue(ExifTag.ColorSpace, (ushort)2); + + clone = new ExifProfile(profile); + TestProfile(clone); + } + + [Fact] + public void WriteFraction() + { + using (MemoryStream memStream = new MemoryStream()) + { + double exposureTime = 1.0 / 1600; + + ExifProfile profile = GetExifProfile(); + + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); + + Image image = new Image(1, 1); + image.ExifProfile = profile; + + image.SaveAsJpeg(memStream); + + memStream.Position = 0; + image = new Image(memStream); + + profile = image.ExifProfile; + Assert.NotNull(profile); + + ExifValue value = profile.GetValue(ExifTag.ExposureTime); + Assert.NotNull(value); + Assert.NotEqual(exposureTime, ((Rational)value.Value).ToDouble()); + + memStream.Position = 0; + profile = GetExifProfile(); + + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); + image.ExifProfile = profile; + + image.SaveAsJpeg(memStream); + + memStream.Position = 0; + image = new Image(memStream); + + profile = image.ExifProfile; + Assert.NotNull(profile); + + value = profile.GetValue(ExifTag.ExposureTime); + Assert.Equal(exposureTime, ((Rational)value.Value).ToDouble()); + } + } + + [Fact] + public void ReadWriteInfinity() + { + using (FileStream stream = File.OpenRead(TestImages.Jpeg.Floorplan)) + { + Image image = new Image(stream); + image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); + + image = WriteAndRead(image); + ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); + + image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); + + image = WriteAndRead(image); + value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); + + image.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); + + image = WriteAndRead(image); + value = image.ExifProfile.GetValue(ExifTag.FlashEnergy); + Assert.NotNull(value); + Assert.Equal(new Rational(double.PositiveInfinity), value.Value); + } + } + + [Fact] + public void SetValue() + { + Rational[] latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; + + using (FileStream stream = File.OpenRead(TestImages.Jpeg.Floorplan)) + { + Image image = new Image(stream); + image.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); + + ExifValue value = image.ExifProfile.GetValue(ExifTag.Software); + TestValue(value, "ImageSharp"); + + Assert.Throws(() => { value.Value = 15; }); + + image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); + + value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + + TestValue(value, new SignedRational(7555, 100)); + + Assert.Throws(() => { value.Value = 75; }); + + image.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); + + value = image.ExifProfile.GetValue(ExifTag.XResolution); + TestValue(value, new Rational(150, 1)); + + Assert.Throws(() => { value.Value = "ImageSharp"; }); + + image.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); + + value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + TestValue(value, (string)null); + + image.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); + + value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); + TestValue(value, latitude); + + image = WriteAndRead(image); + + Assert.NotNull(image.ExifProfile); + Assert.Equal(17, image.ExifProfile.Values.Count()); + + value = image.ExifProfile.GetValue(ExifTag.Software); + TestValue(value, "ImageSharp"); + + value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + TestValue(value, new SignedRational(75.55)); + + value = image.ExifProfile.GetValue(ExifTag.XResolution); + TestValue(value, new Rational(150.0)); + + value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + Assert.Null(value); + + value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); + TestValue(value, latitude); + + image.ExifProfile.Parts = ExifParts.ExifTags; + + image = WriteAndRead(image); + + Assert.NotNull(image.ExifProfile); + Assert.Equal(8, image.ExifProfile.Values.Count()); + + Assert.NotNull(image.ExifProfile.GetValue(ExifTag.ColorSpace)); + Assert.True(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.False(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.Null(image.ExifProfile.GetValue(ExifTag.ColorSpace)); + + Assert.Equal(7, image.ExifProfile.Values.Count()); + } + } + + [Fact] + public void Values() + { + ExifProfile profile = GetExifProfile(); + + TestProfile(profile); + + var thumbnail = profile.CreateThumbnail(); + Assert.NotNull(thumbnail); + Assert.Equal(256, thumbnail.Width); + Assert.Equal(170, thumbnail.Height); + } + + [Fact] + public void WriteTooLargeProfile() + { + StringBuilder junk = new StringBuilder(); + for (int i = 0; i < 65500; i++) + junk.Append("I"); + + Image image = new Image(100, 100); + image.ExifProfile = new ExifProfile(); + image.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); + + using (MemoryStream memStream = new MemoryStream()) + { + Assert.Throws(() => image.SaveAsJpeg(memStream)); + } + } + + private static ExifProfile GetExifProfile() + { + using (FileStream stream = File.OpenRead(TestImages.Jpeg.Floorplan)) + { + Image image = new Image(stream); + + ExifProfile profile = image.ExifProfile; + Assert.NotNull(profile); + + return profile; + } + } + + private static Image WriteAndRead(Image image) + { + using (MemoryStream memStream = new MemoryStream()) + { + image.SaveAsJpeg(memStream); + + memStream.Position = 0; + return new Image(memStream); + } + } + + private static void TestProfile(ExifProfile profile) + { + Assert.NotNull(profile); + + Assert.Equal(16, profile.Values.Count()); + + foreach (ExifValue value in profile.Values) + { + Assert.NotNull(value.Value); + + if (value.Tag == ExifTag.Software) + Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); + + if (value.Tag == ExifTag.XResolution) + Assert.Equal(new Rational(300.0), value.Value); + + if (value.Tag == ExifTag.PixelXDimension) + Assert.Equal(2338U, value.Value); + } + } + + private static void TestValue(ExifValue value, string expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, Rational expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, SignedRational expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, Rational[] expected) + { + Assert.NotNull(value); + + Assert.Equal(expected, (ICollection)value.Value); + } + + private static void TestValue(ExifValue value, double expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, double[] expected) + { + Assert.NotNull(value); + + Assert.Equal(expected, (ICollection)value.Value); + } + } +} diff --git a/tests/ImageSharp.Tests46/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests46/Profiles/Exif/ExifTagDescriptionAttributeTests.cs new file mode 100644 index 000000000..4f77dc11a --- /dev/null +++ b/tests/ImageSharp.Tests46/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + public class ExifDescriptionAttributeTests + { + [Fact] + public void Test_ExifTag() + { + var exifProfile = new ExifProfile(); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); + ExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("None", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)2); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("Inches", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)3); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("Centimeter", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)4); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("4", value.ToString()); + + exifProfile.SetValue(ExifTag.ImageWidth, 123); + value = exifProfile.GetValue(ExifTag.ImageWidth); + Assert.Equal("123", value.ToString()); + } + } +} diff --git a/tests/ImageSharp.Tests46/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests46/Profiles/Exif/ExifValueTests.cs new file mode 100644 index 000000000..e79cb7186 --- /dev/null +++ b/tests/ImageSharp.Tests46/Profiles/Exif/ExifValueTests.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using System.Linq; + using Xunit; + + public class ExifValueTests + { + private static ExifValue GetExifValue() + { + using (FileStream stream = File.OpenRead(TestImages.Jpeg.Floorplan)) + { + Image image = new Image(stream); + + ExifProfile profile = image.ExifProfile; + Assert.NotNull(profile); + + return profile.Values.First(); + } + } + + [Fact] + public void IEquatable() + { + ExifValue first = GetExifValue(); + ExifValue second = GetExifValue(); + + Assert.True(first == second); + Assert.True(first.Equals(second)); + Assert.True(first.Equals((object)second)); + } + + [Fact] + public void Properties() + { + ExifValue value = GetExifValue(); + + Assert.Equal(ExifDataType.Ascii, value.DataType); + Assert.Equal(ExifTag.GPSDOP, value.Tag); + Assert.Equal(false, value.IsArray); + Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); + Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.Value); + } + } +} diff --git a/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs b/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3d57ceff5 --- /dev/null +++ b/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageSharp.Tests")] +[assembly: AssemblyDescription("A cross-platform library for processing of image files written in C#")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ImageSharp.Tests")] +[assembly: AssemblyCopyright("Copyright © James Jackson-South and contributors.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f836e8e6-b4d9-4208-8346-140c74678b91")] diff --git a/tests/ImageSharp.Tests46/TestFile.cs b/tests/ImageSharp.Tests46/TestFile.cs new file mode 100644 index 000000000..4f9f6fa32 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestFile.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System.IO; +using Xunit.Abstractions; + +namespace ImageSharp.Tests +{ + public class TestFile + { + private readonly Image image; + private readonly string file; + + public TestFile(string file) + { + this.file = file; + + using (FileStream stream = File.OpenRead(file)) + { + this.image = new Image(stream); + } + } + + public string FileName + { + get + { + return Path.GetFileName(this.file); + } + } + + public string FileNameWithoutExtension + { + get + { + return Path.GetFileNameWithoutExtension(this.file); + } + } + + public string GetFileName(object value) + { + return this.FileNameWithoutExtension + "-" + value + Path.GetExtension(this.file); + } + + public string GetFileNameWithoutExtension(object value) + { + return this.FileNameWithoutExtension + "-" + value; + } + + public Image CreateImage() + { + return new Image(this.image); + } + } +} diff --git a/tests/ImageSharp.Tests46/TestImages.cs b/tests/ImageSharp.Tests46/TestImages.cs new file mode 100644 index 000000000..3a7f400ca --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + /// + /// Class that contains all the test images. + /// + public static class TestImages + { + public static class Png + { + private static readonly string folder = "../../TestImages/Formats/Png/"; + + public static string P1 => folder + "pl.png"; + public static string Pd => folder + "pd.png"; + public static string Blur => folder + "blur.png"; + public static string Indexed => folder + "indexed.png"; + public static string Splash => folder + "splash.png"; + } + + public static class Jpeg + { + private static readonly string folder = "../../TestImages/Formats/Jpg/"; + public static string Cmyk => folder + "cmyk.jpg"; + public static string Exif => folder + "exif.jpeg"; + public static string Floorplan => folder + "Floorplan.jpeg"; + public static string Calliphora => folder + "Calliphora.jpg"; + public static string Turtle => folder + "turtle.jpg"; + public static string Fb => folder + "fb.jpg"; + public static string Progress => folder + "progress.jpg"; + public static string GammaDalaiLamaGray => folder + "gamma_dalai_lama_gray.jpg"; + } + + public static class Bmp + { + private static readonly string folder = "../../TestImages/Formats/Bmp/"; + + public static string Car => folder + "Car.bmp"; + + public static string F => folder + "F.bmp"; + + public static string NegHeight => folder + "neg_height.bmp"; + } + + public static class Gif + { + private static readonly string folder = "../../TestImages/Formats/Gif/"; + + public static string Rings => folder + "rings.gif"; + public static string Giphy => folder + "giphy.gif"; + } + } +} diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/Car.bmp b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/Car.bmp new file mode 100644 index 000000000..edaf3a8e4 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/Car.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d3a4a30cd67db6ded1e57126c7ba275404703e64b3dfb1c9c711128c15b0124 +size 810054 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/F.bmp b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/F.bmp new file mode 100644 index 000000000..d95598bef --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/F.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6da008d2b285b2db946e6d4ebf8569b0ddd4a05ef273b38304cb65afccac87b3 +size 65502 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/neg_height.bmp b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/neg_height.bmp new file mode 100644 index 000000000..d0b99a902 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/neg_height.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81437b88a5d92fcb545fae4991643a0c73d95d0277dac0b79074971780008c8c +size 6220854 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Gif/giphy.gif b/tests/ImageSharp.Tests46/TestImages/Formats/Gif/giphy.gif new file mode 100644 index 000000000..029afdec6 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Gif/giphy.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f42abd9f3e493a0acd5303ab0d37a6179835c5a14364a1f001abd9d9e906f96 +size 53655 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Gif/rings.gif b/tests/ImageSharp.Tests46/TestImages/Formats/Gif/rings.gif new file mode 100644 index 000000000..acd5d6339 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Gif/rings.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:716448da88152225767c024aac498f5b7562b6e8391907cefc9d03dba40050fd +size 53435 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Calliphora.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Calliphora.jpg new file mode 100644 index 000000000..aa3fdef01 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Calliphora.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67172fcab405f914587b88cd1106328e6b24ab59d622ba509dcc99509951ff5c +size 254766 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Floorplan.jpeg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Floorplan.jpeg new file mode 100644 index 000000000..6f439d220 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Floorplan.jpeg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de00b34b78dfa0886c93d8dd5cede27b4940d5c620e44631e77e6dc8838befc3 +size 161577 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/cmyk.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/cmyk.jpg new file mode 100644 index 000000000..2fe8f0a61 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/cmyk.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0 +size 611572 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/exif.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/exif.jpg new file mode 100644 index 000000000..cba862660 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/exif.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a9d04b92d0de5836c59ede8ae421235488e4031e893e07b1fe7e4b78f6a9901 +size 32764 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/fb.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/fb.jpg new file mode 100644 index 000000000..7241890e2 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/fb.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c +size 15787 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg new file mode 100644 index 000000000..c305caef4 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d92a88b04518e266b98d9d2f5b4eb88f3f91c332d3397ea859bab8cabc41185 +size 84887 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/progress.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/progress.jpg new file mode 100644 index 000000000..30b214c22 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/progress.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9a1f0da3c5b3a3e7e7f35abe9f5b458163b48ca56226227b3d3cffe06af1971 +size 44884 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/turtle.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/turtle.jpg new file mode 100644 index 000000000..07d96543b --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/turtle.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e5c576b0c5e743cfd498b110305268ecbae63c62061ba6c7eb8e060728191f1 +size 55126 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/blur.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/blur.png new file mode 100644 index 000000000..2ac488b7c --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Png/blur.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10df946d3d6a9832bacd9ac2587b890c348d17731412c8fd17c34f66f35d9c94 +size 183768 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/indexed.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/indexed.png new file mode 100644 index 000000000..f06e1cbd6 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Png/indexed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65566cde707c02757a26fb5fb7702be9c53f55c17a1748d81c384559de2d1173 +size 33529 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/pd.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/pd.png new file mode 100644 index 000000000..12fde3229 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Png/pd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91dd938fb916d368738826741551aea694b340ba3362f56200c5d3c5e5510267 +size 1406 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/pl.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/pl.png new file mode 100644 index 000000000..15e991284 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Png/pl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa550e61fe276d2ebee8666d0cbb811449433edb76e01bcd38cb00c1aafc417e +size 91 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/splash.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/splash.png new file mode 100644 index 000000000..ca4f86bce --- /dev/null +++ b/tests/ImageSharp.Tests46/TestImages/Formats/Png/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4c13422913f1c1910f8dd607236e79b4a5c7053deb8ce1c8be8372eca7465fb +size 245033 diff --git a/tests/ImageSharp.Tests46/packages.config b/tests/ImageSharp.Tests46/packages.config new file mode 100644 index 000000000..d0bd528e6 --- /dev/null +++ b/tests/ImageSharp.Tests46/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file From 06a21b3601d4acfa176657a2b98a5e16632f8067 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 9 Nov 2016 21:36:39 +0100 Subject: [PATCH 02/10] merged recent changes to 46 csproj --- .../Common/Extensions/ByteExtensions.cs | 53 ++- .../Common/Extensions/StreamExtensions.cs | 30 ++ .../Formats/Gif/GifDecoderCore.cs | 6 +- .../Formats/Jpg/JpegEncoderCore.cs | 27 +- .../Formats/Png/Filters/AverageFilter.cs | 7 +- .../Formats/Png/Filters/NoneFilter.cs | 9 +- .../Formats/Png/Filters/PaethFilter.cs | 7 +- .../Formats/Png/Filters/SubFilter.cs | 7 +- .../Formats/Png/Filters/UpFilter.cs | 7 +- .../Formats/Png/PngDecoderCore.cs | 121 ++---- .../Formats/Png/PngEncoderCore.cs | 371 +++++++++--------- .../Formats/Png/Zlib/ZlibDeflateStream.cs | 4 +- src/ImageSharp46/ImageSharp46.csproj | 5 + src/ImageSharp46/Properties/AssemblyInfo.cs | 1 - src/ImageSharp46/packages.config | 1 + .../ImageSharp.Tests46.csproj | 5 +- tests/ImageSharp.Tests46/TestFile.cs | 1 - tests/ImageSharp.Tests46/app.config | 19 + 18 files changed, 367 insertions(+), 314 deletions(-) create mode 100644 src/ImageSharp46/Common/Extensions/StreamExtensions.cs create mode 100644 tests/ImageSharp.Tests46/app.config diff --git a/src/ImageSharp46/Common/Extensions/ByteExtensions.cs b/src/ImageSharp46/Common/Extensions/ByteExtensions.cs index 00eab47fc..89cfe6974 100644 --- a/src/ImageSharp46/Common/Extensions/ByteExtensions.cs +++ b/src/ImageSharp46/Common/Extensions/ByteExtensions.cs @@ -16,32 +16,31 @@ namespace ImageSharp /// Converts a byte array to a new array where each value in the original array is represented /// by a the specified number of bits. /// - /// The bytes to convert from. Cannot be null. + /// The bytes to convert from. Cannot be null. /// The number of bits per value. /// The resulting array. Is never null. - /// is null. + /// is null. /// is less than or equals than zero. - public static byte[] ToArrayByBitsLength(this byte[] bytes, int bits) + public static byte[] ToArrayByBitsLength(this byte[] source, int bits) { - Guard.NotNull(bytes, "bytes"); + Guard.NotNull(source, nameof(source)); Guard.MustBeGreaterThan(bits, 0, "bits"); byte[] result; if (bits < 8) { - result = new byte[bytes.Length * 8 / bits]; - - // BUGFIX I dont think it should be there, but I am not sure if it breaks something else - // int factor = (int)Math.Pow(2, bits) - 1; + result = new byte[source.Length * 8 / bits]; int mask = 0xFF >> (8 - bits); int resultOffset = 0; - foreach (byte b in bytes) + // ReSharper disable once ForCanBeConvertedToForeach + for (int i = 0; i < source.Length; i++) { + byte b = source[i]; for (int shift = 0; shift < 8; shift += bits) { - int colorIndex = (b >> (8 - bits - shift)) & mask; // * (255 / factor); + int colorIndex = (b >> (8 - bits - shift)) & mask; result[resultOffset] = (byte)colorIndex; @@ -51,10 +50,42 @@ namespace ImageSharp } else { - result = bytes; + result = source; } return result; } + + /// + /// Optimized reversal algorithm. + /// + /// The byte array. + public static void ReverseBytes(this byte[] source) + { + ReverseBytes(source, 0, source.Length); + } + + /// + /// Optimized reversal algorithm. + /// + /// The byte array. + /// The index. + /// The length. + /// is null. + public static void ReverseBytes(this byte[] source, int index, int length) + { + Guard.NotNull(source, nameof(source)); + + int i = index; + int j = index + length - 1; + while (i < j) + { + byte temp = source[i]; + source[i] = source[j]; + source[j] = temp; + i++; + j--; + } + } } } diff --git a/src/ImageSharp46/Common/Extensions/StreamExtensions.cs b/src/ImageSharp46/Common/Extensions/StreamExtensions.cs new file mode 100644 index 000000000..87d5a6c32 --- /dev/null +++ b/src/ImageSharp46/Common/Extensions/StreamExtensions.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.IO; + + internal static class StreamExtensions + { + public static void Skip(this Stream stream, int count) + { + if (count < 1) + { + return; + } + + if (stream.CanSeek) + { + stream.Position += count; + } + else + { + byte[] foo = new byte[count]; + stream.Read(foo, 0, count); + } + } + } +} diff --git a/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs index 29b34aa9a..44bb5553b 100644 --- a/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs @@ -59,7 +59,7 @@ namespace ImageSharp.Formats this.currentStream = stream; // Skip the identifier - this.currentStream.Seek(6, SeekOrigin.Current); + this.currentStream.Skip(6); this.ReadLogicalScreenDescriptor(); if (this.logicalScreenDescriptor.GlobalColorTableFlag) @@ -192,13 +192,13 @@ namespace ImageSharp.Formats /// The number of bytes to skip. private void Skip(int length) { - this.currentStream.Seek(length, SeekOrigin.Current); + this.currentStream.Skip(length); int flag; while ((flag = this.currentStream.ReadByte()) != 0) { - this.currentStream.Seek(flag, SeekOrigin.Current); + this.currentStream.Skip(flag); } } diff --git a/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs index 8d9b48a20..9cb4fce26 100644 --- a/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs @@ -787,24 +787,21 @@ namespace ImageSharp.Formats { HuffmanSpec spec = specs[i]; int len = 0; + fixed (byte* huffman = this.huffmanBuffer) + fixed (byte* count = spec.Count) + fixed (byte* values = spec.Values) { - fixed (byte* count = spec.Count) + huffman[len++] = headers[i]; + + for (int c = 0; c < spec.Count.Length; c++) + { + huffman[len++] = count[c]; + } + + for (int v = 0; v < spec.Values.Length; v++) { - fixed (byte* values = spec.Values) - { - huffman[len++] = headers[i]; - - for (int c = 0; c < spec.Count.Length; c++) - { - huffman[len++] = count[c]; - } - - for (int v = 0; v < spec.Values.Length; v++) - { - huffman[len++] = values[v]; - } - } + huffman[len++] = values[v]; } } diff --git a/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs index 97ded289c..bef124541 100644 --- a/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs @@ -45,15 +45,16 @@ namespace ImageSharp.Formats /// The scanline to encode /// The previous scanline. /// The bytes per pixel. + /// The number of bytes per scanline /// The - public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel) + public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline) { // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) - byte[] encodedScanline = new byte[scanline.Length + 1]; + byte[] encodedScanline = new byte[bytesPerScanline + 1]; encodedScanline[0] = (byte)FilterType.Average; - for (int x = 0; x < scanline.Length; x++) + for (int x = 0; x < bytesPerScanline; x++) { byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; byte above = previousScanline[x]; diff --git a/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs index 2ac590648..175e5affa 100644 --- a/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs +++ b/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System; + /// /// The None filter, the scanline is transmitted unmodified; it is only necessary to /// insert a filter type byte before the data. @@ -27,13 +29,14 @@ namespace ImageSharp.Formats /// Encodes the scanline /// /// The scanline to encode + /// The number of bytes per scanline /// The - public static byte[] Encode(byte[] scanline) + public static byte[] Encode(byte[] scanline, int bytesPerScanline) { // Insert a byte before the data. - byte[] encodedScanline = new byte[scanline.Length + 1]; + byte[] encodedScanline = new byte[bytesPerScanline + 1]; encodedScanline[0] = (byte)FilterType.None; - scanline.CopyTo(encodedScanline, 1); + Buffer.BlockCopy(scanline, 0, encodedScanline, 1, bytesPerScanline); return encodedScanline; } diff --git a/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs index 1234cb6ef..232d7cc3d 100644 --- a/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs @@ -45,14 +45,15 @@ namespace ImageSharp.Formats /// The scanline to encode /// The previous scanline. /// The bytes per pixel. + /// The number of bytes per scanline /// The - public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel) + public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline) { // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) - byte[] encodedScanline = new byte[scanline.Length + 1]; + byte[] encodedScanline = new byte[bytesPerScanline + 1]; encodedScanline[0] = (byte)FilterType.Paeth; - for (int x = 0; x < scanline.Length; x++) + for (int x = 0; x < bytesPerScanline; x++) { byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; byte above = previousScanline[x]; diff --git a/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs index eb5bd9bfd..c4fbe3e51 100644 --- a/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs @@ -38,14 +38,15 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The bytes per pixel. + /// The number of bytes per scanline /// The - public static byte[] Encode(byte[] scanline, int bytesPerPixel) + public static byte[] Encode(byte[] scanline, int bytesPerPixel, int bytesPerScanline) { // Sub(x) = Raw(x) - Raw(x-bpp) - byte[] encodedScanline = new byte[scanline.Length + 1]; + byte[] encodedScanline = new byte[bytesPerScanline + 1]; encodedScanline[0] = (byte)FilterType.Sub; - for (int x = 0; x < scanline.Length; x++) + for (int x = 0; x < bytesPerScanline; x++) { byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; diff --git a/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs index ebb5e3b54..026070421 100644 --- a/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs @@ -37,15 +37,16 @@ namespace ImageSharp.Formats /// Encodes the scanline /// /// The scanline to encode + /// The number of bytes per scanline /// The previous scanline. /// The - public static byte[] Encode(byte[] scanline, byte[] previousScanline) + public static byte[] Encode(byte[] scanline, int bytesPerScanline, byte[] previousScanline) { // Up(x) = Raw(x) - Prior(x) - byte[] encodedScanline = new byte[scanline.Length + 1]; + byte[] encodedScanline = new byte[bytesPerScanline + 1]; encodedScanline[0] = (byte)FilterType.Up; - for (int x = 0; x < scanline.Length; x++) + for (int x = 0; x < bytesPerScanline; x++) { byte above = previousScanline[x]; diff --git a/src/ImageSharp46/Formats/Png/PngDecoderCore.cs b/src/ImageSharp46/Formats/Png/PngDecoderCore.cs index d57ba8d4e..cad32d9f2 100644 --- a/src/ImageSharp46/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp46/Formats/Png/PngDecoderCore.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Formats { using System; @@ -115,7 +116,7 @@ namespace ImageSharp.Formats { Image currentImage = image; this.currentStream = stream; - this.currentStream.Seek(8, SeekOrigin.Current); + this.currentStream.Skip(8); bool isEndChunkReached = false; @@ -129,35 +130,31 @@ namespace ImageSharp.Formats throw new ImageFormatException("Image does not end with end chunk."); } - if (currentChunk.Type == PngChunkTypes.Header) - { - this.ReadHeaderChunk(currentChunk.Data); - this.ValidateHeader(); - } - else if (currentChunk.Type == PngChunkTypes.Physical) - { - this.ReadPhysicalChunk(currentImage, currentChunk.Data); - } - else if (currentChunk.Type == PngChunkTypes.Data) - { - dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length); - } - else if (currentChunk.Type == PngChunkTypes.Palette) + switch (currentChunk.Type) { - this.palette = currentChunk.Data; - image.Quality = this.palette.Length / 3; - } - else if (currentChunk.Type == PngChunkTypes.PaletteAlpha) - { - this.paletteAlpha = currentChunk.Data; - } - else if (currentChunk.Type == PngChunkTypes.Text) - { - this.ReadTextChunk(currentImage, currentChunk.Data); - } - else if (currentChunk.Type == PngChunkTypes.End) - { - isEndChunkReached = true; + case PngChunkTypes.Header: + this.ReadHeaderChunk(currentChunk.Data); + this.ValidateHeader(); + break; + case PngChunkTypes.Physical: + this.ReadPhysicalChunk(currentImage, currentChunk.Data); + break; + case PngChunkTypes.Data: + dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length); + break; + case PngChunkTypes.Palette: + this.palette = currentChunk.Data; + image.Quality = this.palette.Length / 3; + break; + case PngChunkTypes.PaletteAlpha: + this.paletteAlpha = currentChunk.Data; + break; + case PngChunkTypes.Text: + this.ReadTextChunk(currentImage, currentChunk.Data); + break; + case PngChunkTypes.End: + isEndChunkReached = true; + break; } } @@ -188,8 +185,8 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel where TPacked : struct { - this.ReverseBytes(data, 0, 4); - this.ReverseBytes(data, 4, 4); + data.ReverseBytes(0, 4); + data.ReverseBytes(4, 4); // 39.3700787 = inches in a meter. image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; @@ -216,8 +213,7 @@ namespace ImageSharp.Formats case PngColorType.Rgb: return 3; - // PngColorType.RgbWithAlpha - // TODO: Maybe figure out a way to detect if there are any transparent pixels and encode RGB if none. + // PngColorType.RgbWithAlpha: default: return 4; } @@ -262,18 +258,7 @@ namespace ImageSharp.Formats dataStream.Position = 0; using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) { - using (MemoryStream decompressedStream = new MemoryStream()) - { - compressedStream.CopyTo(decompressedStream); - decompressedStream.Flush(); - decompressedStream.Position = 0; - this.DecodePixelData(decompressedStream, pixels); - //byte[] decompressedBytes = decompressedStream.ToArray(); - //this.DecodePixelData(decompressedBytes, pixels); - } - - //byte[] decompressedBytes = compressedStream.ToArray(); - //this.DecodePixelData(decompressedBytes, pixels); + this.DecodePixelData(compressedStream, pixels); } } @@ -282,9 +267,9 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The packed format. uint, long, float. - /// The pixel data. - /// The image pixels. - private void DecodePixelData(Stream pixelData, PixelAccessor pixels) + /// The compressed pixel data stream. + /// The image pixel accessor. + private void DecodePixelData(Stream compressedStream, PixelAccessor pixels) where TColor : struct, IPackedPixel where TPacked : struct { @@ -293,11 +278,12 @@ namespace ImageSharp.Formats byte[] scanline = new byte[this.bytesPerScanline]; for (int y = 0; y < this.header.Height; y++) { - pixelData.Read(scanline, 0, this.bytesPerScanline); + compressedStream.Read(scanline, 0, this.bytesPerScanline); FilterType filterType = (FilterType)scanline[0]; byte[] defilteredScanline; + // TODO: It would be good if we can reduce the memory usage here. Each filter is creating a new row. switch (filterType) { case FilterType.None: @@ -497,8 +483,8 @@ namespace ImageSharp.Formats { this.header = new PngHeader(); - this.ReverseBytes(data, 0, 4); - this.ReverseBytes(data, 4, 4); + data.ReverseBytes(0, 4); + data.ReverseBytes(4, 4); this.header.Width = BitConverter.ToInt32(data, 0); this.header.Height = BitConverter.ToInt32(data, 4); @@ -584,7 +570,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Image stream is not valid!"); } - this.ReverseBytes(this.crcBuffer); + this.crcBuffer.ReverseBytes(); chunk.Crc = BitConverter.ToUInt32(this.crcBuffer, 0); @@ -649,40 +635,11 @@ namespace ImageSharp.Formats throw new ImageFormatException("Image stream is not valid!"); } - this.ReverseBytes(this.chunkLengthBuffer); + this.chunkLengthBuffer.ReverseBytes(); chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0); return numBytes; } - - /// - /// Optimized reversal algorithm. - /// - /// The byte array. - private void ReverseBytes(byte[] source) - { - this.ReverseBytes(source, 0, source.Length); - } - - /// - /// Optimized reversal algorithm. - /// - /// The byte array. - /// The index. - /// The length. - private void ReverseBytes(byte[] source, int index, int length) - { - int i = index; - int j = index + length - 1; - while (i < j) - { - byte temp = source[i]; - source[i] = source[j]; - source[j] = temp; - i++; - j--; - } - } } } diff --git a/src/ImageSharp46/Formats/Png/PngEncoderCore.cs b/src/ImageSharp46/Formats/Png/PngEncoderCore.cs index 03f74d5a7..a06e306f5 100644 --- a/src/ImageSharp46/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp46/Formats/Png/PngEncoderCore.cs @@ -2,13 +2,14 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Formats { using System; + using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; - using System.Threading.Tasks; using Quantizers; @@ -24,9 +25,20 @@ namespace ImageSharp.Formats private const int MaxBlockSize = 65535; /// - /// Contains the raw pixel data from the image. + /// Reusable buffer for writing chunk types. + /// + private readonly byte[] chunkTypeBuffer = new byte[4]; + + /// + /// Reusable buffer for writing chunk data. + /// + private readonly byte[] chunkDataBuffer = new byte[16]; + + + /// + /// Contains the raw pixel data from an indexed image. /// - private byte[] pixelData; + private byte[] palettePixelData; /// /// The image width. @@ -106,20 +118,16 @@ namespace ImageSharp.Formats this.height = image.Height; // Write the png header. - stream.Write( - new byte[] - { - 0x89, // Set the high bit. - 0x50, // P - 0x4E, // N - 0x47, // G - 0x0D, // Line ending CRLF - 0x0A, // Line ending CRLF - 0x1A, // EOF - 0x0A // LF - }, - 0, - 8); + this.chunkDataBuffer[0] = 0x89; // Set the high bit. + this.chunkDataBuffer[1] = 0x50; // P + this.chunkDataBuffer[2] = 0x4E; // N + this.chunkDataBuffer[3] = 0x47; // G + this.chunkDataBuffer[4] = 0x0D; // Line ending CRLF + this.chunkDataBuffer[5] = 0x0A; // Line ending CRLF + this.chunkDataBuffer[6] = 0x1A; // EOF + this.chunkDataBuffer[7] = 0x0A; // LF + + stream.Write(this.chunkDataBuffer, 0, 8); // Ensure that quality can be set but has a fallback. int quality = this.Quality > 0 ? this.Quality : image.Quality; @@ -131,6 +139,11 @@ namespace ImageSharp.Formats this.PngColorType = PngColorType.Palette; } + if (this.PngColorType == PngColorType.Palette && this.Quality > 256) + { + this.Quality = 256; + } + // Set correct bit depth. this.bitDepth = this.Quality <= 256 ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) @@ -161,23 +174,15 @@ namespace ImageSharp.Formats this.WriteHeaderChunk(stream, header); - // Collect the pixel data + // Collect the indexed pixel data if (this.PngColorType == PngColorType.Palette) { this.CollectIndexedBytes(image, stream, header); } - else if (this.PngColorType == PngColorType.Grayscale || this.PngColorType == PngColorType.GrayscaleWithAlpha) - { - this.CollectGrayscaleBytes(image); - } - else - { - this.CollectColorBytes(image); - } this.WritePhysicalChunk(stream, image); this.WriteGammaChunk(stream); - this.WriteDataChunks(stream); + this.WriteDataChunks(image, stream); this.WriteEndChunk(stream); stream.Flush(); } @@ -192,8 +197,8 @@ namespace ImageSharp.Formats { byte[] buffer = BitConverter.GetBytes(value); - Array.Reverse(buffer); - Array.Copy(buffer, 0, data, offset, 4); + buffer.ReverseBytes(); + Buffer.BlockCopy(buffer, 0, data, offset, 4); } /// @@ -205,8 +210,7 @@ namespace ImageSharp.Formats { byte[] buffer = BitConverter.GetBytes(value); - Array.Reverse(buffer); - + buffer.ReverseBytes(); stream.Write(buffer, 0, 4); } @@ -219,8 +223,7 @@ namespace ImageSharp.Formats { byte[] buffer = BitConverter.GetBytes(value); - Array.Reverse(buffer); - + buffer.ReverseBytes(); stream.Write(buffer, 0, 4); } @@ -236,46 +239,44 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel where TPacked : struct { - // Quatize the image and get the pixels + // Quantize the image and get the pixels. QuantizedImage quantized = this.WritePaletteChunk(stream, header, image); - this.pixelData = quantized.Pixels; + this.palettePixelData = quantized.Pixels; } /// - /// Collects the grayscale pixel data. + /// Collects a row of grayscale pixels. /// /// The pixel format. /// The packed format. uint, long, float. /// The image to encode. - private void CollectGrayscaleBytes(ImageBase image) + /// The row index. + /// The raw scanline. + private void CollectGrayscaleBytes(ImageBase image, int row, byte[] rawScanline) where TColor : struct, IPackedPixel where TPacked : struct { // Copy the pixels across from the image. - this.pixelData = new byte[this.width * this.height * this.bytesPerPixel]; - int stride = this.width * this.bytesPerPixel; - byte[] bytes = new byte[4]; + // Reuse the chunk type buffer. using (PixelAccessor pixels = image.Lock()) { - for (int y = 0; y < this.height; y++) + for (int x = 0; x < this.width; x++) { - for (int x = 0; x < this.width; x++) + // Convert the color to YCbCr and store the luminance + // Optionally store the original color alpha. + int offset = x * this.bytesPerPixel; + pixels[x, row].ToBytes(this.chunkTypeBuffer, 0, ComponentOrder.XYZW); + byte luminance = (byte)((0.299F * this.chunkTypeBuffer[0]) + (0.587F * this.chunkTypeBuffer[1]) + (0.114F * this.chunkTypeBuffer[2])); + + for (int i = 0; i < this.bytesPerPixel; i++) { - // Convert the color to YCbCr and store the luminance - // Optionally store the original color alpha. - int dataOffset = (y * stride) + (x * this.bytesPerPixel); - pixels[x, y].ToBytes(bytes, 0, ComponentOrder.XYZW); - YCbCr luminance = new Color(bytes[0], bytes[1], bytes[2], bytes[3]); - for (int i = 0; i < this.bytesPerPixel; i++) + if (i == 0) + { + rawScanline[offset] = luminance; + } + else { - if (i == 0) - { - this.pixelData[dataOffset] = (byte)luminance.Y; - } - else - { - this.pixelData[dataOffset + i] = bytes[3]; - } + rawScanline[offset + i] = this.chunkTypeBuffer[3]; } } } @@ -283,34 +284,24 @@ namespace ImageSharp.Formats } /// - /// Collects the true color pixel data. + /// Collects a row of true color pixel data. /// /// The pixel format. /// The packed format. uint, long, float. /// The image to encode. - private void CollectColorBytes(ImageBase image) + /// The row index. + /// The raw scanline. + private void CollectColorBytes(ImageBase image, int row, byte[] rawScanline) where TColor : struct, IPackedPixel where TPacked : struct { - // Copy the pixels across from the image. - // TODO: This could be sped up more if we add a method to PixelAccessor that does this by row directly to a byte array. - this.pixelData = new byte[this.width * this.height * this.bytesPerPixel]; - int stride = this.width * this.bytesPerPixel; using (PixelAccessor pixels = image.Lock()) { int bpp = this.bytesPerPixel; - Parallel.For( - 0, - this.height, - Bootstrapper.Instance.ParallelOptions, - y => - { - for (int x = 0; x < this.width; x++) - { - int dataOffset = (y * stride) + (x * this.bytesPerPixel); - pixels[x, y].ToBytes(this.pixelData, dataOffset, bpp == 4 ? ComponentOrder.XYZW : ComponentOrder.XYZ); - } - }); + for (int x = 0; x < this.width; x++) + { + pixels[x, row].ToBytes(rawScanline, x * this.bytesPerPixel, bpp == 4 ? ComponentOrder.XYZW : ComponentOrder.XYZ); + } } } @@ -318,33 +309,35 @@ namespace ImageSharp.Formats /// Encodes the pixel data line by line. /// Each scanline is encoded in the most optimal manner to improve compression. /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to encode. + /// The row. + /// The previous scanline. + /// The raw scanline. + /// The number of bytes per scanline. /// The - private byte[] EncodePixelData() + private byte[] EncodePixelRow(ImageBase image, int row, byte[] previousScanline, byte[] rawScanline, int bytesPerScanline) + where TColor : struct, IPackedPixel + where TPacked : struct { - // TODO: Use pointers - List filteredScanlines = new List(); - - byte[] previousScanline = new byte[this.width * this.bytesPerPixel]; - - for (int y = 0; y < this.height; y++) + switch (this.PngColorType) { - byte[] rawScanline = this.GetRawScanline(y); - byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, this.bytesPerPixel); - - filteredScanlines.Add(filteredScanline); - - previousScanline = rawScanline; + case PngColorType.Palette: + Buffer.BlockCopy(this.palettePixelData, row * bytesPerScanline, rawScanline, 0, bytesPerScanline); + break; + case PngColorType.Grayscale: + case PngColorType.GrayscaleWithAlpha: + this.CollectGrayscaleBytes(image, row, rawScanline); + break; + default: + this.CollectColorBytes(image, row, rawScanline); + break; } - // TODO: We should be able to use a byte array when not using interlaced encoding. - List result = new List(); + byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline, this.bytesPerPixel); - foreach (byte[] encodedScanline in filteredScanlines) - { - result.AddRange(encodedScanline); - } - - return result.ToArray(); + return filteredScanline; } /// @@ -353,36 +346,42 @@ namespace ImageSharp.Formats /// /// The raw scanline /// The previous scanline - /// The number of bytes per pixel + /// The number of bytes per scanline + /// The number of bytes per pixel /// The - private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int byteCount) + private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) { - List> candidates = new List>(); + Tuple[] candidates; + // Palette images don't compress well with adaptive filtering. if (this.PngColorType == PngColorType.Palette) { - byte[] none = NoneFilter.Encode(rawScanline); - candidates.Add(new Tuple(none, this.CalculateTotalVariation(none))); + candidates = new Tuple[1]; + + byte[] none = NoneFilter.Encode(rawScanline, bytesPerScanline); + candidates[0] = new Tuple(none, this.CalculateTotalVariation(none)); } else { - byte[] sub = SubFilter.Encode(rawScanline, byteCount); - candidates.Add(new Tuple(sub, this.CalculateTotalVariation(sub))); + candidates = new Tuple[4]; + + byte[] sub = SubFilter.Encode(rawScanline, bytesPerPixel, bytesPerScanline); + candidates[0] = new Tuple(sub, this.CalculateTotalVariation(sub)); - byte[] up = UpFilter.Encode(rawScanline, previousScanline); - candidates.Add(new Tuple(up, this.CalculateTotalVariation(up))); + byte[] up = UpFilter.Encode(rawScanline, bytesPerScanline, previousScanline); + candidates[1] = new Tuple(up, this.CalculateTotalVariation(up)); - byte[] average = AverageFilter.Encode(rawScanline, previousScanline, byteCount); - candidates.Add(new Tuple(average, this.CalculateTotalVariation(average))); + byte[] average = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline); + candidates[2] = new Tuple(average, this.CalculateTotalVariation(average)); - byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, byteCount); - candidates.Add(new Tuple(paeth, this.CalculateTotalVariation(paeth))); + byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline); + candidates[3] = new Tuple(paeth, this.CalculateTotalVariation(paeth)); } int lowestTotalVariation = int.MaxValue; int lowestTotalVariationIndex = 0; - for (int i = 0; i < candidates.Count; i++) + for (int i = 0; i < candidates.Length; i++) { if (candidates[i].Item2 < lowestTotalVariation) { @@ -412,19 +411,6 @@ namespace ImageSharp.Formats return totalVariation; } - /// - /// Get the raw scanline data from the pixel data - /// - /// The row number - /// The - private byte[] GetRawScanline(int y) - { - int stride = this.bytesPerPixel * this.width; - byte[] rawScanline = new byte[stride]; - Array.Copy(this.pixelData, y * stride, rawScanline, 0, stride); - return rawScanline; - } - /// /// Calculates the correct number of bytes per pixel for the given color type. /// @@ -460,18 +446,16 @@ namespace ImageSharp.Formats /// The . private void WriteHeaderChunk(Stream stream, PngHeader header) { - byte[] chunkData = new byte[13]; - - WriteInteger(chunkData, 0, header.Width); - WriteInteger(chunkData, 4, header.Height); + WriteInteger(this.chunkDataBuffer, 0, header.Width); + WriteInteger(this.chunkDataBuffer, 4, header.Height); - chunkData[8] = header.BitDepth; - chunkData[9] = header.ColorType; - chunkData[10] = header.CompressionMethod; - chunkData[11] = header.FilterMethod; - chunkData[12] = header.InterlaceMethod; + this.chunkDataBuffer[8] = header.BitDepth; + this.chunkDataBuffer[9] = header.ColorType; + this.chunkDataBuffer[10] = header.CompressionMethod; + this.chunkDataBuffer[11] = header.FilterMethod; + this.chunkDataBuffer[12] = header.InterlaceMethod; - this.WriteChunk(stream, PngChunkTypes.Header, chunkData); + this.WriteChunk(stream, PngChunkTypes.Header, this.chunkDataBuffer, 0, 13); } /// @@ -507,36 +491,44 @@ namespace ImageSharp.Formats // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; - byte[] colorTable = new byte[colorTableLength]; - - // TODO: Optimize this. - Parallel.For( - 0, - pixelCount, - Bootstrapper.Instance.ParallelOptions, - i => + byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength); + byte[] bytes = ArrayPool.Shared.Rent(4); + + try + { + for (int i = 0; i < pixelCount; i++) { int offset = i * 3; - Color color = new Color(palette[i].ToVector4()); - int alpha = color.A; + palette[i].ToBytes(bytes, 0, ComponentOrder.XYZW); + + int alpha = bytes[3]; // Premultiply the color. This helps prevent banding. + // TODO: Vector? if (alpha < 255 && alpha > this.Threshold) { - color = Color.Multiply(color, new Color(alpha, alpha, alpha, 255)); + bytes[0] = (byte)(bytes[0] * alpha).Clamp(0, 255); + bytes[1] = (byte)(bytes[1] * alpha).Clamp(0, 255); + bytes[2] = (byte)(bytes[2] * alpha).Clamp(0, 255); } - colorTable[offset] = color.R; - colorTable[offset + 1] = color.G; - colorTable[offset + 2] = color.B; + colorTable[offset] = bytes[0]; + colorTable[offset + 1] = bytes[1]; + colorTable[offset + 2] = bytes[2]; if (alpha <= this.Threshold) { transparentPixels.Add((byte)offset); } - }); + } - this.WriteChunk(stream, PngChunkTypes.Palette, colorTable); + this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); + } + finally + { + ArrayPool.Shared.Return(colorTable); + ArrayPool.Shared.Return(bytes); + } // Write the transparency data if (transparentPixels.Any()) @@ -565,14 +557,12 @@ namespace ImageSharp.Formats int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787D); int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787D); - byte[] chunkData = new byte[9]; - - WriteInteger(chunkData, 0, dpmX); - WriteInteger(chunkData, 4, dpmY); + WriteInteger(this.chunkDataBuffer, 0, dpmX); + WriteInteger(this.chunkDataBuffer, 4, dpmY); - chunkData[8] = 1; + this.chunkDataBuffer[8] = 1; - this.WriteChunk(stream, PngChunkTypes.Physical, chunkData); + this.WriteChunk(stream, PngChunkTypes.Physical, this.chunkDataBuffer, 0, 9); } } @@ -584,50 +574,67 @@ namespace ImageSharp.Formats { if (this.WriteGamma) { - int gammaValue = (int)(this.Gamma * 100000f); - - byte[] fourByteData = new byte[4]; + int gammaValue = (int)(this.Gamma * 100000F); byte[] size = BitConverter.GetBytes(gammaValue); - fourByteData[0] = size[3]; - fourByteData[1] = size[2]; - fourByteData[2] = size[1]; - fourByteData[3] = size[0]; + this.chunkDataBuffer[0] = size[3]; + this.chunkDataBuffer[1] = size[2]; + this.chunkDataBuffer[2] = size[1]; + this.chunkDataBuffer[3] = size[0]; - this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData); + this.WriteChunk(stream, PngChunkTypes.Gamma, this.chunkDataBuffer, 0, 4); } } /// /// Writes the pixel information to the stream. /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The image to encode. /// The stream. - private void WriteDataChunks(Stream stream) + private void WriteDataChunks(ImageBase image, Stream stream) + where TColor : struct, IPackedPixel + where TPacked : struct { - byte[] data = this.EncodePixelData(); + int bytesPerScanline = this.width * this.bytesPerPixel; + byte[] previousScanline = ArrayPool.Shared.Rent(bytesPerScanline); + byte[] rawScanline = ArrayPool.Shared.Rent(bytesPerScanline); byte[] buffer; int bufferLength; - MemoryStream memoryStream = null; try { memoryStream = new MemoryStream(); - using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) { - deflateStream.Write(data, 0, data.Length); - } + for (int y = 0; y < this.height; y++) + { + byte[] data = this.EncodePixelRow(image, y, previousScanline, rawScanline, bytesPerScanline); + deflateStream.Write(data, 0, data.Length); + deflateStream.Flush(); + + // Do a bit of shuffling; + byte[] tmp = rawScanline; + rawScanline = previousScanline; + previousScanline = tmp; + } - bufferLength = (int)memoryStream.Length; - buffer = memoryStream.ToArray(); + bufferLength = (int)memoryStream.Length; + buffer = memoryStream.ToArray(); + } } finally { + ArrayPool.Shared.Return(previousScanline); + ArrayPool.Shared.Return(rawScanline); memoryStream?.Dispose(); } + // Store the chunks in repeated 64k blocks. + // This reduces the memory load for decoding the image for many decoders. int numChunks = bufferLength / MaxBlockSize; if (bufferLength % MaxBlockSize != 0) @@ -669,7 +676,7 @@ namespace ImageSharp.Formats } /// - /// Writes a chunk of a specified length to the stream at the given offset. + /// Writes a chunk of a specified length to the stream at the given offset. /// /// The to write to. /// The type of chunk to write. @@ -678,26 +685,24 @@ namespace ImageSharp.Formats /// The of the data to write. private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length) { + // Chunk length WriteInteger(stream, length); - byte[] typeArray = new byte[4]; - typeArray[0] = (byte)type[0]; - typeArray[1] = (byte)type[1]; - typeArray[2] = (byte)type[2]; - typeArray[3] = (byte)type[3]; - - stream.Write(typeArray, 0, 4); + // Chunk type + this.chunkTypeBuffer[0] = (byte)type[0]; + this.chunkTypeBuffer[1] = (byte)type[1]; + this.chunkTypeBuffer[2] = (byte)type[2]; + this.chunkTypeBuffer[3] = (byte)type[3]; - if (data != null) - { - stream.Write(data, offset, length); - } + stream.Write(this.chunkTypeBuffer, 0, 4); Crc32 crc32 = new Crc32(); - crc32.Update(typeArray); + crc32.Update(this.chunkTypeBuffer); + // Chunk data if (data != null) { + stream.Write(data, offset, length); crc32.Update(data, offset, length); } diff --git a/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs index 3fa61ff56..2deb7dcf0 100644 --- a/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -37,7 +37,9 @@ namespace ImageSharp.Formats /// private bool isDisposed; - // The stream responsible for decompressing the input stream. + /// + /// The stream responsible for compressing the input stream. + /// private DeflateStream deflateStream; /// diff --git a/src/ImageSharp46/ImageSharp46.csproj b/src/ImageSharp46/ImageSharp46.csproj index b2b5d3755..bf7829e22 100644 --- a/src/ImageSharp46/ImageSharp46.csproj +++ b/src/ImageSharp46/ImageSharp46.csproj @@ -42,6 +42,10 @@ ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll True + + ..\..\packages\System.Buffers.4.0.0\lib\netstandard1.1\System.Buffers.dll + True + ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll @@ -138,6 +142,7 @@ + diff --git a/src/ImageSharp46/Properties/AssemblyInfo.cs b/src/ImageSharp46/Properties/AssemblyInfo.cs index 3a9fc9d7e..ec405e1d0 100644 --- a/src/ImageSharp46/Properties/AssemblyInfo.cs +++ b/src/ImageSharp46/Properties/AssemblyInfo.cs @@ -37,4 +37,3 @@ using System.Runtime.CompilerServices; // Ensure the internals can be tested. [assembly: InternalsVisibleTo("ImageSharp.Benchmarks")] [assembly: InternalsVisibleTo("ImageSharp.Tests")] -[assembly: InternalsVisibleTo("ImageSharp.Tests46")] \ No newline at end of file diff --git a/src/ImageSharp46/packages.config b/src/ImageSharp46/packages.config index 9faeca551..b67050550 100644 --- a/src/ImageSharp46/packages.config +++ b/src/ImageSharp46/packages.config @@ -3,6 +3,7 @@ + diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj index 2fd552efe..843e5c4b5 100644 --- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -7,8 +7,8 @@ {635E0A15-3893-4763-A7F6-FCCFF85BCCA4} Library Properties - ImageSharp.Tests46 - ImageSharp.Tests46 + ImageSharp.Tests + ImageSharp.Tests v4.6.1 512 @@ -118,6 +118,7 @@ + diff --git a/tests/ImageSharp.Tests46/TestFile.cs b/tests/ImageSharp.Tests46/TestFile.cs index 4f9f6fa32..f7e7f8517 100644 --- a/tests/ImageSharp.Tests46/TestFile.cs +++ b/tests/ImageSharp.Tests46/TestFile.cs @@ -4,7 +4,6 @@ // using System.IO; -using Xunit.Abstractions; namespace ImageSharp.Tests { diff --git a/tests/ImageSharp.Tests46/app.config b/tests/ImageSharp.Tests46/app.config new file mode 100644 index 000000000..5e95024db --- /dev/null +++ b/tests/ImageSharp.Tests46/app.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 306deee67e26b94c07da8e3748e1241020ee99e9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 9 Nov 2016 23:32:57 +0100 Subject: [PATCH 03/10] JpegSandbox --- .../Formats/Jpg/JpegDecoderCore.cs | 1 + .../Benchmark/DecodeJpeg.cs | 6 ++- .../Formats/Png/PngTests.cs | 2 + .../ImageSharp.Tests46.csproj | 1 + tests/ImageSharp.Tests46/JpegSandbox.cs | 44 +++++++++++++++++++ .../Processors/Filters/BoxBlurTest.cs | 1 + 6 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Tests46/JpegSandbox.cs diff --git a/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs index 332dcec48..a1e596649 100644 --- a/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs @@ -220,6 +220,7 @@ namespace ImageSharp.Formats } } + /// /// Decodes the image from the specified this._stream and sets /// the data to image. diff --git a/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs b/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs index cd32e7829..968ca070f 100644 --- a/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs +++ b/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using ImageSharp.Tests; using Xunit; using Xunit.Abstractions; @@ -15,7 +16,7 @@ namespace ImageSharp.Tests46.Benchmark public class DecodeJpeg { - private static byte[] jpegBytes = File.ReadAllBytes("../../TestImages/Formats/Jpg/Calliphora.jpg"); + private static byte[] jpegBytes = File.ReadAllBytes(TestImages.Jpeg.Calliphora); private ITestOutputHelper _output; @@ -60,8 +61,9 @@ namespace ImageSharp.Tests46.Benchmark { DoBenchmark(times, memoryStream => { - CoreImage image = new CoreImage(memoryStream); ; + CoreImage image = new CoreImage(memoryStream); }); } + } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs index c442b71c9..165a32ec9 100644 --- a/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs +++ b/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs @@ -13,6 +13,8 @@ namespace ImageSharp.Tests public class PngTests : FileTestBase { + + [Fact] public void ImageCanSaveIndexedPng() { diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj index 843e5c4b5..194d84be8 100644 --- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -72,6 +72,7 @@ + diff --git a/tests/ImageSharp.Tests46/JpegSandbox.cs b/tests/ImageSharp.Tests46/JpegSandbox.cs new file mode 100644 index 000000000..425d9193b --- /dev/null +++ b/tests/ImageSharp.Tests46/JpegSandbox.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using ImageSharp.Formats; +using Xunit; + +namespace ImageSharp.Tests +{ + public class JpegSandbox + { + public const string SandboxOutputDirectory = "_SandboxOutput"; + + protected string CreateTestOutputFile(string fileName) + { + if (!Directory.Exists(SandboxOutputDirectory)) + { + Directory.CreateDirectory(SandboxOutputDirectory); + } + + string id = Guid.NewGuid().ToString().Substring(0, 4); + + string ext = Path.GetExtension(fileName); + fileName = Path.GetFileNameWithoutExtension(fileName); + + return $"{SandboxOutputDirectory}/{fileName}_{id}{ext}"; + } + + protected Stream CreateOutputStream(string fileName) + { + fileName = CreateTestOutputFile(fileName); + return File.OpenWrite(fileName); + } + + [Fact] + public void OpenJpeg_SaveBmp() + { + var image = new TestFile(TestImages.Jpeg.Calliphora).CreateImage(); + + using (var stream = CreateOutputStream(nameof(OpenJpeg_SaveBmp)+".bmp")) + { + image.Save(stream, new BmpFormat()); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs index 4755acb1e..453d56863 100644 --- a/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs @@ -9,6 +9,7 @@ namespace ImageSharp.Tests using Xunit; + public class BoxBlurTest : FileTestBase { public static readonly TheoryData BoxBlurValues From ee5d3ac38fca5263ca8c5d004aea5e61c2173b3c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Nov 2016 02:27:55 +0100 Subject: [PATCH 04/10] minor cache optimiziation + benchmarks --- ImageSharp.46.sln | 6 + .../Formats/Jpg/Components/Block.cs | 21 +- .../Formats/Jpg/Components/Component.cs | 10 +- .../Formats/Jpg/Components/FDCT.cs | 2 +- .../Formats/Jpg/Components/Huffman.cs | 29 +- .../Formats/Jpg/Components/IDCT.cs | 6 +- .../Formats/Jpg/JpegDecoderCore.cs | 628 ++++++++++-------- .../Formats/Jpg/JpegEncoderCore.cs | 23 +- src/ImageSharp46/ImageSharp46.csproj | 5 +- src/ImageSharp46/packages.config | 1 + tests/ConsoleBenchmark/App.config | 22 + .../ConsoleBenchmark/ConsoleBenchmark.csproj | 89 +++ tests/ConsoleBenchmark/Program.cs | 36 + .../Properties/AssemblyInfo.cs | 36 + .../TestImages/Formats/Bmp/Car.bmp | 3 + .../TestImages/Formats/Bmp/F.bmp | 3 + .../TestImages/Formats/Bmp/neg_height.bmp | 3 + .../TestImages/Formats/Gif/giphy.gif | 3 + .../TestImages/Formats/Gif/rings.gif | 3 + .../TestImages/Formats/Jpg/Calliphora.jpg | 3 + .../TestImages/Formats/Jpg/Floorplan.jpeg | 3 + .../TestImages/Formats/Jpg/cmyk.jpg | 3 + .../TestImages/Formats/Jpg/exif.jpg | 3 + .../TestImages/Formats/Jpg/fb.jpg | 3 + .../Formats/Jpg/gamma_dalai_lama_gray.jpg | 3 + .../TestImages/Formats/Jpg/progress.jpg | 3 + .../TestImages/Formats/Jpg/turtle.jpg | 3 + .../TestImages/Formats/Png/blur.png | 3 + .../TestImages/Formats/Png/indexed.png | 3 + .../TestImages/Formats/Png/pd.png | 3 + .../TestImages/Formats/Png/pl.png | 3 + .../TestImages/Formats/Png/splash.png | 3 + tests/ConsoleBenchmark/packages.config | 8 + .../ImageSharp.Benchmarks/Image/DecodeJpeg.cs | 22 +- .../{DecodeJpeg.cs => DecodeJpegBenchmark.cs} | 6 +- .../ImageSharp.Tests46.csproj | 20 +- tests/ImageSharp.Tests46/JpegSandbox.cs | 38 +- tests/ImageSharp.Tests46/TestImages.cs | 23 +- tests/ImageSharp.Tests46/packages.config | 2 + 39 files changed, 735 insertions(+), 352 deletions(-) create mode 100644 tests/ConsoleBenchmark/App.config create mode 100644 tests/ConsoleBenchmark/ConsoleBenchmark.csproj create mode 100644 tests/ConsoleBenchmark/Program.cs create mode 100644 tests/ConsoleBenchmark/Properties/AssemblyInfo.cs create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Bmp/Car.bmp create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Bmp/F.bmp create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Bmp/neg_height.bmp create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Gif/giphy.gif create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Gif/rings.gif create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Jpg/Calliphora.jpg create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Jpg/Floorplan.jpeg create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Jpg/cmyk.jpg create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Jpg/exif.jpg create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Jpg/fb.jpg create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Jpg/progress.jpg create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Jpg/turtle.jpg create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Png/blur.png create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Png/indexed.png create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Png/pd.png create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Png/pl.png create mode 100644 tests/ConsoleBenchmark/TestImages/Formats/Png/splash.png create mode 100644 tests/ConsoleBenchmark/packages.config rename tests/ImageSharp.Tests46/Benchmark/{DecodeJpeg.cs => DecodeJpegBenchmark.cs} (91%) diff --git a/ImageSharp.46.sln b/ImageSharp.46.sln index fe112b9ea..270d391cc 100644 --- a/ImageSharp.46.sln +++ b/ImageSharp.46.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp46", "src\ImageSh EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Tests46", "tests\ImageSharp.Tests46\ImageSharp.Tests46.csproj", "{635E0A15-3893-4763-A7F6-FCCFF85BCCA4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleBenchmark", "tests\ConsoleBenchmark\ConsoleBenchmark.csproj", "{8783E3A1-79F1-4E37-AA79-F06C48BED5E7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {635E0A15-3893-4763-A7F6-FCCFF85BCCA4}.Debug|Any CPU.Build.0 = Debug|Any CPU {635E0A15-3893-4763-A7F6-FCCFF85BCCA4}.Release|Any CPU.ActiveCfg = Release|Any CPU {635E0A15-3893-4763-A7F6-FCCFF85BCCA4}.Release|Any CPU.Build.0 = Release|Any CPU + {8783E3A1-79F1-4E37-AA79-F06C48BED5E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8783E3A1-79F1-4E37-AA79-F06C48BED5E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8783E3A1-79F1-4E37-AA79-F06C48BED5E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8783E3A1-79F1-4E37-AA79-F06C48BED5E7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ImageSharp46/Formats/Jpg/Components/Block.cs b/src/ImageSharp46/Formats/Jpg/Components/Block.cs index 8bc5a861c..168413292 100644 --- a/src/ImageSharp46/Formats/Jpg/Components/Block.cs +++ b/src/ImageSharp46/Formats/Jpg/Components/Block.cs @@ -3,12 +3,14 @@ // Licensed under the Apache License, Version 2.0. // +using System.Buffers; + namespace ImageSharp.Formats { /// /// Represents an 8x8 block of coefficients to transform and encode. /// - internal class Block + internal struct Block { /// /// Gets the size of the block. @@ -18,16 +20,23 @@ namespace ImageSharp.Formats /// /// The array of block data. /// - private readonly int[] data; + public int[] Data; /// /// Initializes a new instance of the class. /// - public Block() + //public Block() + //{ + // this.data = new int[BlockSize]; + //} + + public void Init() { - this.data = new int[BlockSize]; + this.Data = new int[BlockSize]; } + public bool IsInitialized => this.Data != null; + /// /// Gets the pixel data at the given block index. /// @@ -37,8 +46,8 @@ namespace ImageSharp.Formats /// public int this[int index] { - get { return this.data[index]; } - set { this.data[index] = value; } + get { return this.Data[index]; } + set { this.Data[index] = value; } } } } diff --git a/src/ImageSharp46/Formats/Jpg/Components/Component.cs b/src/ImageSharp46/Formats/Jpg/Components/Component.cs index f56b6d513..f70dbff3f 100644 --- a/src/ImageSharp46/Formats/Jpg/Components/Component.cs +++ b/src/ImageSharp46/Formats/Jpg/Components/Component.cs @@ -8,26 +8,26 @@ namespace ImageSharp.Formats /// /// Represents a single color component /// - internal class Component + internal struct Component { /// /// Gets or sets the horizontal sampling factor. /// - public int HorizontalFactor { get; set; } + public int HorizontalFactor; /// /// Gets or sets the vertical sampling factor. /// - public int VerticalFactor { get; set; } + public int VerticalFactor; /// /// Gets or sets the identifier /// - public byte Identifier { get; set; } + public byte Identifier; /// /// Gets or sets the quantization table destination selector. /// - public byte Selector { get; set; } + public byte Selector; } } diff --git a/src/ImageSharp46/Formats/Jpg/Components/FDCT.cs b/src/ImageSharp46/Formats/Jpg/Components/FDCT.cs index cd27b9e73..13ce80f55 100644 --- a/src/ImageSharp46/Formats/Jpg/Components/FDCT.cs +++ b/src/ImageSharp46/Formats/Jpg/Components/FDCT.cs @@ -45,7 +45,7 @@ namespace ImageSharp.Formats /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. /// /// The block of coefficients. - public static void Transform(Block block) + public static void Transform(ref Block block) { // Pass 1: process rows. for (int y = 0; y < 8; y++) diff --git a/src/ImageSharp46/Formats/Jpg/Components/Huffman.cs b/src/ImageSharp46/Formats/Jpg/Components/Huffman.cs index 2c38cfd38..cef99ea1a 100644 --- a/src/ImageSharp46/Formats/Jpg/Components/Huffman.cs +++ b/src/ImageSharp46/Formats/Jpg/Components/Huffman.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Represents a Huffman tree /// - internal class Huffman + internal struct Huffman { /// /// Initializes a new instance of the class. @@ -16,20 +16,29 @@ namespace ImageSharp.Formats /// The log-2 size of the Huffman decoder's look-up table. /// The maximum (inclusive) number of codes in a Huffman tree. /// The maximum (inclusive) number of bits in a Huffman code. - public Huffman(int lutSize, int maxNCodes, int maxCodeLength) + //public Huffman(int lutSize, int maxNCodes, int maxCodeLength) + //{ + // this.Lut = new ushort[1 << lutSize]; + // this.Values = new byte[maxNCodes]; + // this.MinCodes = new int[maxCodeLength]; + // this.MaxCodes = new int[maxCodeLength]; + // this.Indices = new int[maxCodeLength]; + // this.Length = 0; + //} + + public void Init(int lutSize, int maxNCodes, int maxCodeLength) { this.Lut = new ushort[1 << lutSize]; this.Values = new byte[maxNCodes]; this.MinCodes = new int[maxCodeLength]; this.MaxCodes = new int[maxCodeLength]; this.Indices = new int[maxCodeLength]; - this.Length = 0; } /// /// Gets or sets the number of codes in the tree. /// - public int Length { get; set; } + public int Length; /// /// Gets the look-up table for the next LutSize bits in the bit-stream. @@ -37,28 +46,30 @@ namespace ImageSharp.Formats /// are 1 plus the code length, or 0 if the value is too large to fit in /// lutSize bits. /// - public ushort[] Lut { get; } + public ushort[] Lut; /// /// Gets the the decoded values, sorted by their encoding. /// - public byte[] Values { get; } + public byte[] Values; /// /// Gets the array of minimum codes. /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. /// - public int[] MinCodes { get; } + public int[] MinCodes; /// /// Gets the array of maximum codes. /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. /// - public int[] MaxCodes { get; } + public int[] MaxCodes; /// /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. /// - public int[] Indices { get; } + public int[] Indices; } + + } \ No newline at end of file diff --git a/src/ImageSharp46/Formats/Jpg/Components/IDCT.cs b/src/ImageSharp46/Formats/Jpg/Components/IDCT.cs index bc145779a..8bbbae357 100644 --- a/src/ImageSharp46/Formats/Jpg/Components/IDCT.cs +++ b/src/ImageSharp46/Formats/Jpg/Components/IDCT.cs @@ -3,6 +3,8 @@ // Licensed under the Apache License, Version 2.0. // +using System.Numerics; + namespace ImageSharp.Formats { /// @@ -39,8 +41,10 @@ namespace ImageSharp.Formats /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. /// /// The source block of coefficients - public static void Transform(Block src) + public static void Transform(ref Block block) { + + var src = block.Data; // Horizontal 1-D IDCT. for (int y = 0; y < 8; y++) { diff --git a/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs index a1e596649..d64803eb4 100644 --- a/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp46/Formats/Jpg/JpegDecoderCore.cs @@ -44,6 +44,8 @@ namespace ImageSharp.Formats /// private const int MaxTh = 3; + private const int ThRowSize = MaxTh + 1; + /// /// The maximum number of quantization tables /// @@ -85,7 +87,9 @@ namespace ImageSharp.Formats /// /// The huffman trees /// - private readonly Huffman[,] huffmanTrees; + //private readonly Huffman[,] huffmanTrees; + + private readonly Huffman[] huffmanTrees; /// /// Quantization tables, in zigzag order. @@ -187,12 +191,16 @@ namespace ImageSharp.Formats /// private short verticalResolution; + private int blockIndex; + /// /// Initializes a new instance of the class. /// public JpegDecoderCore() { - this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1]; + //this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1]; + this.huffmanTrees = new Huffman[(MaxTc + 1)*(MaxTh + 1)]; + this.quantizationTables = new Block[MaxTq + 1]; this.temp = new byte[2 * Block.BlockSize]; this.componentArray = new Component[MaxComponents]; @@ -201,23 +209,26 @@ namespace ImageSharp.Formats this.bytes = new Bytes(); // TODO: This looks like it could be static. + for (int i = 0; i < MaxTc + 1; i++) { for (int j = 0; j < MaxTh + 1; j++) { - this.huffmanTrees[i, j] = new Huffman(LutSize, MaxNCodes, MaxCodeLength); + //this.huffmanTrees[i, j].Init(LutSize, MaxNCodes, MaxCodeLength); + this.huffmanTrees[i* ThRowSize + j].Init(LutSize, MaxNCodes, MaxCodeLength); } } for (int i = 0; i < this.quantizationTables.Length; i++) { - this.quantizationTables[i] = new Block(); + //this.quantizationTables[i] = new Block(); + this.quantizationTables[i].Init(); } - for (int i = 0; i < this.componentArray.Length; i++) - { - this.componentArray[i] = new Component(); - } + //for (int i = 0; i < this.componentArray.Length; i++) + //{ + // this.componentArray[i] = new Component(); + //} } @@ -502,92 +513,96 @@ namespace ImageSharp.Formats throw new ImageFormatException("Bad Th value"); } - Huffman huffman = this.huffmanTrees[tc, th]; + ProcessDefineHuffmanTablesMarkerLoop(ref this.huffmanTrees[tc* ThRowSize + th], ref remaining); + } + } - // Read nCodes and huffman.Valuess (and derive h.Length). - // nCodes[i] is the number of codes with code length i. - // h.Length is the total number of codes. - huffman.Length = 0; + private void ProcessDefineHuffmanTablesMarkerLoop(ref Huffman huffman, ref int remaining) + { - int[] ncodes = new int[MaxCodeLength]; - for (int i = 0; i < ncodes.Length; i++) - { - ncodes[i] = this.temp[i + 1]; - huffman.Length += ncodes[i]; - } + // Read nCodes and huffman.Valuess (and derive h.Length). + // nCodes[i] is the number of codes with code length i. + // h.Length is the total number of codes. + huffman.Length = 0; - if (huffman.Length == 0) - { - throw new ImageFormatException("Huffman table has zero length"); - } + int[] ncodes = new int[MaxCodeLength]; + for (int i = 0; i < ncodes.Length; i++) + { + ncodes[i] = this.temp[i + 1]; + huffman.Length += ncodes[i]; + } - if (huffman.Length > MaxNCodes) - { - throw new ImageFormatException("Huffman table has excessive length"); - } + if (huffman.Length == 0) + { + throw new ImageFormatException("Huffman table has zero length"); + } - remaining -= huffman.Length + 17; - if (remaining < 0) - { - throw new ImageFormatException("DHT has wrong length"); - } + if (huffman.Length > MaxNCodes) + { + throw new ImageFormatException("Huffman table has excessive length"); + } - this.ReadFull(huffman.Values, 0, huffman.Length); + remaining -= huffman.Length + 17; + if (remaining < 0) + { + throw new ImageFormatException("DHT has wrong length"); + } - // Derive the look-up table. - for (int i = 0; i < huffman.Lut.Length; i++) - { - huffman.Lut[i] = 0; - } + this.ReadFull(huffman.Values, 0, huffman.Length); + + // Derive the look-up table. + for (int i = 0; i < huffman.Lut.Length; i++) + { + huffman.Lut[i] = 0; + } - uint x = 0, code = 0; + uint x = 0, code = 0; - for (int i = 0; i < LutSize; i++) + for (int i = 0; i < LutSize; i++) + { + code <<= 1; + + for (int j = 0; j < ncodes[i]; j++) { - code <<= 1; + // The codeLength is 1+i, so shift code by 8-(1+i) to + // calculate the high bits for every 8-bit sequence + // whose codeLength's high bits matches code. + // The high 8 bits of lutValue are the encoded value. + // The low 8 bits are 1 plus the codeLength. + byte base2 = (byte) (code << (7 - i)); + ushort lutValue = (ushort) ((huffman.Values[x] << 8) | (2 + i)); - for (int j = 0; j < ncodes[i]; j++) + for (int k = 0; k < 1 << (7 - i); k++) { - // The codeLength is 1+i, so shift code by 8-(1+i) to - // calculate the high bits for every 8-bit sequence - // whose codeLength's high bits matches code. - // The high 8 bits of lutValue are the encoded value. - // The low 8 bits are 1 plus the codeLength. - byte base2 = (byte)(code << (7 - i)); - ushort lutValue = (ushort)((huffman.Values[x] << 8) | (2 + i)); - - for (int k = 0; k < 1 << (7 - i); k++) - { - huffman.Lut[base2 | k] = lutValue; - } - - code++; - x++; + huffman.Lut[base2 | k] = lutValue; } + + code++; + x++; } + } - // Derive minCodes, maxCodes, and indices. - int c = 0, index = 0; - for (int i = 0; i < ncodes.Length; i++) + // Derive minCodes, maxCodes, and indices. + int c = 0, index = 0; + for (int i = 0; i < ncodes.Length; i++) + { + int nc = ncodes[i]; + if (nc == 0) { - int nc = ncodes[i]; - if (nc == 0) - { - huffman.MinCodes[i] = -1; - huffman.MaxCodes[i] = -1; - huffman.Indices[i] = -1; - } - else - { - huffman.MinCodes[i] = c; - huffman.MaxCodes[i] = c + nc - 1; - huffman.Indices[i] = index; - c += nc; - index += nc; - } - - c <<= 1; + huffman.MinCodes[i] = -1; + huffman.MaxCodes[i] = -1; + huffman.Indices[i] = -1; + } + else + { + huffman.MinCodes[i] = c; + huffman.MaxCodes[i] = c + nc - 1; + huffman.Indices[i] = index; + c += nc; + index += nc; } + + c <<= 1; } } @@ -596,7 +611,7 @@ namespace ImageSharp.Formats /// /// The huffman value /// The - private byte DecodeHuffman(Huffman huffman) + private byte DecodeHuffman(ref Huffman huffman) { if (huffman.Length == 0) { @@ -1480,7 +1495,8 @@ namespace ImageSharp.Formats this.ReadFull(this.temp, 0, remaining); byte scanComponentCount = this.temp[0]; - if (remaining != 4 + (2 * scanComponentCount)) + int scanComponentCountBy2 = 2 * scanComponentCount; + if (remaining != 4 + scanComponentCountBy2) { throw new ImageFormatException("SOS length inconsistent with number of components"); } @@ -1490,51 +1506,7 @@ namespace ImageSharp.Formats for (int i = 0; i < scanComponentCount; i++) { - // Component selector. - int cs = this.temp[1 + (2 * i)]; - int compIndex = -1; - for (int j = 0; j < this.componentCount; j++) - { - Component compv = this.componentArray[j]; - if (cs == compv.Identifier) - { - compIndex = j; - } - } - - if (compIndex < 0) - { - throw new ImageFormatException("Unknown component selector"); - } - - scan[i].Index = (byte)compIndex; - - // Section B.2.3 states that "the value of Cs_j shall be different from - // the values of Cs_1 through Cs_(j-1)". Since we have previously - // verified that a frame's component identifiers (C_i values in section - // B.2.2) are unique, it suffices to check that the implicit indexes - // into comp are unique. - for (int j = 0; j < i; j++) - { - if (scan[i].Index == scan[j].Index) - { - throw new ImageFormatException("Repeated component selector"); - } - } - - totalHv += this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor; - - scan[i].DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4); - if (scan[i].DcTableSelector > MaxTh) - { - throw new ImageFormatException("Bad DC table selector value"); - } - - scan[i].AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f); - if (scan[i].AcTableSelector > MaxTh) - { - throw new ImageFormatException("Bad AC table selector value"); - } + ProcessScanImpl(i, ref scan[i], scan, ref totalHv); } // Section B.2.3 states that if there is more than one component then the @@ -1565,10 +1537,10 @@ namespace ImageSharp.Formats if (this.isProgressive) { - zigStart = this.temp[1 + (2 * scanComponentCount)]; - zigEnd = this.temp[2 + (2 * scanComponentCount)]; - ah = this.temp[3 + (2 * scanComponentCount)] >> 4; - al = this.temp[3 + (2 * scanComponentCount)] & 0x0f; + zigStart = this.temp[1 + scanComponentCountBy2]; + zigEnd = this.temp[2 + scanComponentCountBy2]; + ah = this.temp[3 + scanComponentCountBy2] >> 4; + al = this.temp[3 + scanComponentCountBy2] & 0x0f; if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd) { @@ -1608,7 +1580,7 @@ namespace ImageSharp.Formats for (int j = 0; j < this.progCoeffs[compIndex].Length; j++) { - this.progCoeffs[compIndex][j] = new Block(); + this.progCoeffs[compIndex][j].Init(); } } } @@ -1620,7 +1592,7 @@ namespace ImageSharp.Formats byte expectedRst = JpegConstants.Markers.RST0; // b is the decoded coefficients block, in natural (not zig-zag) order. - Block b; + //Block b; int[] dc = new int[MaxComponents]; // bx and by are the location of the current block, in units of 8x8 @@ -1636,7 +1608,7 @@ namespace ImageSharp.Formats int compIndex = scan[i].Index; int hi = this.componentArray[compIndex].HorizontalFactor; int vi = this.componentArray[compIndex].VerticalFactor; - Block qt = this.quantizationTables[this.componentArray[compIndex].Selector]; + for (int j = 0; j < hi * vi; j++) { @@ -1679,168 +1651,28 @@ namespace ImageSharp.Formats } } + var qtIndex = this.componentArray[compIndex].Selector; + // Load the previous partially decoded coefficients, if applicable. - b = this.isProgressive ? this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] : new Block(); - - if (ah != 0) - { - this.Refine(b, this.huffmanTrees[AcTable, scan[i].AcTableSelector], zigStart, zigEnd, 1 << al); - } - else - { - int zig = zigStart; - if (zig == 0) - { - zig++; - - // Decode the DC coefficient, as specified in section F.2.2.1. - byte value = this.DecodeHuffman(this.huffmanTrees[DcTable, scan[i].DcTableSelector]); - if (value > 16) - { - throw new ImageFormatException("Excessive DC component"); - } - - int deltaDC = this.ReceiveExtend(value); - dc[compIndex] += deltaDC; - b[0] = dc[compIndex] << al; - } - - if (zig <= zigEnd && this.eobRun > 0) - { - this.eobRun--; - } - else - { - // Decode the AC coefficients, as specified in section F.2.2.2. - Huffman huffv = this.huffmanTrees[AcTable, scan[i].AcTableSelector]; - for (; zig <= zigEnd; zig++) - { - byte value = this.DecodeHuffman(huffv); - byte val0 = (byte)(value >> 4); - byte val1 = (byte)(value & 0x0f); - if (val1 != 0) - { - zig += val0; - if (zig > zigEnd) - { - break; - } - - int ac = this.ReceiveExtend(val1); - b[Unzig[zig]] = ac << al; - } - else - { - if (val0 != 0x0f) - { - this.eobRun = (ushort)(1 << val0); - if (val0 != 0) - { - this.eobRun |= (ushort)this.DecodeBits(val0); - } - - this.eobRun--; - break; - } - - zig += 0x0f; - } - } - } - } + + //b = this.isProgressive ? this.progCoeffs[compIndex][blockIndex] : new Block(); if (this.isProgressive) { - if (zigEnd != Block.BlockSize - 1 || al != 0) - { - // We haven't completely decoded this 8x8 block. Save the coefficients. - this.progCoeffs[compIndex][((by * mxx) * hi) + bx] = b; - - // At this point, we could execute the rest of the loop body to dequantize and - // perform the inverse DCT, to save early stages of a progressive image to the - // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, - // the jpeg.Decode function does not return until the entire image is decoded, - // so we "continue" here to avoid wasted computation. - continue; - } - } - - // Dequantize, perform the inverse DCT and store the block to the image. - for (int zig = 0; zig < Block.BlockSize; zig++) - { - b[Unzig[zig]] *= qt[zig]; - } - - IDCT.Transform(b); - - byte[] dst; - int offset; - int stride; - - if (this.componentCount == 1) - { - dst = this.grayImage.Pixels; - stride = this.grayImage.Stride; - offset = this.grayImage.Offset + (8 * ((by * this.grayImage.Stride) + bx)); + blockIndex = ((@by * mxx) * hi) + bx; + ProcessBlockImpl(ah, + ref this.progCoeffs[compIndex][blockIndex], + scan, i, zigStart, zigEnd, al, dc, compIndex, @by, mxx, hi, bx, + ref this.quantizationTables[qtIndex] + ); } else { - switch (compIndex) - { - case 0: - dst = this.ycbcrImage.YChannel; - stride = this.ycbcrImage.YStride; - offset = this.ycbcrImage.YOffset + (8 * ((by * this.ycbcrImage.YStride) + bx)); - break; - - case 1: - dst = this.ycbcrImage.CbChannel; - stride = this.ycbcrImage.CStride; - offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx)); - break; - - case 2: - dst = this.ycbcrImage.CrChannel; - stride = this.ycbcrImage.CStride; - offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx)); - break; - - case 3: - - dst = this.blackPixels; - stride = this.blackStride; - offset = 8 * ((by * this.blackStride) + bx); - break; - - default: - throw new ImageFormatException("Too many components"); - } - } - - // Level shift by +128, clip to [0, 255], and write to dst. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - int yStride = y * stride; - - for (int x = 0; x < 8; x++) - { - int c = b[y8 + x]; - if (c < -128) - { - c = 0; - } - else if (c > 127) - { - c = 255; - } - else - { - c += 128; - } - - dst[yStride + x + offset] = (byte)c; - } + var b = new Block(); + b.Init(); + ProcessBlockImpl(ah, ref b, scan, i, zigStart, zigEnd, al, dc, compIndex, @by, mxx, hi, + bx, ref this.quantizationTables[qtIndex] + ); } } @@ -1883,6 +1715,226 @@ namespace ImageSharp.Formats // for my } + private void ProcessBlockImpl(int ah, ref Block b, Scan[] scan, int i, int zigStart, int zigEnd, int al, + int[] dc, int compIndex, int @by, int mxx, int hi, int bx, ref Block qt) + { + if (ah != 0) + { + this.Refine(ref b, ref this.huffmanTrees[AcTable * ThRowSize + scan[i].AcTableSelector], zigStart, zigEnd, 1 << al); + } + else + { + int zig = zigStart; + if (zig == 0) + { + zig++; + + // Decode the DC coefficient, as specified in section F.2.2.1. + byte value = this.DecodeHuffman(ref this.huffmanTrees[DcTable * ThRowSize + scan[i].DcTableSelector]); + if (value > 16) + { + throw new ImageFormatException("Excessive DC component"); + } + + int deltaDC = this.ReceiveExtend(value); + dc[compIndex] += deltaDC; + b[0] = dc[compIndex] << al; + } + + if (zig <= zigEnd && this.eobRun > 0) + { + this.eobRun--; + } + else + { + // Decode the AC coefficients, as specified in section F.2.2.2. + //Huffman huffv = ; + for (; zig <= zigEnd; zig++) + { + byte value = this.DecodeHuffman(ref this.huffmanTrees[AcTable * ThRowSize + scan[i].AcTableSelector]); + byte val0 = (byte) (value >> 4); + byte val1 = (byte) (value & 0x0f); + if (val1 != 0) + { + zig += val0; + if (zig > zigEnd) + { + break; + } + + int ac = this.ReceiveExtend(val1); + b[Unzig[zig]] = ac << al; + } + else + { + if (val0 != 0x0f) + { + this.eobRun = (ushort) (1 << val0); + if (val0 != 0) + { + this.eobRun |= (ushort) this.DecodeBits(val0); + } + + this.eobRun--; + break; + } + + zig += 0x0f; + } + } + } + } + + if (this.isProgressive) + { + if (zigEnd != Block.BlockSize - 1 || al != 0) + { + // We haven't completely decoded this 8x8 block. Save the coefficients. + this.progCoeffs[compIndex][((@by*mxx)*hi) + bx] = b; + + // At this point, we could execute the rest of the loop body to dequantize and + // perform the inverse DCT, to save early stages of a progressive image to the + // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, + // the jpeg.Decode function does not return until the entire image is decoded, + // so we "continue" here to avoid wasted computation. + return; + } + } + + // Dequantize, perform the inverse DCT and store the block to the image. + for (int zig = 0; zig < Block.BlockSize; zig++) + { + b[Unzig[zig]] *= qt[zig]; + } + + IDCT.Transform(ref b); + + byte[] dst; + int offset; + int stride; + + if (this.componentCount == 1) + { + dst = this.grayImage.Pixels; + stride = this.grayImage.Stride; + offset = this.grayImage.Offset + (8*((@by*this.grayImage.Stride) + bx)); + } + else + { + switch (compIndex) + { + case 0: + dst = this.ycbcrImage.YChannel; + stride = this.ycbcrImage.YStride; + offset = this.ycbcrImage.YOffset + (8*((@by*this.ycbcrImage.YStride) + bx)); + break; + + case 1: + dst = this.ycbcrImage.CbChannel; + stride = this.ycbcrImage.CStride; + offset = this.ycbcrImage.COffset + (8*((@by*this.ycbcrImage.CStride) + bx)); + break; + + case 2: + dst = this.ycbcrImage.CrChannel; + stride = this.ycbcrImage.CStride; + offset = this.ycbcrImage.COffset + (8*((@by*this.ycbcrImage.CStride) + bx)); + break; + + case 3: + + dst = this.blackPixels; + stride = this.blackStride; + offset = 8*((@by*this.blackStride) + bx); + break; + + default: + throw new ImageFormatException("Too many components"); + } + } + + // Level shift by +128, clip to [0, 255], and write to dst. + for (int y = 0; y < 8; y++) + { + int y8 = y*8; + int yStride = y*stride; + + for (int x = 0; x < 8; x++) + { + int c = b[y8 + x]; + if (c < -128) + { + c = 0; + } + else if (c > 127) + { + c = 255; + } + else + { + c += 128; + } + + dst[yStride + x + offset] = (byte) c; + } + } + } + + private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv) + { + // Component selector. + int cs = this.temp[1 + (2 * i)]; + int compIndex = -1; + for (int j = 0; j < this.componentCount; j++) + { + //Component compv = ; + if (cs == this.componentArray[j].Identifier) + { + compIndex = j; + } + } + + if (compIndex < 0) + { + throw new ImageFormatException("Unknown component selector"); + } + + currentScan.Index = (byte)compIndex; + + ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]); + } + + private void ProcessComponentImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv, ref Component currentComponent) + { + // Section B.2.3 states that "the value of Cs_j shall be different from + // the values of Cs_1 through Cs_(j-1)". Since we have previously + // verified that a frame's component identifiers (C_i values in section + // B.2.2) are unique, it suffices to check that the implicit indexes + // into comp are unique. + for (int j = 0; j < i; j++) + { + if (currentScan.Index == scan[j].Index) + { + throw new ImageFormatException("Repeated component selector"); + } + } + + + totalHv += currentComponent.HorizontalFactor*currentComponent.VerticalFactor; + + currentScan.DcTableSelector = (byte) (this.temp[2 + (2*i)] >> 4); + if (currentScan.DcTableSelector > MaxTh) + { + throw new ImageFormatException("Bad DC table selector value"); + } + + currentScan.AcTableSelector = (byte) (this.temp[2 + (2*i)] & 0x0f); + if (currentScan.AcTableSelector > MaxTh) + { + throw new ImageFormatException("Bad AC table selector value"); + } + } + /// /// Decodes a successive approximation refinement block, as specified in section G.1.2. /// @@ -1891,7 +1943,7 @@ namespace ImageSharp.Formats /// The zig-zag start index /// The zig-zag end index /// The low transform offset - private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta) + private void Refine(ref Block b, ref Huffman h, int zigStart, int zigEnd, int delta) { // Refining a DC component is trivial. if (zigStart == 0) @@ -1918,7 +1970,7 @@ namespace ImageSharp.Formats { bool done = false; int z = 0; - byte val = this.DecodeHuffman(h); + byte val = this.DecodeHuffman(ref h); int val0 = val >> 4; int val1 = val & 0x0f; diff --git a/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs index 9cb4fce26..9a282a72b 100644 --- a/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs @@ -488,9 +488,9 @@ namespace ImageSharp.Formats /// The quantization table index. /// The previous DC value. /// The - private int WriteBlock(Block block, QuantIndex index, int prevDC) + private int WriteBlock(ref Block block, QuantIndex index, int prevDC) { - FDCT.Transform(block); + FDCT.Transform(ref block); // Emit the DC delta. int dc = Round(block[0], 8 * this.quant[(int)index][0]); @@ -541,7 +541,8 @@ namespace ImageSharp.Formats /// The red chroma block. /// The blue chroma block. // ReSharper disable StyleCop.SA1305 - private void ToYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) + private void ToYCbCr(PixelAccessor pixels, int x, int y, + ref Block yBlock, ref Block cbBlock, ref Block crBlock) // ReSharper restore StyleCop.SA1305 where TColor : struct, IPackedPixel where TPacked : struct @@ -858,10 +859,10 @@ namespace ImageSharp.Formats { for (int x = 0; x < pixels.Width; x += 8) { - this.ToYCbCr(pixels, x, y, b, cb, cr); - prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); - prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb); - prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr); + this.ToYCbCr(pixels, x, y, ref b, ref cb, ref cr); + prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY); + prevDCCb = this.WriteBlock(ref cb, QuantIndex.Chrominance, prevDCCb); + prevDCCr = this.WriteBlock(ref cr, QuantIndex.Chrominance, prevDCCr); } } } @@ -902,14 +903,14 @@ namespace ImageSharp.Formats int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); - prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); + this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]); + prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY); } this.Scale16X16To8X8(b, cb); - prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb); + prevDCCb = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCb); this.Scale16X16To8X8(b, cr); - prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr); + prevDCCr = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCr); } } } diff --git a/src/ImageSharp46/ImageSharp46.csproj b/src/ImageSharp46/ImageSharp46.csproj index bf7829e22..e88dbb0e2 100644 --- a/src/ImageSharp46/ImageSharp46.csproj +++ b/src/ImageSharp46/ImageSharp46.csproj @@ -86,7 +86,10 @@ True - + + ..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll + True + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.0.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll True diff --git a/src/ImageSharp46/packages.config b/src/ImageSharp46/packages.config index b67050550..6ace6583b 100644 --- a/src/ImageSharp46/packages.config +++ b/src/ImageSharp46/packages.config @@ -23,6 +23,7 @@ + diff --git a/tests/ConsoleBenchmark/App.config b/tests/ConsoleBenchmark/App.config new file mode 100644 index 000000000..5bf749f82 --- /dev/null +++ b/tests/ConsoleBenchmark/App.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ConsoleBenchmark/ConsoleBenchmark.csproj b/tests/ConsoleBenchmark/ConsoleBenchmark.csproj new file mode 100644 index 000000000..da38098fc --- /dev/null +++ b/tests/ConsoleBenchmark/ConsoleBenchmark.csproj @@ -0,0 +1,89 @@ + + + + + Debug + AnyCPU + {8783E3A1-79F1-4E37-AA79-F06C48BED5E7} + Exe + Properties + ConsoleBenchmark + ConsoleBenchmark + v4.6.1 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + ..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll + True + + + + + + + + + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll + True + + + ..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + {fba0b5f6-09c2-4317-8ef6-6adb9b20e6b1} + ImageSharp46 + + + {635e0a15-3893-4763-a7f6-fccff85bcca4} + ImageSharp.Tests46 + + + + + \ No newline at end of file diff --git a/tests/ConsoleBenchmark/Program.cs b/tests/ConsoleBenchmark/Program.cs new file mode 100644 index 000000000..52a53e316 --- /dev/null +++ b/tests/ConsoleBenchmark/Program.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ImageSharp.Tests; +using ImageSharp.Tests46.Benchmark; +using Xunit.Abstractions; + +namespace ConsoleBenchmark +{ + class Program + { + private class Output : ITestOutputHelper + { + public void WriteLine(string message) + { + Console.WriteLine(message); + } + + public void WriteLine(string format, params object[] args) + { + Console.WriteLine(format, args); + } + } + + static void Main(string[] args) + { + DecodeJpegBenchmark benchmark = new DecodeJpegBenchmark(new Output()); + benchmark.JpegCore(100); + //JpegSandbox test = new JpegSandbox(null); + + //test.OpenJpeg_SaveBmp(TestImages.Jpeg.Calliphora); + } + } +} diff --git a/tests/ConsoleBenchmark/Properties/AssemblyInfo.cs b/tests/ConsoleBenchmark/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..5d8efdb2e --- /dev/null +++ b/tests/ConsoleBenchmark/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ConsoleBenchmark")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Sapa")] +[assembly: AssemblyProduct("ConsoleBenchmark")] +[assembly: AssemblyCopyright("Copyright © Sapa 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8783e3a1-79f1-4e37-aa79-f06c48bed5e7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Bmp/Car.bmp b/tests/ConsoleBenchmark/TestImages/Formats/Bmp/Car.bmp new file mode 100644 index 000000000..edaf3a8e4 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Bmp/Car.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d3a4a30cd67db6ded1e57126c7ba275404703e64b3dfb1c9c711128c15b0124 +size 810054 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Bmp/F.bmp b/tests/ConsoleBenchmark/TestImages/Formats/Bmp/F.bmp new file mode 100644 index 000000000..d95598bef --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Bmp/F.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6da008d2b285b2db946e6d4ebf8569b0ddd4a05ef273b38304cb65afccac87b3 +size 65502 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Bmp/neg_height.bmp b/tests/ConsoleBenchmark/TestImages/Formats/Bmp/neg_height.bmp new file mode 100644 index 000000000..d0b99a902 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Bmp/neg_height.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81437b88a5d92fcb545fae4991643a0c73d95d0277dac0b79074971780008c8c +size 6220854 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Gif/giphy.gif b/tests/ConsoleBenchmark/TestImages/Formats/Gif/giphy.gif new file mode 100644 index 000000000..029afdec6 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Gif/giphy.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f42abd9f3e493a0acd5303ab0d37a6179835c5a14364a1f001abd9d9e906f96 +size 53655 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Gif/rings.gif b/tests/ConsoleBenchmark/TestImages/Formats/Gif/rings.gif new file mode 100644 index 000000000..acd5d6339 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Gif/rings.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:716448da88152225767c024aac498f5b7562b6e8391907cefc9d03dba40050fd +size 53435 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Jpg/Calliphora.jpg b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/Calliphora.jpg new file mode 100644 index 000000000..aa3fdef01 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/Calliphora.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67172fcab405f914587b88cd1106328e6b24ab59d622ba509dcc99509951ff5c +size 254766 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Jpg/Floorplan.jpeg b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/Floorplan.jpeg new file mode 100644 index 000000000..6f439d220 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/Floorplan.jpeg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de00b34b78dfa0886c93d8dd5cede27b4940d5c620e44631e77e6dc8838befc3 +size 161577 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Jpg/cmyk.jpg b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/cmyk.jpg new file mode 100644 index 000000000..2fe8f0a61 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/cmyk.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0 +size 611572 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Jpg/exif.jpg b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/exif.jpg new file mode 100644 index 000000000..cba862660 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/exif.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a9d04b92d0de5836c59ede8ae421235488e4031e893e07b1fe7e4b78f6a9901 +size 32764 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Jpg/fb.jpg b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/fb.jpg new file mode 100644 index 000000000..7241890e2 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/fb.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c +size 15787 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg new file mode 100644 index 000000000..c305caef4 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d92a88b04518e266b98d9d2f5b4eb88f3f91c332d3397ea859bab8cabc41185 +size 84887 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Jpg/progress.jpg b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/progress.jpg new file mode 100644 index 000000000..30b214c22 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/progress.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9a1f0da3c5b3a3e7e7f35abe9f5b458163b48ca56226227b3d3cffe06af1971 +size 44884 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Jpg/turtle.jpg b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/turtle.jpg new file mode 100644 index 000000000..07d96543b --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Jpg/turtle.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e5c576b0c5e743cfd498b110305268ecbae63c62061ba6c7eb8e060728191f1 +size 55126 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Png/blur.png b/tests/ConsoleBenchmark/TestImages/Formats/Png/blur.png new file mode 100644 index 000000000..2ac488b7c --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Png/blur.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10df946d3d6a9832bacd9ac2587b890c348d17731412c8fd17c34f66f35d9c94 +size 183768 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Png/indexed.png b/tests/ConsoleBenchmark/TestImages/Formats/Png/indexed.png new file mode 100644 index 000000000..f06e1cbd6 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Png/indexed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65566cde707c02757a26fb5fb7702be9c53f55c17a1748d81c384559de2d1173 +size 33529 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Png/pd.png b/tests/ConsoleBenchmark/TestImages/Formats/Png/pd.png new file mode 100644 index 000000000..12fde3229 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Png/pd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91dd938fb916d368738826741551aea694b340ba3362f56200c5d3c5e5510267 +size 1406 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Png/pl.png b/tests/ConsoleBenchmark/TestImages/Formats/Png/pl.png new file mode 100644 index 000000000..15e991284 --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Png/pl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa550e61fe276d2ebee8666d0cbb811449433edb76e01bcd38cb00c1aafc417e +size 91 diff --git a/tests/ConsoleBenchmark/TestImages/Formats/Png/splash.png b/tests/ConsoleBenchmark/TestImages/Formats/Png/splash.png new file mode 100644 index 000000000..ca4f86bce --- /dev/null +++ b/tests/ConsoleBenchmark/TestImages/Formats/Png/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4c13422913f1c1910f8dd607236e79b4a5c7053deb8ce1c8be8372eca7465fb +size 245033 diff --git a/tests/ConsoleBenchmark/packages.config b/tests/ConsoleBenchmark/packages.config new file mode 100644 index 000000000..50e8c3f1f --- /dev/null +++ b/tests/ConsoleBenchmark/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs index 6f2bd5e60..49af14763 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs @@ -26,17 +26,17 @@ namespace ImageSharp.Benchmarks.Image } } - [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] - public Size JpegSystemDrawing() - { - using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes)) - { - using (Image image = Image.FromStream(memoryStream)) - { - return image.Size; - } - } - } + //[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] + //public Size JpegSystemDrawing() + //{ + // using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes)) + // { + // using (Image image = Image.FromStream(memoryStream)) + // { + // return image.Size; + // } + // } + //} [Benchmark(Description = "ImageSharp Jpeg")] public CoreSize JpegCore() diff --git a/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs b/tests/ImageSharp.Tests46/Benchmark/DecodeJpegBenchmark.cs similarity index 91% rename from tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs rename to tests/ImageSharp.Tests46/Benchmark/DecodeJpegBenchmark.cs index 968ca070f..6de820f8b 100644 --- a/tests/ImageSharp.Tests46/Benchmark/DecodeJpeg.cs +++ b/tests/ImageSharp.Tests46/Benchmark/DecodeJpegBenchmark.cs @@ -14,20 +14,20 @@ namespace ImageSharp.Tests46.Benchmark using CoreImage = ImageSharp.Image; using CoreSize = ImageSharp.Size; - public class DecodeJpeg + public class DecodeJpegBenchmark { private static byte[] jpegBytes = File.ReadAllBytes(TestImages.Jpeg.Calliphora); private ITestOutputHelper _output; - public DecodeJpeg(ITestOutputHelper output) + public DecodeJpegBenchmark(ITestOutputHelper output) { _output = output; } private void DoBenchmark(int times, Action action, [CallerMemberName]string method = null) { - _output.WriteLine($"Starting {method}.. "); + _output.WriteLine($"{method} x {times} ... "); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < times; i++) { diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj index 194d84be8..a1b4e990f 100644 --- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -1,5 +1,6 @@  + Debug @@ -12,6 +13,8 @@ v4.6.1 512 + + true @@ -37,7 +40,10 @@ - + + ..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll + True + @@ -62,7 +68,7 @@ - + @@ -148,8 +154,16 @@ - + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + - \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Ellipse.cs b/src/ImageSharp46/Numerics/Ellipse.cs deleted file mode 100644 index f464c4b26..000000000 --- a/src/ImageSharp46/Numerics/Ellipse.cs +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.ComponentModel; - using System.Numerics; - - public struct Ellipse : IEquatable - { - /// - /// The center point. - /// - private Point center; - - /// - /// Represents a that has X and Y values set to zero. - /// - public static readonly Ellipse Empty = default(Ellipse); - - public Ellipse(Point center, float radiusX, float radiusY) - { - this.center = center; - this.RadiusX = radiusX; - this.RadiusY = radiusY; - } - - /// - /// Gets the x-radius of this . - /// - public float RadiusX { get; } - - /// - /// Gets the y-radius of this . - /// - public float RadiusY { get; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Ellipse left, Ellipse right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Ellipse left, Ellipse right) - { - return !left.Equals(right); - } - - /// - /// Returns the center point of the given - /// - /// The ellipse - /// - public static Vector2 Center(Ellipse ellipse) - { - return new Vector2(ellipse.center.X, ellipse.center.Y); - } - - /// - /// Determines if the specfied point is contained within the rectangular region defined by - /// this . - /// - /// The x-coordinate of the given point. - /// The y-coordinate of the given point. - /// The - public bool Contains(int x, int y) - { - if (this.RadiusX <= 0 || this.RadiusY <= 0) - { - return false; - } - - // TODO: SIMD? - // This is a more general form of the circle equation - // X^2/a^2 + Y^2/b^2 <= 1 - Point normalized = new Point(x - this.center.X, y - this.center.Y); - int nX = normalized.X; - int nY = normalized.Y; - - return ((double)(nX * nX) / (this.RadiusX * this.RadiusX)) - + ((double)(nY * nY) / (this.RadiusY * this.RadiusY)) - <= 1.0; - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Ellipse [ Empty ]"; - } - - return - $"Ellipse [ RadiusX={this.RadiusX}, RadiusY={this.RadiusX}, Centre={this.center.X},{this.center.Y} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Ellipse) - { - return this.Equals((Ellipse)obj); - } - - return false; - } - - /// - public bool Equals(Ellipse other) - { - return this.center.Equals(other.center) - && this.RadiusX.Equals(other.RadiusX) - && this.RadiusY.Equals(other.RadiusY); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Ellipse ellipse) - { - unchecked - { - int hashCode = ellipse.center.GetHashCode(); - hashCode = (hashCode * 397) ^ ellipse.RadiusX.GetHashCode(); - hashCode = (hashCode * 397) ^ ellipse.RadiusY.GetHashCode(); - return hashCode; - } - } - } -} diff --git a/src/ImageSharp46/Numerics/LongRational.cs b/src/ImageSharp46/Numerics/LongRational.cs deleted file mode 100644 index f56abc289..000000000 --- a/src/ImageSharp46/Numerics/LongRational.cs +++ /dev/null @@ -1,355 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Globalization; - using System.Text; - - /// - /// Represents a number that can be expressed as a fraction - /// - /// - /// This is a very simplified implementation of a rational number designed for use with metadata only. - /// - internal struct LongRational : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// - /// The number above the line in a vulgar fraction showing how many of the parts - /// indicated by the denominator are taken. - /// - /// - /// The number below the line in a vulgar fraction; a divisor. - /// - public LongRational(long numerator, long denominator) - : this(numerator, denominator, false) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The number above the line in a vulgar fraction showing how many of the parts - /// indicated by the denominator are taken. - /// - /// - /// The number below the line in a vulgar fraction; a divisor. - /// - /// - /// Whether to attempt to simplify the fractional parts. - /// - public LongRational(long numerator, long denominator, bool simplify) - : this() - { - this.Numerator = numerator; - this.Denominator = denominator; - - if (simplify) - { - this.Simplify(); - } - } - - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public LongRational(double value, bool bestPrecision) - : this() - { - if (double.IsNaN(value)) - { - this.Numerator = this.Denominator = 0; - return; - } - - if (double.IsPositiveInfinity(value)) - { - this.Numerator = 1; - this.Denominator = 0; - return; - } - - if (double.IsNegativeInfinity(value)) - { - this.Numerator = -1; - this.Denominator = 0; - return; - } - - this.Numerator = 1; - this.Denominator = 1; - - double val = Math.Abs(value); - double df = this.Numerator / (double)this.Denominator; - double epsilon = bestPrecision ? double.Epsilon : .000001; - - while (Math.Abs(df - val) > epsilon) - { - if (df < val) - { - this.Numerator++; - } - else - { - this.Denominator++; - this.Numerator = (int)(val * this.Denominator); - } - - df = this.Numerator / (double)this.Denominator; - } - - if (value < 0.0) - { - this.Numerator *= -1; - } - - this.Simplify(); - } - - /// - /// Gets the numerator of a number. - /// - public long Numerator - { - get; - private set; - } - - /// - /// Gets the denominator of a number. - /// - public long Denominator - { - get; - private set; - } - - /// - /// Gets a value indicating whether this instance is indeterminate. - /// - public bool IsIndeterminate - { - get - { - if (this.Denominator != 0) - { - return false; - } - - return this.Numerator == 0; - } - } - - /// - /// Gets a value indicating whether this instance is an integer (n, 1) - /// - public bool IsInteger => this.Denominator == 1; - - /// - /// Gets a value indicating whether this instance is equal to negative infinity (-1, 0) - /// - public bool IsNegativeInfinity - { - get - { - if (this.Denominator != 0) - { - return false; - } - - return this.Numerator == -1; - } - } - - /// - /// Gets a value indicating whether this instance is equal to positive infinity (1, 0) - /// - public bool IsPositiveInfinity - { - get - { - if (this.Denominator != 0) - { - return false; - } - - return this.Numerator == 1; - } - } - - /// - /// Gets a value indicating whether this instance is equal to 0 (0, 1) - /// - public bool IsZero - { - get - { - if (this.Denominator != 1) - { - return false; - } - - return this.Numerator == 0; - } - } - - /// - public bool Equals(LongRational other) - { - if (this.Denominator == other.Denominator) - { - return this.Numerator == other.Numerator; - } - - if (this.Numerator == 0 && this.Denominator == 0) - { - return other.Numerator == 0 && other.Denominator == 0; - } - - if (other.Numerator == 0 && other.Denominator == 0) - { - return this.Numerator == 0 && this.Denominator == 0; - } - - return (this.Numerator * other.Denominator) == (this.Denominator * other.Numerator); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - return this.ToString(CultureInfo.InvariantCulture); - } - - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) - { - if (this.IsIndeterminate) - { - return "[ Indeterminate ]"; - } - - if (this.IsPositiveInfinity) - { - return "[ PositiveInfinity ]"; - } - - if (this.IsNegativeInfinity) - { - return "[ NegativeInfinity ]"; - } - - if (this.IsZero) - { - return "0"; - } - - if (this.IsInteger) - { - return this.Numerator.ToString(provider); - } - - StringBuilder sb = new StringBuilder(); - sb.Append(this.Numerator.ToString(provider)); - sb.Append("/"); - sb.Append(this.Denominator.ToString(provider)); - return sb.ToString(); - } - - /// - /// Finds the greatest common divisor of two values. - /// - /// The first value - /// The second value - /// The - private static long GreatestCommonDivisor(long left, long right) - { - return right == 0 ? left : GreatestCommonDivisor(right, left % right); - } - - /// - /// Simplifies the - /// - private void Simplify() - { - if (this.IsIndeterminate) - { - return; - } - - if (this.IsNegativeInfinity) - { - return; - } - - if (this.IsPositiveInfinity) - { - return; - } - - if (this.IsInteger) - { - return; - } - - if (this.IsZero) - { - return; - } - - if (this.Numerator == 0) - { - this.Denominator = 0; - return; - } - - if (this.Numerator == this.Denominator) - { - this.Numerator = 1; - this.Denominator = 1; - } - - long gcd = GreatestCommonDivisor(Math.Abs(this.Numerator), Math.Abs(this.Denominator)); - if (gcd > 1) - { - this.Numerator = this.Numerator / gcd; - this.Denominator = this.Denominator / gcd; - } - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(LongRational rational) - { - return ((rational.Numerator * 397) ^ rational.Denominator).GetHashCode(); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Point.cs b/src/ImageSharp46/Numerics/Point.cs deleted file mode 100644 index 3cd47659c..000000000 --- a/src/ImageSharp46/Numerics/Point.cs +++ /dev/null @@ -1,282 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.ComponentModel; - using System.Numerics; - using System.Runtime.CompilerServices; - - /// - /// Represents an ordered pair of integer x- and y-coordinates that defines a point in - /// a two-dimensional plane. - /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Point : IEquatable - { - /// - /// Represents a that has X and Y values set to zero. - /// - public static readonly Point Empty = default(Point); - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the point. - /// The vertical position of the point. - public Point(int x, int y) - : this() - { - this.X = x; - this.Y = y; - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector representing the width and height. - /// - public Point(Vector2 vector) - { - this.X = (int)Math.Round(vector.X); - this.Y = (int)Math.Round(vector.Y); - } - - /// - /// Gets or sets the x-coordinate of this . - /// - public int X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public int Y { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Computes the sum of adding two points. - /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point operator +(Point left, Point right) - { - return new Point(left.X + right.X, left.Y + right.Y); - } - - /// - /// Computes the difference left by subtracting one point from another. - /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point operator -(Point left, Point right) - { - return new Point(left.X - right.X, left.Y - right.Y); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Point left, Point right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Point left, Point right) - { - return !left.Equals(right); - } - - /// - /// Creates a rotation matrix for the given point and angle. - /// - /// The origin point to rotate around - /// Rotation in degrees - /// The rotation - public static Matrix3x2 CreateRotation(Point origin, float degrees) - { - float radians = ImageMaths.DegreesToRadians(degrees); - return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y)); - } - - /// - /// Rotates a point around a given a rotation matrix. - /// - /// The point to rotate - /// Rotation matrix used - /// The rotated - public static Point Rotate(Point point, Matrix3x2 rotation) - { - return new Point(Vector2.Transform(new Vector2(point.X, point.Y), rotation)); - } - - /// - /// Rotates a point around a given origin by the specified angle in degrees. - /// - /// The point to rotate - /// The center point to rotate around. - /// The angle in degrees. - /// The rotated - public static Point Rotate(Point point, Point origin, float degrees) - { - return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateRotation(origin, degrees))); - } - - /// - /// Creates a skew matrix for the given point and angle. - /// - /// The origin point to rotate around - /// The x-angle in degrees. - /// The y-angle in degrees. - /// The rotation - public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) - { - float radiansX = ImageMaths.DegreesToRadians(degreesX); - float radiansY = ImageMaths.DegreesToRadians(degreesY); - return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(origin.X, origin.Y)); - } - - /// - /// Skews a point using a given a skew matrix. - /// - /// The point to rotate - /// Rotation matrix used - /// The rotated - public static Point Skew(Point point, Matrix3x2 skew) - { - return new Point(Vector2.Transform(new Vector2(point.X, point.Y), skew)); - } - - /// - /// Skews a point around a given origin by the specified angles in degrees. - /// - /// The point to skew. - /// The center point to rotate around. - /// The x-angle in degrees. - /// The y-angle in degrees. - /// The skewed - public static Point Skew(Point point, Point origin, float degreesX, float degreesY) - { - return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateSkew(origin, degreesX, degreesY))); - } - - /// - /// Gets a representation for this . - /// - /// A representation for this object. - public Vector2 ToVector2() - { - return new Vector2(this.X, this.Y); - } - - /// - /// Translates this by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - public void Offset(int dx, int dy) - { - this.X += dx; - this.Y += dy; - } - - /// - /// Translates this by the specified amount. - /// - /// The used offset this . - public void Offset(Point p) - { - this.Offset(p.X, p.Y); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Point [ Empty ]"; - } - - return $"Point [ X={this.X}, Y={this.Y} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Point) - { - return this.Equals((Point)obj); - } - - return false; - } - - /// - public bool Equals(Point other) - { - return this.X == other.X && this.Y == other.Y; - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Point point) - { - return point.X ^ point.Y; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Rational.cs b/src/ImageSharp46/Numerics/Rational.cs deleted file mode 100644 index d219a5469..000000000 --- a/src/ImageSharp46/Numerics/Rational.cs +++ /dev/null @@ -1,189 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Globalization; - - /// - /// Represents a number that can be expressed as a fraction. - /// - /// - /// This is a very simplified implementation of a rational number designed for use with metadata only. - /// - public struct Rational : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The to create the rational from. - public Rational(uint value) - : this(value, 1) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - public Rational(uint numerator, uint denominator) - : this(numerator, denominator, true) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - /// Specified if the rational should be simplified. - public Rational(uint numerator, uint denominator, bool simplify) - { - LongRational rational = new LongRational(numerator, denominator, simplify); - - this.Numerator = (uint)rational.Numerator; - this.Denominator = (uint)rational.Denominator; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - public Rational(double value) - : this(value, false) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public Rational(double value, bool bestPrecision) - { - LongRational rational = new LongRational(Math.Abs(value), bestPrecision); - - this.Numerator = (uint)rational.Numerator; - this.Denominator = (uint)rational.Denominator; - } - - /// - /// Gets the numerator of a number. - /// - public uint Numerator { get; } - - /// - /// Gets the denominator of a number. - /// - public uint Denominator { get; } - - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator ==(Rational left, Rational right) - { - return Rational.Equals(left, right); - } - - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator !=(Rational left, Rational right) - { - return !Rational.Equals(left, right); - } - - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// - /// The . - /// - public static Rational FromDouble(double value) - { - return new Rational(value, false); - } - - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// Whether to use the best possible precision when parsing the value. - /// - /// The . - /// - public static Rational FromDouble(double value, bool bestPrecision) - { - return new Rational(value, bestPrecision); - } - - /// - public override bool Equals(object obj) - { - if (obj is Rational) - { - return this.Equals((Rational)obj); - } - - return false; - } - - /// - public bool Equals(Rational other) - { - LongRational left = new LongRational(this.Numerator, this.Denominator); - LongRational right = new LongRational(other.Numerator, other.Denominator); - - return left.Equals(right); - } - - /// - public override int GetHashCode() - { - LongRational self = new LongRational(this.Numerator, this.Denominator); - return self.GetHashCode(); - } - - /// - /// Converts a rational number to the nearest . - /// - /// - /// The . - /// - public double ToDouble() - { - return this.Numerator / (double)this.Denominator; - } - - /// - public override string ToString() - { - return this.ToString(CultureInfo.InvariantCulture); - } - - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) - { - LongRational rational = new LongRational(this.Numerator, this.Denominator); - return rational.ToString(provider); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Rectangle.cs b/src/ImageSharp46/Numerics/Rectangle.cs deleted file mode 100644 index fb623c2eb..000000000 --- a/src/ImageSharp46/Numerics/Rectangle.cs +++ /dev/null @@ -1,291 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Stores a set of four integers that represent the location and size of a rectangle. - /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Rectangle : IEquatable - { - /// - /// Represents a that has X, Y, Width, and Height values set to zero. - /// - public static readonly Rectangle Empty = default(Rectangle); - - /// - /// The backing vector for SIMD support. - /// - private Vector4 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the rectangle. - /// The vertical position of the rectangle. - /// The width of the rectangle. - /// The height of the rectangle. - public Rectangle(int x, int y, int width, int height) - { - this.backingVector = new Vector4(x, y, width, height); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The which specifies the rectangles point in a two-dimensional plane. - /// - /// - /// The which specifies the rectangles height and width. - /// - public Rectangle(Point point, Size size) - { - this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector. - public Rectangle(Vector4 vector) - { - this.backingVector = vector; - } - - /// - /// Gets or sets the x-coordinate of this . - /// - public int X - { - get - { - return (int)this.backingVector.X; - } - - set - { - this.backingVector.X = value; - } - } - - /// - /// Gets or sets the y-coordinate of this . - /// - public int Y - { - get - { - return (int)this.backingVector.Y; - } - - set - { - this.backingVector.Y = value; - } - } - - /// - /// Gets or sets the width of this . - /// - public int Width - { - get - { - return (int)this.backingVector.Z; - } - - set - { - this.backingVector.Z = value; - } - } - - /// - /// Gets or sets the height of this . - /// - public int Height - { - get - { - return (int)this.backingVector.W; - } - - set - { - this.backingVector.W = value; - } - } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Gets the y-coordinate of the top edge of this . - /// - public int Top => this.Y; - - /// - /// Gets the x-coordinate of the right edge of this . - /// - public int Right => this.X + this.Width; - - /// - /// Gets the y-coordinate of the bottom edge of this . - /// - public int Bottom => this.Y + this.Height; - - /// - /// Gets the x-coordinate of the left edge of this . - /// - public int Left => this.X; - - /// - /// Computes the sum of adding two rectangles. - /// - /// The rectangle on the left hand of the operand. - /// The rectangle on the right hand of the operand. - /// - /// The - /// - public static Rectangle operator +(Rectangle left, Rectangle right) - { - return new Rectangle(left.backingVector + right.backingVector); - } - - /// - /// Computes the difference left by subtracting one rectangle from another. - /// - /// The rectangle on the left hand of the operand. - /// The rectangle on the right hand of the operand. - /// - /// The - /// - public static Rectangle operator -(Rectangle left, Rectangle right) - { - return new Rectangle(left.backingVector - right.backingVector); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Rectangle left, Rectangle right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Rectangle left, Rectangle right) - { - return !left.Equals(right); - } - - /// - /// Determines if the specfied point is contained within the rectangular region defined by - /// this . - /// - /// The x-coordinate of the given point. - /// The y-coordinate of the given point. - /// The - public bool Contains(int x, int y) - { - // TODO: SIMD? - return this.X <= x - && x < this.Right - && this.Y <= y - && y < this.Bottom; - } - - /// - /// Returns the center point of the given - /// - /// The rectangle - /// - public static Point Center(Rectangle rectangle) - { - return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Rectangle [ Empty ]"; - } - - return - $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Rectangle) - { - return this.Equals((Rectangle)obj); - } - - return false; - } - - /// - public bool Equals(Rectangle other) - { - return this.backingVector.Equals(other.backingVector); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Rectangle rectangle) - { - return rectangle.backingVector.GetHashCode(); - } - } -} diff --git a/src/ImageSharp46/Numerics/SignedRational.cs b/src/ImageSharp46/Numerics/SignedRational.cs deleted file mode 100644 index bd2213dce..000000000 --- a/src/ImageSharp46/Numerics/SignedRational.cs +++ /dev/null @@ -1,189 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Globalization; - - /// - /// Represents a number that can be expressed as a fraction. - /// - /// - /// This is a very simplified implementation of a rational number designed for use with metadata only. - /// - public struct SignedRational : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The to create the rational from. - public SignedRational(int value) - : this(value, 1) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - public SignedRational(int numerator, int denominator) - : this(numerator, denominator, true) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - /// Specified if the rational should be simplified. - public SignedRational(int numerator, int denominator, bool simplify) - { - LongRational rational = new LongRational(numerator, denominator, simplify); - - this.Numerator = (int)rational.Numerator; - this.Denominator = (int)rational.Denominator; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - public SignedRational(double value) - : this(value, false) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public SignedRational(double value, bool bestPrecision) - { - LongRational rational = new LongRational(value, bestPrecision); - - this.Numerator = (int)rational.Numerator; - this.Denominator = (int)rational.Denominator; - } - - /// - /// Gets the numerator of a number. - /// - public int Numerator { get; } - - /// - /// Gets the denominator of a number. - /// - public int Denominator { get; } - - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator ==(SignedRational left, SignedRational right) - { - return SignedRational.Equals(left, right); - } - - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator !=(SignedRational left, SignedRational right) - { - return !SignedRational.Equals(left, right); - } - - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// - /// The . - /// - public static SignedRational FromDouble(double value) - { - return new SignedRational(value, false); - } - - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// Whether to use the best possible precision when parsing the value. - /// - /// The . - /// - public static SignedRational FromDouble(double value, bool bestPrecision) - { - return new SignedRational(value, bestPrecision); - } - - /// - public override bool Equals(object obj) - { - if (obj is SignedRational) - { - return this.Equals((SignedRational)obj); - } - - return false; - } - - /// - public bool Equals(SignedRational other) - { - LongRational left = new LongRational(this.Numerator, this.Denominator); - LongRational right = new LongRational(other.Numerator, other.Denominator); - - return left.Equals(right); - } - - /// - public override int GetHashCode() - { - LongRational self = new LongRational(this.Numerator, this.Denominator); - return self.GetHashCode(); - } - - /// - /// Converts a rational number to the nearest . - /// - /// - /// The . - /// - public double ToDouble() - { - return this.Numerator / (double)this.Denominator; - } - - /// - public override string ToString() - { - return this.ToString(CultureInfo.InvariantCulture); - } - - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) - { - LongRational rational = new LongRational(this.Numerator, this.Denominator); - return rational.ToString(provider); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Numerics/Size.cs b/src/ImageSharp46/Numerics/Size.cs deleted file mode 100644 index bae645ac8..000000000 --- a/src/ImageSharp46/Numerics/Size.cs +++ /dev/null @@ -1,166 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.ComponentModel; - using System.Runtime.CompilerServices; - - /// - /// Stores an ordered pair of integers, which specify a height and width. - /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Size : IEquatable - { - /// - /// Represents a that has Width and Height values set to zero. - /// - public static readonly Size Empty = default(Size); - - /// - /// Initializes a new instance of the struct. - /// - /// The width of the size. - /// The height of the size. - public Size(int width, int height) - { - this.Width = width; - this.Height = height; - } - - /// - /// Gets or sets the width of this . - /// - public int Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public int Height { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Computes the sum of adding two sizes. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size operator +(Size left, Size right) - { - return new Size(left.Width + right.Width, left.Height + right.Height); - } - - /// - /// Computes the difference left by subtracting one size from another. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size operator -(Size left, Size right) - { - return new Size(left.Width - right.Width, left.Height - right.Height); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Size left, Size right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Size left, Size right) - { - return !left.Equals(right); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Size [ Empty ]"; - } - - return $"Size [ Width={this.Width}, Height={this.Height} ]"; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) - { - if (obj is Size) - { - return this.Equals((Size)obj); - } - - return false; - } - - /// - public bool Equals(Size other) - { - return this.Width == other.Width && this.Height == other.Height; - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Size size) - { - return size.Width ^ size.Height; - } - } -} diff --git a/src/ImageSharp46/PixelAccessor.cs b/src/ImageSharp46/PixelAccessor.cs deleted file mode 100644 index 5c2ec5065..000000000 --- a/src/ImageSharp46/PixelAccessor.cs +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Runtime.CompilerServices; - - /// - /// An optimized pixel accessor for the class. - /// - public sealed unsafe class PixelAccessor : PixelAccessor - { - /// - /// Initializes a new instance of the class. - /// - /// The image to provide pixel access for. - public PixelAccessor(ImageBase image) - : base(image) - { - } - - /// - protected override void CopyFromZYX(PixelRow row, int targetY, int width) - { - byte* source = row.PixelBase; - byte* destination = this.GetRowPointer(targetY); - - for (int x = 0; x < width; x++) - { - Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | 255 << 24)); - - source += 3; - destination += 4; - } - } - - /// - protected override void CopyFromZYXW(PixelRow row, int targetY, int width) - { - byte* source = row.PixelBase; - byte* destination = this.GetRowPointer(targetY); - - for (int x = 0; x < width; x++) - { - Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | *(source + 3) << 24)); - - source += 4; - destination += 4; - } - } - - /// - protected override void CopyToZYX(PixelRow row, int sourceY, int width) - { - byte* source = this.GetRowPointer(sourceY); - byte* destination = row.PixelBase; - - for (int x = 0; x < width; x++) - { - *destination = *(source + 2); - *(destination + 1) = *(source + 1); - *(destination + 2) = *(source + 0); - - source += 4; - destination += 3; - } - } - - /// - protected override void CopyToZYXW(PixelRow row, int sourceY, int width) - { - byte* source = this.GetRowPointer(sourceY); - byte* destination = row.PixelBase; - - for (int x = 0; x < width; x++) - { - *destination = *(source + 2); - *(destination + 1) = *(source + 1); - *(destination + 2) = *(source + 0); - *(destination + 3) = *(source + 3); - - source += 4; - destination += 4; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifDataType.cs b/src/ImageSharp46/Profiles/Exif/ExifDataType.cs deleted file mode 100644 index f2d012c6c..000000000 --- a/src/ImageSharp46/Profiles/Exif/ExifDataType.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Specifies exif data types. - /// - public enum ExifDataType - { - /// - /// Unknown - /// - Unknown, - - /// - /// Byte - /// - Byte, - - /// - /// Ascii - /// - Ascii, - - /// - /// Short - /// - Short, - - /// - /// Long - /// - Long, - - /// - /// Rational - /// - Rational, - - /// - /// SignedByte - /// - SignedByte, - - /// - /// Undefined - /// - Undefined, - - /// - /// SignedShort - /// - SignedShort, - - /// - /// SignedLong - /// - SignedLong, - - /// - /// SignedRational - /// - SignedRational, - - /// - /// SingleFloat - /// - SingleFloat, - - /// - /// DoubleFloat - /// - DoubleFloat - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifParts.cs b/src/ImageSharp46/Profiles/Exif/ExifParts.cs deleted file mode 100644 index 880a43453..000000000 --- a/src/ImageSharp46/Profiles/Exif/ExifParts.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - /// - /// Specifies which parts will be written when the profile is added to an image. - /// - [Flags] - public enum ExifParts - { - /// - /// None - /// - None = 0, - - /// - /// IfdTags - /// - IfdTags = 1, - - /// - /// ExifTags - /// - ExifTags = 4, - - /// - /// GPSTags - /// - GPSTags = 8, - - /// - /// All - /// - All = IfdTags | ExifTags | GPSTags - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifProfile.cs b/src/ImageSharp46/Profiles/Exif/ExifProfile.cs deleted file mode 100644 index 7694ef7c6..000000000 --- a/src/ImageSharp46/Profiles/Exif/ExifProfile.cs +++ /dev/null @@ -1,239 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.IO; - - /// - /// Represents an EXIF profile providing access to the collection of values. - /// - public sealed class ExifProfile - { - /// - /// The byte array to read the EXIF profile from. - /// - private readonly byte[] data; - - /// - /// The collection of EXIF values - /// - private Collection values; - - /// - /// The list of invalid EXIF tags - /// - private List invalidTags; - - /// - /// The thumbnail offset position in the byte stream - /// - private int thumbnailOffset; - - /// - /// The thumbnail length in the byte stream - /// - private int thumbnailLength; - - /// - /// Initializes a new instance of the class. - /// - public ExifProfile() - : this((byte[])null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The byte array to read the EXIF profile from. - public ExifProfile(byte[] data) - { - this.Parts = ExifParts.All; - this.data = data; - this.invalidTags = new List(); - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another EXIF profile. - /// - /// The other EXIF profile, where the clone should be made from. - /// is null. - public ExifProfile(ExifProfile other) - { - Guard.NotNull(other, nameof(other)); - - this.Parts = other.Parts; - this.thumbnailLength = other.thumbnailLength; - this.thumbnailOffset = other.thumbnailOffset; - this.invalidTags = new List(other.invalidTags); - if (other.values != null) - { - this.values = new Collection(); - foreach (ExifValue value in other.values) - { - this.values.Add(new ExifValue(value)); - } - } - else - { - this.data = other.data; - } - } - - /// - /// Gets or sets which parts will be written when the profile is added to an image. - /// - public ExifParts Parts - { - get; - set; - } - - /// - /// Gets the tags that where found but contained an invalid value. - /// - public IEnumerable InvalidTags => this.invalidTags; - - /// - /// Gets the values of this EXIF profile. - /// - public IEnumerable Values - { - get - { - this.InitializeValues(); - return this.values; - } - } - - /// - /// Returns the thumbnail in the EXIF profile when available. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public Image CreateThumbnail() - where TColor : struct, IPackedPixel - where TPacked : struct - { - this.InitializeValues(); - - if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) - { - return null; - } - - if (this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) - { - return null; - } - - using (MemoryStream memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) - { - return new Image(memStream); - } - } - - /// - /// Returns the value with the specified tag. - /// - /// The tag of the EXIF value. - public ExifValue GetValue(ExifTag tag) - { - foreach (ExifValue exifValue in this.Values) - { - if (exifValue.Tag == tag) - { - return exifValue; - } - } - - return null; - } - - /// - /// Removes the value with the specified tag. - /// - /// The tag of the EXIF value. - public bool RemoveValue(ExifTag tag) - { - this.InitializeValues(); - - for (int i = 0; i < this.values.Count; i++) - { - if (this.values[i].Tag == tag) - { - this.values.RemoveAt(i); - return true; - } - } - - return false; - } - - /// - /// Sets the value of the specified tag. - /// - /// The tag of the EXIF value. - /// The value. - public void SetValue(ExifTag tag, object value) - { - foreach (ExifValue exifValue in this.Values) - { - if (exifValue.Tag == tag) - { - exifValue.Value = value; - return; - } - } - - ExifValue newExifValue = ExifValue.Create(tag, value); - this.values.Add(newExifValue); - } - - /// - /// Converts this instance to a byte array. - /// - /// The - public byte[] ToByteArray() - { - if (this.values == null) - { - return this.data; - } - - if (this.values.Count == 0) - { - return null; - } - - ExifWriter writer = new ExifWriter(this.values, this.Parts); - return writer.GetData(); - } - - private void InitializeValues() - { - if (this.values != null) - { - return; - } - - if (this.data == null) - { - this.values = new Collection(); - return; - } - - ExifReader reader = new ExifReader(); - this.values = reader.Read(this.data); - this.invalidTags = new List(reader.InvalidTags); - this.thumbnailOffset = (int)reader.ThumbnailOffset; - this.thumbnailLength = (int)reader.ThumbnailLength; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifReader.cs b/src/ImageSharp46/Profiles/Exif/ExifReader.cs deleted file mode 100644 index eef6f6f3a..000000000 --- a/src/ImageSharp46/Profiles/Exif/ExifReader.cs +++ /dev/null @@ -1,515 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageSharp -{ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Text; - - /// - /// Reads and parses EXIF data from a byte array - /// - internal sealed class ExifReader - { - private delegate TDataType ConverterMethod(byte[] data); - - private readonly Collection invalidTags = new Collection(); - private byte[] exifData; - private uint currentIndex; - private bool isLittleEndian; - private uint exifOffset; - private uint gpsOffset; - private uint startIndex; - - /// - /// Gets the thumbnail length in the byte stream - /// - public uint ThumbnailLength - { - get; - private set; - } - - /// - /// Gets the thumbnail offset position in the byte stream - /// - public uint ThumbnailOffset - { - get; - private set; - } - - /// - /// Gets the remaining length. - /// - private int RemainingLength - { - get - { - if (this.currentIndex >= this.exifData.Length) - { - return 0; - } - - return this.exifData.Length - (int)this.currentIndex; - } - } - - /// - /// Reads and returns the collection of EXIF values. - /// - /// The data. - /// - /// The . - /// - public Collection Read(byte[] data) - { - Collection result = new Collection(); - - this.exifData = data; - - if (this.GetString(4) == "Exif") - { - if (this.GetShort() != 0) - { - return result; - } - - this.startIndex = 6; - } - else - { - this.currentIndex = 0; - } - - this.isLittleEndian = this.GetString(2) == "II"; - - if (this.GetShort() != 0x002A) - { - return result; - } - - uint ifdOffset = this.GetLong(); - this.AddValues(result, ifdOffset); - - uint thumbnailOffset = this.GetLong(); - this.GetThumbnail(thumbnailOffset); - - if (this.exifOffset != 0) - { - this.AddValues(result, this.exifOffset); - } - - if (this.gpsOffset != 0) - { - this.AddValues(result, this.gpsOffset); - } - - return result; - } - - public IEnumerable InvalidTags => this.invalidTags; - - private void AddValues(Collection values, uint index) - { - this.currentIndex = this.startIndex + index; - ushort count = this.GetShort(); - - for (ushort i = 0; i < count; i++) - { - ExifValue value = this.CreateValue(); - if (value == null) - { - continue; - } - - bool duplicate = false; - foreach (ExifValue val in values) - { - if (val.Tag == value.Tag) - { - duplicate = true; - break; - } - } - - if (duplicate) - { - continue; - } - - if (value.Tag == ExifTag.SubIFDOffset) - { - if (value.DataType == ExifDataType.Long) - { - this.exifOffset = (uint)value.Value; - } - } - else if (value.Tag == ExifTag.GPSIFDOffset) - { - if (value.DataType == ExifDataType.Long) - { - this.gpsOffset = (uint)value.Value; - } - } - else - { - values.Add(value); - } - } - } - - private object ConvertValue(ExifDataType dataType, byte[] data, uint numberOfComponents) - { - if (data == null || data.Length == 0) - { - return null; - } - - switch (dataType) - { - case ExifDataType.Unknown: - return null; - case ExifDataType.Ascii: - return ToString(data); - case ExifDataType.Byte: - if (numberOfComponents == 1) - { - return ToByte(data); - } - - return data; - case ExifDataType.DoubleFloat: - if (numberOfComponents == 1) - { - return this.ToDouble(data); - } - - return ToArray(dataType, data, this.ToDouble); - case ExifDataType.Long: - if (numberOfComponents == 1) - { - return this.ToLong(data); - } - - return ToArray(dataType, data, this.ToLong); - case ExifDataType.Rational: - if (numberOfComponents == 1) - { - return this.ToRational(data); - } - - return ToArray(dataType, data, this.ToRational); - case ExifDataType.Short: - if (numberOfComponents == 1) - { - return this.ToShort(data); - } - - return ToArray(dataType, data, this.ToShort); - case ExifDataType.SignedByte: - if (numberOfComponents == 1) - { - return this.ToSignedByte(data); - } - - return ToArray(dataType, data, this.ToSignedByte); - case ExifDataType.SignedLong: - if (numberOfComponents == 1) - { - return this.ToSignedLong(data); - } - - return ToArray(dataType, data, this.ToSignedLong); - case ExifDataType.SignedRational: - if (numberOfComponents == 1) - { - return this.ToSignedRational(data); - } - - return ToArray(dataType, data, this.ToSignedRational); - case ExifDataType.SignedShort: - if (numberOfComponents == 1) - { - return this.ToSignedShort(data); - } - - return ToArray(dataType, data, this.ToSignedShort); - case ExifDataType.SingleFloat: - if (numberOfComponents == 1) - { - return this.ToSingle(data); - } - - return ToArray(dataType, data, this.ToSingle); - case ExifDataType.Undefined: - if (numberOfComponents == 1) - { - return ToByte(data); - } - - return data; - default: - throw new NotSupportedException(); - } - } - - private ExifValue CreateValue() - { - if (this.RemainingLength < 12) - { - return null; - } - - ExifTag tag = this.ToEnum(this.GetShort(), ExifTag.Unknown); - ExifDataType dataType = this.ToEnum(this.GetShort(), ExifDataType.Unknown); - object value; - - if (dataType == ExifDataType.Unknown) - { - return new ExifValue(tag, dataType, null, false); - } - - uint numberOfComponents = this.GetLong(); - - uint size = numberOfComponents * ExifValue.GetSize(dataType); - byte[] data = this.GetBytes(4); - - if (size > 4) - { - uint oldIndex = this.currentIndex; - this.currentIndex = this.ToLong(data) + this.startIndex; - if (this.RemainingLength < size) - { - this.invalidTags.Add(tag); - this.currentIndex = oldIndex; - return null; - } - - value = this.ConvertValue(dataType, this.GetBytes(size), numberOfComponents); - this.currentIndex = oldIndex; - } - else - { - value = this.ConvertValue(dataType, data, numberOfComponents); - } - - bool isArray = value != null && numberOfComponents > 1; - return new ExifValue(tag, dataType, value, isArray); - } - - private TEnum ToEnum(int value, TEnum defaultValue) - where TEnum : struct - { - TEnum enumValue = (TEnum)(object)value; - if (Enum.GetValues(typeof(TEnum)).Cast().Any(v => v.Equals(enumValue))) - { - return enumValue; - } - - return defaultValue; - } - - private byte[] GetBytes(uint length) - { - if (this.currentIndex + length > (uint)this.exifData.Length) - { - return null; - } - - byte[] data = new byte[length]; - Array.Copy(this.exifData, (int)this.currentIndex, data, 0, (int)length); - this.currentIndex += length; - - return data; - } - - private uint GetLong() - { - return this.ToLong(this.GetBytes(4)); - } - - private ushort GetShort() - { - return this.ToShort(this.GetBytes(2)); - } - - private string GetString(uint length) - { - return ToString(this.GetBytes(length)); - } - - private void GetThumbnail(uint offset) - { - Collection values = new Collection(); - this.AddValues(values, offset); - - foreach (ExifValue value in values) - { - if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long)) - { - this.ThumbnailOffset = (uint)value.Value + this.startIndex; - } - else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long) - { - this.ThumbnailLength = (uint)value.Value; - } - } - } - - private static TDataType[] ToArray(ExifDataType dataType, byte[] data, ConverterMethod converter) - { - int dataTypeSize = (int)ExifValue.GetSize(dataType); - int length = data.Length / dataTypeSize; - - TDataType[] result = new TDataType[length]; - byte[] buffer = new byte[dataTypeSize]; - - for (int i = 0; i < length; i++) - { - Array.Copy(data, i * dataTypeSize, buffer, 0, dataTypeSize); - - result.SetValue(converter(buffer), i); - } - - return result; - } - - private static byte ToByte(byte[] data) - { - return data[0]; - } - - private double ToDouble(byte[] data) - { - if (!this.ValidateArray(data, 8)) - { - return default(double); - } - - return BitConverter.ToDouble(data, 0); - } - - private uint ToLong(byte[] data) - { - if (!this.ValidateArray(data, 4)) - { - return default(uint); - } - - return BitConverter.ToUInt32(data, 0); - } - - private ushort ToShort(byte[] data) - { - if (!this.ValidateArray(data, 2)) - { - return default(ushort); - } - - return BitConverter.ToUInt16(data, 0); - } - - private float ToSingle(byte[] data) - { - if (!this.ValidateArray(data, 4)) - { - return default(float); - } - - return BitConverter.ToSingle(data, 0); - } - - private static string ToString(byte[] data) - { - string result = Encoding.UTF8.GetString(data, 0, data.Length); - int nullCharIndex = result.IndexOf('\0'); - if (nullCharIndex != -1) - { - result = result.Substring(0, nullCharIndex); - } - - return result; - } - - private Rational ToRational(byte[] data) - { - if (!this.ValidateArray(data, 8, 4)) - { - return default(Rational); - } - - uint numerator = BitConverter.ToUInt32(data, 0); - uint denominator = BitConverter.ToUInt32(data, 4); - - return new Rational(numerator, denominator, false); - } - - private sbyte ToSignedByte(byte[] data) - { - return unchecked((sbyte)data[0]); - } - - private int ToSignedLong(byte[] data) - { - if (!this.ValidateArray(data, 4)) - { - return default(int); - } - - return BitConverter.ToInt32(data, 0); - } - - private SignedRational ToSignedRational(byte[] data) - { - if (!this.ValidateArray(data, 8, 4)) - { - return default(SignedRational); - } - - int numerator = BitConverter.ToInt32(data, 0); - int denominator = BitConverter.ToInt32(data, 4); - - return new SignedRational(numerator, denominator, false); - } - - private short ToSignedShort(byte[] data) - { - if (!this.ValidateArray(data, 2)) - { - return default(short); - } - - return BitConverter.ToInt16(data, 0); - } - - private bool ValidateArray(byte[] data, int size) - { - return this.ValidateArray(data, size, size); - } - - private bool ValidateArray(byte[] data, int size, int stepSize) - { - if (data == null || data.Length < size) - { - return false; - } - - if (this.isLittleEndian == BitConverter.IsLittleEndian) - { - return true; - } - - for (int i = 0; i < data.Length; i += stepSize) - { - Array.Reverse(data, i, stepSize); - } - - return true; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifTag.cs b/src/ImageSharp46/Profiles/Exif/ExifTag.cs deleted file mode 100644 index 43f725f0c..000000000 --- a/src/ImageSharp46/Profiles/Exif/ExifTag.cs +++ /dev/null @@ -1,1547 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -// Descriptions from: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html - -namespace ImageSharp -{ - /// - /// All exif tags from the Exif standard 2.2 - /// - public enum ExifTag - { - /// - /// Unknown - /// - Unknown = 0xFFFF, - - /// - /// SubIFDOffset - /// - SubIFDOffset = 0x8769, - - /// - /// GPSIFDOffset - /// - GPSIFDOffset = 0x8825, - - /// - /// SubfileType - /// - [ExifTagDescription((uint)0, "Full-resolution Image")] - [ExifTagDescription((uint)1, "Reduced-resolution image")] - [ExifTagDescription((uint)2, "Single page of multi-page image")] - [ExifTagDescription((uint)3, "Single page of multi-page reduced-resolution image")] - [ExifTagDescription((uint)4, "Transparency mask")] - [ExifTagDescription((uint)5, "Transparency mask of reduced-resolution image")] - [ExifTagDescription((uint)6, "Transparency mask of multi-page image")] - [ExifTagDescription((uint)7, "Transparency mask of reduced-resolution multi-page image")] - [ExifTagDescription((uint)0x10001, "Alternate reduced-resolution image ")] - SubfileType = 0x00FE, - - /// - /// OldSubfileType - /// - [ExifTagDescription((ushort)1, "Full-resolution Image")] - [ExifTagDescription((ushort)2, "Reduced-resolution image")] - [ExifTagDescription((ushort)3, "Single page of multi-page image")] - OldSubfileType = 0x00FF, - - /// - /// ImageWidth - /// - ImageWidth = 0x0100, - - /// - /// ImageLength - /// - ImageLength = 0x0101, - - /// - /// BitsPerSample - /// - BitsPerSample = 0x0102, - - /// - /// Compression - /// - [ExifTagDescription((ushort)1, "Uncompressed")] - [ExifTagDescription((ushort)2, "CCITT 1D")] - [ExifTagDescription((ushort)3, "T4/Group 3 Fax")] - [ExifTagDescription((ushort)4, "T6/Group 4 Fax")] - [ExifTagDescription((ushort)5, "LZW")] - [ExifTagDescription((ushort)6, "JPEG (old-style)")] - [ExifTagDescription((ushort)7, "JPEG")] - [ExifTagDescription((ushort)8, "Adobe Deflate")] - [ExifTagDescription((ushort)9, "JBIG B&W")] - [ExifTagDescription((ushort)10, "JBIG Color")] - [ExifTagDescription((ushort)99, "JPEG")] - [ExifTagDescription((ushort)262, "Kodak 262")] - [ExifTagDescription((ushort)32766, "Next")] - [ExifTagDescription((ushort)32767, "Sony ARW Compressed")] - [ExifTagDescription((ushort)32769, "Packed RAW")] - [ExifTagDescription((ushort)32770, "Samsung SRW Compressed")] - [ExifTagDescription((ushort)32771, "CCIRLEW")] - [ExifTagDescription((ushort)32772, "Samsung SRW Compressed 2")] - [ExifTagDescription((ushort)32773, "PackBits")] - [ExifTagDescription((ushort)32809, "Thunderscan")] - [ExifTagDescription((ushort)32867, "Kodak KDC Compressed")] - [ExifTagDescription((ushort)32895, "IT8CTPAD")] - [ExifTagDescription((ushort)32896, "IT8LW")] - [ExifTagDescription((ushort)32897, "IT8MP")] - [ExifTagDescription((ushort)32898, "IT8BL")] - [ExifTagDescription((ushort)32908, "PixarFilm")] - [ExifTagDescription((ushort)32909, "PixarLog")] - [ExifTagDescription((ushort)32946, "Deflate")] - [ExifTagDescription((ushort)32947, "DCS")] - [ExifTagDescription((ushort)34661, "JBIG")] - [ExifTagDescription((ushort)34676, "SGILog")] - [ExifTagDescription((ushort)34677, "SGILog24")] - [ExifTagDescription((ushort)34712, "JPEG 2000")] - [ExifTagDescription((ushort)34713, "Nikon NEF Compressed")] - [ExifTagDescription((ushort)34715, "JBIG2 TIFF FX")] - [ExifTagDescription((ushort)34718, "Microsoft Document Imaging (MDI) Binary Level Codec")] - [ExifTagDescription((ushort)34719, "Microsoft Document Imaging (MDI) Progressive Transform Codec")] - [ExifTagDescription((ushort)34720, "Microsoft Document Imaging (MDI) Vector")] - [ExifTagDescription((ushort)34892, "Lossy JPEG")] - [ExifTagDescription((ushort)65000, "Kodak DCR Compressed")] - [ExifTagDescription((ushort)65535, "Pentax PEF Compressed")] - Compression = 0x0103, - - /// - /// PhotometricInterpretation - /// - [ExifTagDescription((ushort)0, "WhiteIsZero")] - [ExifTagDescription((ushort)1, "BlackIsZero")] - [ExifTagDescription((ushort)2, "RGB")] - [ExifTagDescription((ushort)3, "RGB Palette")] - [ExifTagDescription((ushort)4, "Transparency Mask")] - [ExifTagDescription((ushort)5, "CMYK")] - [ExifTagDescription((ushort)6, "YCbCr")] - [ExifTagDescription((ushort)8, "CIELab")] - [ExifTagDescription((ushort)9, "ICCLab")] - [ExifTagDescription((ushort)10, "TULab")] - [ExifTagDescription((ushort)32803, "Color Filter Array")] - [ExifTagDescription((ushort)32844, "Pixar LogL")] - [ExifTagDescription((ushort)32845, "Pixar LogLuv")] - [ExifTagDescription((ushort)34892, "Linear Raw")] - PhotometricInterpretation = 0x0106, - - /// - /// Thresholding - /// - [ExifTagDescription((ushort)1, "No dithering or halftoning")] - [ExifTagDescription((ushort)2, "Ordered dither or halftone")] - [ExifTagDescription((ushort)3, "Randomized dither")] - Thresholding = 0x0107, - - /// - /// CellWidth - /// - CellWidth = 0x0108, - - /// - /// CellLength - /// - CellLength = 0x0109, - - /// - /// FillOrder - /// - [ExifTagDescription((ushort)1, "Normal")] - [ExifTagDescription((ushort)2, "Reversed")] - FillOrder = 0x010A, - - /// - /// DocumentName - /// - DocumentName = 0x010D, - - /// - /// ImageDescription - /// - ImageDescription = 0x010E, - - /// - /// Make - /// - Make = 0x010F, - - /// - /// Model - /// - Model = 0x0110, - - /// - /// StripOffsets - /// - StripOffsets = 0x0111, - - /// - /// Orientation - /// - [ExifTagDescription((ushort)1, "Horizontal (normal)")] - [ExifTagDescription((ushort)2, "Mirror horizontal")] - [ExifTagDescription((ushort)3, "Rotate 180")] - [ExifTagDescription((ushort)4, "Mirror vertical")] - [ExifTagDescription((ushort)5, "Mirror horizontal and rotate 270 CW")] - [ExifTagDescription((ushort)6, "Rotate 90 CW")] - [ExifTagDescription((ushort)7, "Mirror horizontal and rotate 90 CW")] - [ExifTagDescription((ushort)8, "Rotate 270 CW")] - Orientation = 0x0112, - - /// - /// SamplesPerPixel - /// - SamplesPerPixel = 0x0115, - - /// - /// RowsPerStrip - /// - RowsPerStrip = 0x0116, - - /// - /// StripByteCounts - /// - StripByteCounts = 0x0117, - - /// - /// MinSampleValue - /// - MinSampleValue = 0x0118, - - /// - /// MaxSampleValue - /// - MaxSampleValue = 0x0119, - - /// - /// XResolution - /// - XResolution = 0x011A, - - /// - /// YResolution - /// - YResolution = 0x011B, - - /// - /// PlanarConfiguration - /// - [ExifTagDescription((ushort)1, "Chunky")] - [ExifTagDescription((ushort)2, "Planar")] - PlanarConfiguration = 0x011C, - - /// - /// PageName - /// - PageName = 0x011D, - - /// - /// XPosition - /// - XPosition = 0x011E, - - /// - /// YPosition - /// - YPosition = 0x011F, - - /// - /// FreeOffsets - /// - FreeOffsets = 0x0120, - - /// - /// FreeByteCounts - /// - FreeByteCounts = 0x0121, - - /// - /// GrayResponseUnit - /// - [ExifTagDescription((ushort)1, "0.1")] - [ExifTagDescription((ushort)2, "0.001")] - [ExifTagDescription((ushort)3, "0.0001")] - [ExifTagDescription((ushort)4, "1e-05")] - [ExifTagDescription((ushort)5, "1e-06")] - GrayResponseUnit = 0x0122, - - /// - /// GrayResponseCurve - /// - GrayResponseCurve = 0x0123, - - /// - /// T4Options - /// - [ExifTagDescription((uint)0, "2-Dimensional encoding")] - [ExifTagDescription((uint)1, "Uncompressed")] - [ExifTagDescription((uint)2, "Fill bits added")] - T4Options = 0x0124, - - /// - /// T6Options - /// - [ExifTagDescription((uint)1, "Uncompressed")] - T6Options = 0x0125, - - /// - /// ResolutionUnit - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - ResolutionUnit = 0x0128, - - /// - /// PageNumber - /// - PageNumber = 0x0129, - - /// - /// ColorResponseUnit - /// - ColorResponseUnit = 0x012C, - - /// - /// TransferFunction - /// - TransferFunction = 0x012D, - - /// - /// Software - /// - Software = 0x0131, - - /// - /// DateTime - /// - DateTime = 0x0132, - - /// - /// Artist - /// - Artist = 0x013B, - - /// - /// HostComputer - /// - HostComputer = 0x013C, - - /// - /// Predictor - /// - Predictor = 0x013D, - - /// - /// WhitePoint - /// - WhitePoint = 0x013E, - - /// - /// PrimaryChromaticities - /// - PrimaryChromaticities = 0x013F, - - /// - /// ColorMap - /// - ColorMap = 0x0140, - - /// - /// HalftoneHints - /// - HalftoneHints = 0x0141, - - /// - /// TileWidth - /// - TileWidth = 0x0142, - - /// - /// TileLength - /// - TileLength = 0x0143, - - /// - /// TileOffsets - /// - TileOffsets = 0x0144, - - /// - /// TileByteCounts - /// - TileByteCounts = 0x0145, - - /// - /// BadFaxLines - /// - BadFaxLines = 0x0146, - - /// - /// CleanFaxData - /// - [ExifTagDescription((uint)0, "Clean")] - [ExifTagDescription((uint)1, "Regenerated")] - [ExifTagDescription((uint)2, "Unclean")] - CleanFaxData = 0x0147, - - /// - /// ConsecutiveBadFaxLines - /// - ConsecutiveBadFaxLines = 0x0148, - - /// - /// InkSet - /// - [ExifTagDescription((ushort)1, "CMYK")] - [ExifTagDescription((ushort)2, "Not CMYK")] - InkSet = 0x014C, - - /// - /// InkNames - /// - InkNames = 0x014D, - - /// - /// NumberOfInks - /// - NumberOfInks = 0x014E, - - /// - /// DotRange - /// - DotRange = 0x0150, - - /// - /// TargetPrinter - /// - TargetPrinter = 0x0151, - - /// - /// ExtraSamples - /// - [ExifTagDescription((ushort)0, "Unspecified")] - [ExifTagDescription((ushort)1, "Associated Alpha")] - [ExifTagDescription((ushort)2, "Unassociated Alpha")] - ExtraSamples = 0x0152, - - /// - /// SampleFormat - /// - [ExifTagDescription((ushort)1, "Unsigned")] - [ExifTagDescription((ushort)2, "Signed")] - [ExifTagDescription((ushort)3, "Float")] - [ExifTagDescription((ushort)4, "Undefined")] - [ExifTagDescription((ushort)5, "Complex int")] - [ExifTagDescription((ushort)6, "Complex float")] - SampleFormat = 0x0153, - - /// - /// SMinSampleValue - /// - SMinSampleValue = 0x0154, - - /// - /// SMaxSampleValue - /// - SMaxSampleValue = 0x0155, - - /// - /// TransferRange - /// - TransferRange = 0x0156, - - /// - /// ClipPath - /// - ClipPath = 0x0157, - - /// - /// XClipPathUnits - /// - XClipPathUnits = 0x0158, - - /// - /// YClipPathUnits - /// - YClipPathUnits = 0x0159, - - /// - /// Indexed - /// - [ExifTagDescription((ushort)0, "Not indexed")] - [ExifTagDescription((ushort)1, "Indexed")] - Indexed = 0x015A, - - /// - /// JPEGTables - /// - JPEGTables = 0x015B, - - /// - /// OPIProxy - /// - [ExifTagDescription((ushort)0, "Higher resolution image does not exist")] - [ExifTagDescription((ushort)1, "Higher resolution image exists")] - OPIProxy = 0x015F, - - /// - /// ProfileType - /// - [ExifTagDescription((uint)0, "Unspecified")] - [ExifTagDescription((uint)1, "Group 3 FAX")] - ProfileType = 0x0191, - - /// - /// FaxProfile - /// - [ExifTagDescription((byte)0, "Unknown")] - [ExifTagDescription((byte)1, "Minimal B&W lossless, S")] - [ExifTagDescription((byte)2, "Extended B&W lossless, F")] - [ExifTagDescription((byte)3, "Lossless JBIG B&W, J")] - [ExifTagDescription((byte)4, "Lossy color and grayscale, C")] - [ExifTagDescription((byte)5, "Lossless color and grayscale, L")] - [ExifTagDescription((byte)6, "Mixed raster content, M")] - [ExifTagDescription((byte)7, "Profile T")] - [ExifTagDescription((byte)255, "Multi Profiles")] - FaxProfile = 0x0192, - - /// - /// CodingMethods - /// - [ExifTagDescription((ulong)0, "Unspecified compression")] - [ExifTagDescription((ulong)1, "Modified Huffman")] - [ExifTagDescription((ulong)2, "Modified Read")] - [ExifTagDescription((ulong)4, "Modified MR")] - [ExifTagDescription((ulong)8, "JBIG")] - [ExifTagDescription((ulong)16, "Baseline JPEG")] - [ExifTagDescription((ulong)32, "JBIG color")] - CodingMethods = 0x0193, - - /// - /// VersionYear - /// - VersionYear = 0x0194, - - /// - /// ModeNumber - /// - ModeNumber = 0x0195, - - /// - /// Decode - /// - Decode = 0x01B1, - - /// - /// DefaultImageColor - /// - DefaultImageColor = 0x01B2, - - /// - /// T82ptions - /// - T82ptions = 0x01B3, - - /// - /// JPEGProc - /// - [ExifTagDescription((ushort)1, "Baseline")] - [ExifTagDescription((ushort)14, "Lossless")] - JPEGProc = 0x0200, - - /// - /// JPEGInterchangeFormat - /// - JPEGInterchangeFormat = 0x0201, - - /// - /// JPEGInterchangeFormatLength - /// - JPEGInterchangeFormatLength = 0x0202, - - /// - /// JPEGRestartInterval - /// - JPEGRestartInterval = 0x0203, - - /// - /// JPEGLosslessPredictors - /// - JPEGLosslessPredictors = 0x0205, - - /// - /// JPEGPointTransforms - /// - JPEGPointTransforms = 0x0206, - - /// - /// JPEGQTables - /// - JPEGQTables = 0x0207, - - /// - /// JPEGDCTables - /// - JPEGDCTables = 0x0208, - - /// - /// JPEGACTables - /// - JPEGACTables = 0x0209, - - /// - /// YCbCrCoefficients - /// - YCbCrCoefficients = 0x0211, - - /// - /// YCbCrSubsampling - /// - YCbCrSubsampling = 0x0212, - - /// - /// YCbCrPositioning - /// - [ExifTagDescription((ushort)1, "Centered")] - [ExifTagDescription((ushort)2, "Co-sited")] - YCbCrPositioning = 0x0213, - - /// - /// ReferenceBlackWhite - /// - ReferenceBlackWhite = 0x0214, - - /// - /// StripRowCounts - /// - StripRowCounts = 0x022F, - - /// - /// XMP - /// - XMP = 0x02BC, - - /// - /// Rating - /// - Rating = 0x4746, - - /// - /// RatingPercent - /// - RatingPercent = 0x4749, - - /// - /// ImageID - /// - ImageID = 0x800D, - - /// - /// CFARepeatPatternDim - /// - CFARepeatPatternDim = 0x828D, - - /// - /// CFAPattern2 - /// - CFAPattern2 = 0x828E, - - /// - /// BatteryLevel - /// - BatteryLevel = 0x828F, - - /// - /// Copyright - /// - Copyright = 0x8298, - - /// - /// ExposureTime - /// - ExposureTime = 0x829A, - - /// - /// FNumber - /// - FNumber = 0x829D, - - /// - /// MDFileTag - /// - MDFileTag = 0x82A5, - - /// - /// MDScalePixel - /// - MDScalePixel = 0x82A6, - - /// - /// MDLabName - /// - MDLabName = 0x82A8, - - /// - /// MDSampleInfo - /// - MDSampleInfo = 0x82A9, - - /// - /// MDPrepDate - /// - MDPrepDate = 0x82AA, - - /// - /// MDPrepTime - /// - MDPrepTime = 0x82AB, - - /// - /// MDFileUnits - /// - MDFileUnits = 0x82AC, - - /// - /// PixelScale - /// - PixelScale = 0x830E, - - /// - /// IntergraphPacketData - /// - IntergraphPacketData = 0x847E, - - /// - /// IntergraphRegisters - /// - IntergraphRegisters = 0x847F, - - /// - /// IntergraphMatrix - /// - IntergraphMatrix = 0x8480, - - /// - /// ModelTiePoint - /// - ModelTiePoint = 0x8482, - - /// - /// SEMInfo - /// - SEMInfo = 0x8546, - - /// - /// ModelTransform - /// - ModelTransform = 0x85D8, - - /// - /// ImageLayer - /// - ImageLayer = 0x87AC, - - /// - /// ExposureProgram - /// - [ExifTagDescription((ushort)0, "Not Defined")] - [ExifTagDescription((ushort)1, "Manual")] - [ExifTagDescription((ushort)2, "Program AE")] - [ExifTagDescription((ushort)3, "Aperture-priority AE")] - [ExifTagDescription((ushort)4, "Shutter speed priority AE")] - [ExifTagDescription((ushort)5, "Creative (Slow speed)")] - [ExifTagDescription((ushort)6, "Action (High speed)")] - [ExifTagDescription((ushort)7, "Portrait")] - [ExifTagDescription((ushort)8, "Landscape")] - [ExifTagDescription((ushort)9, "Bulb")] - ExposureProgram = 0x8822, - - /// - /// SpectralSensitivity - /// - SpectralSensitivity = 0x8824, - - /// - /// ISOSpeedRatings - /// - ISOSpeedRatings = 0x8827, - - /// - /// OECF - /// - OECF = 0x8828, - - /// - /// Interlace - /// - Interlace = 0x8829, - - /// - /// TimeZoneOffset - /// - TimeZoneOffset = 0x882A, - - /// - /// SelfTimerMode - /// - SelfTimerMode = 0x882B, - - /// - /// SensitivityType - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Standard Output Sensitivity")] - [ExifTagDescription((ushort)2, "Recommended Exposure Index")] - [ExifTagDescription((ushort)3, "ISO Speed")] - [ExifTagDescription((ushort)4, "Standard Output Sensitivity and Recommended Exposure Index")] - [ExifTagDescription((ushort)5, "Standard Output Sensitivity and ISO Speed")] - [ExifTagDescription((ushort)6, "Recommended Exposure Index and ISO Speed")] - [ExifTagDescription((ushort)7, "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed")] - SensitivityType = 0x8830, - - /// - /// StandardOutputSensitivity - /// - StandardOutputSensitivity = 0x8831, - - /// - /// RecommendedExposureIndex - /// - RecommendedExposureIndex = 0x8832, - - /// - /// ISOSpeed - /// - ISOSpeed = 0x8833, - - /// - /// ISOSpeedLatitudeyyy - /// - ISOSpeedLatitudeyyy = 0x8834, - - /// - /// ISOSpeedLatitudezzz - /// - ISOSpeedLatitudezzz = 0x8835, - - /// - /// FaxRecvParams - /// - FaxRecvParams = 0x885C, - - /// - /// FaxSubaddress - /// - FaxSubaddress = 0x885D, - - /// - /// FaxRecvTime - /// - FaxRecvTime = 0x885E, - - /// - /// ExifVersion - /// - ExifVersion = 0x9000, - - /// - /// DateTimeOriginal - /// - DateTimeOriginal = 0x9003, - - /// - /// DateTimeDigitized - /// - DateTimeDigitized = 0x9004, - - /// - /// OffsetTime - /// - OffsetTime = 0x9010, - - /// - /// OffsetTimeOriginal - /// - OffsetTimeOriginal = 0x9011, - - /// - /// OffsetTimeDigitized - /// - OffsetTimeDigitized = 0x9012, - - /// - /// ComponentsConfiguration - /// - ComponentsConfiguration = 0x9101, - - /// - /// CompressedBitsPerPixel - /// - CompressedBitsPerPixel = 0x9102, - - /// - /// ShutterSpeedValue - /// - ShutterSpeedValue = 0x9201, - - /// - /// ApertureValue - /// - ApertureValue = 0x9202, - - /// - /// BrightnessValue - /// - BrightnessValue = 0x9203, - - /// - /// ExposureBiasValue - /// - ExposureBiasValue = 0x9204, - - /// - /// MaxApertureValue - /// - MaxApertureValue = 0x9205, - - /// - /// SubjectDistance - /// - SubjectDistance = 0x9206, - - /// - /// MeteringMode - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Average")] - [ExifTagDescription((ushort)2, "Center-weighted average")] - [ExifTagDescription((ushort)3, "Spot")] - [ExifTagDescription((ushort)4, "Multi-spot")] - [ExifTagDescription((ushort)5, "Multi-segment")] - [ExifTagDescription((ushort)6, "Partial")] - [ExifTagDescription((ushort)255, "Other")] - MeteringMode = 0x9207, - - /// - /// LightSource - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Daylight")] - [ExifTagDescription((ushort)2, "Fluorescent")] - [ExifTagDescription((ushort)3, "Tungsten (Incandescent)")] - [ExifTagDescription((ushort)4, "Flash")] - [ExifTagDescription((ushort)9, "Fine Weather")] - [ExifTagDescription((ushort)10, "Cloudy")] - [ExifTagDescription((ushort)11, "Shade")] - [ExifTagDescription((ushort)12, "Daylight Fluorescent")] - [ExifTagDescription((ushort)13, "Day White Fluorescent")] - [ExifTagDescription((ushort)14, "Cool White Fluorescent")] - [ExifTagDescription((ushort)15, "White Fluorescent")] - [ExifTagDescription((ushort)16, "Warm White Fluorescent")] - [ExifTagDescription((ushort)17, "Standard Light A")] - [ExifTagDescription((ushort)18, "Standard Light B")] - [ExifTagDescription((ushort)19, "Standard Light C")] - [ExifTagDescription((ushort)20, "D55")] - [ExifTagDescription((ushort)21, "D65")] - [ExifTagDescription((ushort)22, "D75")] - [ExifTagDescription((ushort)23, "D50")] - [ExifTagDescription((ushort)24, "ISO Studio Tungsten")] - [ExifTagDescription((ushort)255, "Other")] - LightSource = 0x9208, - - /// - /// Flash - /// - [ExifTagDescription((ushort)0, "No Flash")] - [ExifTagDescription((ushort)1, "Fired")] - [ExifTagDescription((ushort)5, "Fired, Return not detected")] - [ExifTagDescription((ushort)7, "Fired, Return detected")] - [ExifTagDescription((ushort)8, "On, Did not fire")] - [ExifTagDescription((ushort)9, "On, Fired")] - [ExifTagDescription((ushort)13, "On, Return not detected")] - [ExifTagDescription((ushort)15, "On, Return detected")] - [ExifTagDescription((ushort)16, "Off, Did not fire")] - [ExifTagDescription((ushort)20, "Off, Did not fire, Return not detected")] - [ExifTagDescription((ushort)24, "Auto, Did not fire")] - [ExifTagDescription((ushort)25, "Auto, Fired")] - [ExifTagDescription((ushort)29, "Auto, Fired, Return not detected")] - [ExifTagDescription((ushort)31, "Auto, Fired, Return detected")] - [ExifTagDescription((ushort)32, "No flash function")] - [ExifTagDescription((ushort)48, "Off, No flash function")] - [ExifTagDescription((ushort)65, "Fired, Red-eye reduction")] - [ExifTagDescription((ushort)69, "Fired, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)71, "Fired, Red-eye reduction, Return detected")] - [ExifTagDescription((ushort)73, "On, Red-eye reduction")] - [ExifTagDescription((ushort)77, "On, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)69, "On, Red-eye reduction, Return detected")] - [ExifTagDescription((ushort)80, "Off, Red-eye reduction")] - [ExifTagDescription((ushort)88, "Auto, Did not fire, Red-eye reduction")] - [ExifTagDescription((ushort)89, "Auto, Fired, Red-eye reduction")] - [ExifTagDescription((ushort)93, "Auto, Fired, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)95, "Auto, Fired, Red-eye reduction, Return detected")] - Flash = 0x9209, - - /// - /// FocalLength - /// - FocalLength = 0x920A, - - /// - /// FlashEnergy2 - /// - FlashEnergy2 = 0x920B, - - /// - /// SpatialFrequencyResponse2 - /// - SpatialFrequencyResponse2 = 0x920C, - - /// - /// Noise - /// - Noise = 0x920D, - - /// - /// FocalPlaneXResolution2 - /// - FocalPlaneXResolution2 = 0x920E, - - /// - /// FocalPlaneYResolution2 - /// - FocalPlaneYResolution2 = 0x920F, - - /// - /// FocalPlaneResolutionUnit2 - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - [ExifTagDescription((ushort)4, "Millimeter")] - [ExifTagDescription((ushort)5, "Micrometer")] - FocalPlaneResolutionUnit2 = 0x9210, - - /// - /// ImageNumber - /// - ImageNumber = 0x9211, - - /// - /// SecurityClassification - /// - [ExifTagDescription("C", "Confidential")] - [ExifTagDescription("R", "Restricted")] - [ExifTagDescription("S", "Secret")] - [ExifTagDescription("T", "Top Secret")] - [ExifTagDescription("U", "Unclassified")] - SecurityClassification = 0x9212, - - /// - /// ImageHistory - /// - ImageHistory = 0x9213, - - /// - /// SubjectArea - /// - SubjectArea = 0x9214, - - /// - /// ExposureIndex2 - /// - ExposureIndex2 = 0x9215, - - /// - /// TIFFEPStandardID - /// - TIFFEPStandardID = 0x9216, - - /// - /// SensingMethod - /// - [ExifTagDescription((ushort)1, "Not defined")] - [ExifTagDescription((ushort)2, "One-chip color area")] - [ExifTagDescription((ushort)3, "Two-chip color area")] - [ExifTagDescription((ushort)4, "Three-chip color area")] - [ExifTagDescription((ushort)5, "Color sequential area")] - [ExifTagDescription((ushort)7, "Trilinear")] - [ExifTagDescription((ushort)8, "Color sequential linear")] - SensingMethod2 = 0x9217, - - /// - /// MakerNote - /// - MakerNote = 0x927C, - - /// - /// UserComment - /// - UserComment = 0x9286, - - /// - /// SubsecTime - /// - SubsecTime = 0x9290, - - /// - /// SubsecTimeOriginal - /// - SubsecTimeOriginal = 0x9291, - - /// - /// SubsecTimeDigitized - /// - SubsecTimeDigitized = 0x9292, - - /// - /// ImageSourceData - /// - ImageSourceData = 0x935C, - - /// - /// AmbientTemperature - /// - AmbientTemperature = 0x9400, - - /// - /// Humidity - /// - Humidity = 0x9401, - - /// - /// Pressure - /// - Pressure = 0x9402, - - /// - /// WaterDepth - /// - WaterDepth = 0x9403, - - /// - /// Acceleration - /// - Acceleration = 0x9404, - - /// - /// CameraElevationAngle - /// - CameraElevationAngle = 0x9405, - - /// - /// XPTitle - /// - XPTitle = 0x9C9B, - - /// - /// XPComment - /// - XPComment = 0x9C9C, - - /// - /// XPAuthor - /// - XPAuthor = 0x9C9D, - - /// - /// XPKeywords - /// - XPKeywords = 0x9C9E, - - /// - /// XPSubject - /// - XPSubject = 0x9C9F, - - /// - /// FlashpixVersion - /// - FlashpixVersion = 0xA000, - - /// - /// ColorSpace - /// - [ExifTagDescription((ushort)1, "sRGB")] - [ExifTagDescription((ushort)2, "Adobe RGB")] - [ExifTagDescription((ushort)4093, "Wide Gamut RGB")] - [ExifTagDescription((ushort)65534, "ICC Profile")] - [ExifTagDescription((ushort)65535, "Uncalibrated")] - ColorSpace = 0xA001, - - /// - /// PixelXDimension - /// - PixelXDimension = 0xA002, - - /// - /// PixelYDimension - /// - PixelYDimension = 0xA003, - - /// - /// RelatedSoundFile - /// - RelatedSoundFile = 0xA004, - - /// - /// FlashEnergy - /// - FlashEnergy = 0xA20B, - - /// - /// SpatialFrequencyResponse - /// - SpatialFrequencyResponse = 0xA20C, - - /// - /// FocalPlaneXResolution - /// - FocalPlaneXResolution = 0xA20E, - - /// - /// FocalPlaneYResolution - /// - FocalPlaneYResolution = 0xA20F, - - /// - /// FocalPlaneResolutionUnit - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - [ExifTagDescription((ushort)4, "Millimeter")] - [ExifTagDescription((ushort)5, "Micrometer")] - FocalPlaneResolutionUnit = 0xA210, - - /// - /// SubjectLocation - /// - SubjectLocation = 0xA214, - - /// - /// ExposureIndex - /// - ExposureIndex = 0xA215, - - /// - /// SensingMethod - /// - [ExifTagDescription((ushort)1, "Not defined")] - [ExifTagDescription((ushort)2, "One-chip color area")] - [ExifTagDescription((ushort)3, "Two-chip color area")] - [ExifTagDescription((ushort)4, "Three-chip color area")] - [ExifTagDescription((ushort)5, "Color sequential area")] - [ExifTagDescription((ushort)7, "Trilinear")] - [ExifTagDescription((ushort)8, "Color sequential linear")] - SensingMethod = 0xA217, - - /// - /// FileSource - /// - FileSource = 0xA300, - - /// - /// SceneType - /// - SceneType = 0xA301, - - /// - /// CFAPattern - /// - CFAPattern = 0xA302, - - /// - /// CustomRendered - /// - [ExifTagDescription((ushort)1, "Normal")] - [ExifTagDescription((ushort)2, "Custom")] - CustomRendered = 0xA401, - - /// - /// ExposureMode - /// - [ExifTagDescription((ushort)0, "Auto")] - [ExifTagDescription((ushort)1, "Manual")] - [ExifTagDescription((ushort)2, "Auto bracket")] - ExposureMode = 0xA402, - - /// - /// WhiteBalance - /// - [ExifTagDescription((ushort)0, "Auto")] - [ExifTagDescription((ushort)1, "Manual")] - WhiteBalance = 0xA403, - - /// - /// DigitalZoomRatio - /// - DigitalZoomRatio = 0xA404, - - /// - /// FocalLengthIn35mmFilm - /// - FocalLengthIn35mmFilm = 0xA405, - - /// - /// SceneCaptureType - /// - [ExifTagDescription((ushort)0, "Standard")] - [ExifTagDescription((ushort)1, "Landscape")] - [ExifTagDescription((ushort)2, "Portrait")] - [ExifTagDescription((ushort)3, "Night")] - SceneCaptureType = 0xA406, - - /// - /// GainControl - /// - [ExifTagDescription((ushort)0, "None")] - [ExifTagDescription((ushort)1, "Low gain up")] - [ExifTagDescription((ushort)2, "High gain up")] - [ExifTagDescription((ushort)3, "Low gain down")] - [ExifTagDescription((ushort)4, "High gain down")] - GainControl = 0xA407, - - /// - /// Contrast - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Low")] - [ExifTagDescription((ushort)2, "High")] - Contrast = 0xA408, - - /// - /// Saturation - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Low")] - [ExifTagDescription((ushort)2, "High")] - Saturation = 0xA409, - - /// - /// Sharpness - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Soft")] - [ExifTagDescription((ushort)2, "Hard")] - Sharpness = 0xA40A, - - /// - /// DeviceSettingDescription - /// - DeviceSettingDescription = 0xA40B, - - /// - /// SubjectDistanceRange - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Macro")] - [ExifTagDescription((ushort)2, "Close")] - [ExifTagDescription((ushort)3, "Distant")] - SubjectDistanceRange = 0xA40C, - - /// - /// ImageUniqueID - /// - ImageUniqueID = 0xA420, - - /// - /// OwnerName - /// - OwnerName = 0xA430, - - /// - /// SerialNumber - /// - SerialNumber = 0xA431, - - /// - /// LensInfo - /// - LensInfo = 0xA432, - - /// - /// LensMake - /// - LensMake = 0xA433, - - /// - /// LensModel - /// - LensModel = 0xA434, - - /// - /// LensSerialNumber - /// - LensSerialNumber = 0xA435, - - /// - /// GDALMetadata - /// - GDALMetadata = 0xA480, - - /// - /// GDALNoData - /// - GDALNoData = 0xA481, - - /// - /// GPSVersionID - /// - GPSVersionID = 0x0000, - - /// - /// GPSLatitudeRef - /// - GPSLatitudeRef = 0x0001, - - /// - /// GPSLatitude - /// - GPSLatitude = 0x0002, - - /// - /// GPSLongitudeRef - /// - GPSLongitudeRef = 0x0003, - - /// - /// GPSLongitude - /// - GPSLongitude = 0x0004, - - /// - /// GPSAltitudeRef - /// - GPSAltitudeRef = 0x0005, - - /// - /// GPSAltitude - /// - GPSAltitude = 0x0006, - - /// - /// GPSTimestamp - /// - GPSTimestamp = 0x0007, - - /// - /// GPSSatellites - /// - GPSSatellites = 0x0008, - - /// - /// GPSStatus - /// - GPSStatus = 0x0009, - - /// - /// GPSMeasureMode - /// - GPSMeasureMode = 0x000A, - - /// - /// GPSDOP - /// - GPSDOP = 0x000B, - - /// - /// GPSSpeedRef - /// - GPSSpeedRef = 0x000C, - - /// - /// GPSSpeed - /// - GPSSpeed = 0x000D, - - /// - /// GPSTrackRef - /// - GPSTrackRef = 0x000E, - - /// - /// GPSTrack - /// - GPSTrack = 0x000F, - - /// - /// GPSImgDirectionRef - /// - GPSImgDirectionRef = 0x0010, - - /// - /// GPSImgDirection - /// - GPSImgDirection = 0x0011, - - /// - /// GPSMapDatum - /// - GPSMapDatum = 0x0012, - - /// - /// GPSDestLatitudeRef - /// - GPSDestLatitudeRef = 0x0013, - - /// - /// GPSDestLatitude - /// - GPSDestLatitude = 0x0014, - - /// - /// GPSDestLongitudeRef - /// - GPSDestLongitudeRef = 0x0015, - - /// - /// GPSDestLongitude - /// - GPSDestLongitude = 0x0016, - - /// - /// GPSDestBearingRef - /// - GPSDestBearingRef = 0x0017, - - /// - /// GPSDestBearing - /// - GPSDestBearing = 0x0018, - - /// - /// GPSDestDistanceRef - /// - GPSDestDistanceRef = 0x0019, - - /// - /// GPSDestDistance - /// - GPSDestDistance = 0x001A, - - /// - /// GPSProcessingMethod - /// - GPSProcessingMethod = 0x001B, - - /// - /// GPSAreaInformation - /// - GPSAreaInformation = 0x001C, - - /// - /// GPSDateStamp - /// - GPSDateStamp = 0x001D, - - /// - /// GPSDifferential - /// - GPSDifferential = 0x001E - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp46/Profiles/Exif/ExifTagDescriptionAttribute.cs deleted file mode 100644 index ca19a9776..000000000 --- a/src/ImageSharp46/Profiles/Exif/ExifTagDescriptionAttribute.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Reflection; - - /// - /// Class that provides a description for an ExifTag value. - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] - internal sealed class ExifTagDescriptionAttribute : Attribute - { - private object value; - private string description; - - /// - /// Initializes a new instance of the class. - /// - /// The value of the exif tag. - /// The description for the value of the exif tag. - public ExifTagDescriptionAttribute(object value, string description) - { - this.value = value; - this.description = description; - } - - public static string GetDescription(ExifTag tag, object value) - { - FieldInfo field = tag.GetType().GetTypeInfo().GetDeclaredField(tag.ToString()); - if (field == null) - { - return null; - } - - foreach (CustomAttributeData customAttribute in field.CustomAttributes) - { - object attributeValue = customAttribute.ConstructorArguments[0].Value; - - if (Equals(attributeValue, value)) - { - return (string)customAttribute.ConstructorArguments[1].Value; - } - } - - return null; - } - } -} diff --git a/src/ImageSharp46/Profiles/Exif/ExifValue.cs b/src/ImageSharp46/Profiles/Exif/ExifValue.cs deleted file mode 100644 index db62be4c2..000000000 --- a/src/ImageSharp46/Profiles/Exif/ExifValue.cs +++ /dev/null @@ -1,716 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Globalization; - using System.Text; - - /// - /// A value of the exif profile. - /// - public sealed class ExifValue : IEquatable - { - private object exifValue; - - /// - /// Initializes a new instance of the class - /// by making a copy from another exif value. - /// - /// The other exif value, where the clone should be made from. - /// is null. - public ExifValue(ExifValue other) - { - Guard.NotNull(other, nameof(other)); - - this.DataType = other.DataType; - this.IsArray = other.IsArray; - this.Tag = other.Tag; - - if (!other.IsArray) - { - this.exifValue = other.exifValue; - } - else - { - Array array = (Array)other.exifValue; - this.exifValue = array.Clone(); - } - } - - /// - /// Gets the data type of the exif value. - /// - public ExifDataType DataType - { - get; - } - - /// - /// Gets a value indicating whether the value is an array. - /// - public bool IsArray - { - get; - } - - /// - /// Gets the tag of the exif value. - /// - public ExifTag Tag - { - get; - } - - /// - /// Gets or sets the value. - /// - public object Value - { - get - { - return this.exifValue; - } - set - { - this.CheckValue(value); - this.exifValue = value; - } - } - - /// - /// Determines whether the specified ExifValue instances are considered equal. - /// - /// The first ExifValue to compare. - /// The second ExifValue to compare. - /// - public static bool operator ==(ExifValue left, ExifValue right) - { - return Equals(left, right); - } - - /// - /// Determines whether the specified ExifValue instances are not considered equal. - /// - /// The first ExifValue to compare. - /// The second ExifValue to compare. - /// - public static bool operator !=(ExifValue left, ExifValue right) - { - return !Equals(left, right); - } - - /// - /// Determines whether the specified object is equal to the current exif value. - /// - /// The object to compare this exif value with. - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) - { - return true; - } - - return this.Equals(obj as ExifValue); - } - - /// - /// Determines whether the specified exif value is equal to the current exif value. - /// - /// The exif value to compare this exif value with. - public bool Equals(ExifValue other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return - this.Tag == other.Tag && - this.DataType == other.DataType && - Equals(this.exifValue, other.exifValue); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.exifValue == null) - { - return null; - } - - if (this.DataType == ExifDataType.Ascii) - { - return (string)this.exifValue; - } - - if (!this.IsArray) - { - return this.ToString(this.exifValue); - } - - StringBuilder sb = new StringBuilder(); - foreach (object value in (Array)this.exifValue) - { - sb.Append(this.ToString(value)); - sb.Append(" "); - } - - return sb.ToString(); - } - - internal bool HasValue - { - get - { - if (this.exifValue == null) - { - return false; - } - - if (this.DataType == ExifDataType.Ascii) - { - return ((string)this.exifValue).Length > 0; - } - - return true; - } - } - - internal int Length - { - get - { - if (this.exifValue == null) - { - return 4; - } - - int size = (int)(GetSize(this.DataType) * this.NumberOfComponents); - - return size < 4 ? 4 : size; - } - } - - internal int NumberOfComponents - { - get - { - if (this.DataType == ExifDataType.Ascii) - { - return Encoding.UTF8.GetBytes((string)this.exifValue).Length; - } - - if (this.IsArray) - { - return ((Array)this.exifValue).Length; - } - - return 1; - } - } - - internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray) - { - this.Tag = tag; - this.DataType = dataType; - this.IsArray = isArray; - - if (dataType == ExifDataType.Ascii) - { - this.IsArray = false; - } - } - - internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) - : this(tag, dataType, isArray) - { - this.exifValue = value; - } - - internal static ExifValue Create(ExifTag tag, object value) - { - Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag"); - - ExifValue exifValue; - Type type = value?.GetType(); - if (type != null && type.IsArray) - { - type = type.GetElementType(); - } - - switch (tag) - { - case ExifTag.ImageDescription: - case ExifTag.Make: - case ExifTag.Model: - case ExifTag.Software: - case ExifTag.DateTime: - case ExifTag.Artist: - case ExifTag.HostComputer: - case ExifTag.Copyright: - case ExifTag.DocumentName: - case ExifTag.PageName: - case ExifTag.InkNames: - case ExifTag.TargetPrinter: - case ExifTag.ImageID: - case ExifTag.MDLabName: - case ExifTag.MDSampleInfo: - case ExifTag.MDPrepDate: - case ExifTag.MDPrepTime: - case ExifTag.MDFileUnits: - case ExifTag.SEMInfo: - case ExifTag.SpectralSensitivity: - case ExifTag.DateTimeOriginal: - case ExifTag.DateTimeDigitized: - case ExifTag.SubsecTime: - case ExifTag.SubsecTimeOriginal: - case ExifTag.SubsecTimeDigitized: - case ExifTag.FaxSubaddress: - case ExifTag.OffsetTime: - case ExifTag.OffsetTimeOriginal: - case ExifTag.OffsetTimeDigitized: - case ExifTag.SecurityClassification: - case ExifTag.ImageHistory: - case ExifTag.ImageUniqueID: - case ExifTag.OwnerName: - case ExifTag.SerialNumber: - case ExifTag.LensMake: - case ExifTag.LensModel: - case ExifTag.LensSerialNumber: - case ExifTag.GDALMetadata: - case ExifTag.GDALNoData: - case ExifTag.GPSLatitudeRef: - case ExifTag.GPSLongitudeRef: - case ExifTag.GPSSatellites: - case ExifTag.GPSStatus: - case ExifTag.GPSMeasureMode: - case ExifTag.GPSSpeedRef: - case ExifTag.GPSTrackRef: - case ExifTag.GPSImgDirectionRef: - case ExifTag.GPSMapDatum: - case ExifTag.GPSDestLatitudeRef: - case ExifTag.GPSDestLongitudeRef: - case ExifTag.GPSDestBearingRef: - case ExifTag.GPSDestDistanceRef: - case ExifTag.GPSDateStamp: - exifValue = new ExifValue(tag, ExifDataType.Ascii, true); - break; - - case ExifTag.ClipPath: - case ExifTag.VersionYear: - case ExifTag.XMP: - case ExifTag.CFAPattern2: - case ExifTag.TIFFEPStandardID: - case ExifTag.XPTitle: - case ExifTag.XPComment: - case ExifTag.XPAuthor: - case ExifTag.XPKeywords: - case ExifTag.XPSubject: - case ExifTag.GPSVersionID: - exifValue = new ExifValue(tag, ExifDataType.Byte, true); - break; - case ExifTag.FaxProfile: - case ExifTag.ModeNumber: - case ExifTag.GPSAltitudeRef: - exifValue = new ExifValue(tag, ExifDataType.Byte, false); - break; - - case ExifTag.FreeOffsets: - case ExifTag.FreeByteCounts: - case ExifTag.ColorResponseUnit: - case ExifTag.TileOffsets: - case ExifTag.SMinSampleValue: - case ExifTag.SMaxSampleValue: - case ExifTag.JPEGQTables: - case ExifTag.JPEGDCTables: - case ExifTag.JPEGACTables: - case ExifTag.StripRowCounts: - case ExifTag.IntergraphRegisters: - case ExifTag.TimeZoneOffset: - exifValue = new ExifValue(tag, ExifDataType.Long, true); - break; - case ExifTag.SubfileType: - case ExifTag.SubIFDOffset: - case ExifTag.GPSIFDOffset: - case ExifTag.T4Options: - case ExifTag.T6Options: - case ExifTag.XClipPathUnits: - case ExifTag.YClipPathUnits: - case ExifTag.ProfileType: - case ExifTag.CodingMethods: - case ExifTag.T82ptions: - case ExifTag.JPEGInterchangeFormat: - case ExifTag.JPEGInterchangeFormatLength: - case ExifTag.MDFileTag: - case ExifTag.StandardOutputSensitivity: - case ExifTag.RecommendedExposureIndex: - case ExifTag.ISOSpeed: - case ExifTag.ISOSpeedLatitudeyyy: - case ExifTag.ISOSpeedLatitudezzz: - case ExifTag.FaxRecvParams: - case ExifTag.FaxRecvTime: - case ExifTag.ImageNumber: - exifValue = new ExifValue(tag, ExifDataType.Long, false); - break; - - case ExifTag.WhitePoint: - case ExifTag.PrimaryChromaticities: - case ExifTag.YCbCrCoefficients: - case ExifTag.ReferenceBlackWhite: - case ExifTag.PixelScale: - case ExifTag.IntergraphMatrix: - case ExifTag.ModelTiePoint: - case ExifTag.ModelTransform: - case ExifTag.GPSLatitude: - case ExifTag.GPSLongitude: - case ExifTag.GPSTimestamp: - case ExifTag.GPSDestLatitude: - case ExifTag.GPSDestLongitude: - exifValue = new ExifValue(tag, ExifDataType.Rational, true); - break; - case ExifTag.XPosition: - case ExifTag.YPosition: - case ExifTag.XResolution: - case ExifTag.YResolution: - case ExifTag.BatteryLevel: - case ExifTag.ExposureTime: - case ExifTag.FNumber: - case ExifTag.MDScalePixel: - case ExifTag.CompressedBitsPerPixel: - case ExifTag.ApertureValue: - case ExifTag.MaxApertureValue: - case ExifTag.SubjectDistance: - case ExifTag.FocalLength: - case ExifTag.FlashEnergy2: - case ExifTag.FocalPlaneXResolution2: - case ExifTag.FocalPlaneYResolution2: - case ExifTag.ExposureIndex2: - case ExifTag.Humidity: - case ExifTag.Pressure: - case ExifTag.Acceleration: - case ExifTag.FlashEnergy: - case ExifTag.FocalPlaneXResolution: - case ExifTag.FocalPlaneYResolution: - case ExifTag.ExposureIndex: - case ExifTag.DigitalZoomRatio: - case ExifTag.LensInfo: - case ExifTag.GPSAltitude: - case ExifTag.GPSDOP: - case ExifTag.GPSSpeed: - case ExifTag.GPSTrack: - case ExifTag.GPSImgDirection: - case ExifTag.GPSDestBearing: - case ExifTag.GPSDestDistance: - exifValue = new ExifValue(tag, ExifDataType.Rational, false); - break; - - case ExifTag.BitsPerSample: - case ExifTag.MinSampleValue: - case ExifTag.MaxSampleValue: - case ExifTag.GrayResponseCurve: - case ExifTag.ColorMap: - case ExifTag.ExtraSamples: - case ExifTag.PageNumber: - case ExifTag.TransferFunction: - case ExifTag.Predictor: - case ExifTag.HalftoneHints: - case ExifTag.SampleFormat: - case ExifTag.TransferRange: - case ExifTag.DefaultImageColor: - case ExifTag.JPEGLosslessPredictors: - case ExifTag.JPEGPointTransforms: - case ExifTag.YCbCrSubsampling: - case ExifTag.CFARepeatPatternDim: - case ExifTag.IntergraphPacketData: - case ExifTag.ISOSpeedRatings: - case ExifTag.SubjectArea: - case ExifTag.SubjectLocation: - exifValue = new ExifValue(tag, ExifDataType.Short, true); - break; - case ExifTag.OldSubfileType: - case ExifTag.Compression: - case ExifTag.PhotometricInterpretation: - case ExifTag.Thresholding: - case ExifTag.CellWidth: - case ExifTag.CellLength: - case ExifTag.FillOrder: - case ExifTag.Orientation: - case ExifTag.SamplesPerPixel: - case ExifTag.PlanarConfiguration: - case ExifTag.GrayResponseUnit: - case ExifTag.ResolutionUnit: - case ExifTag.CleanFaxData: - case ExifTag.InkSet: - case ExifTag.NumberOfInks: - case ExifTag.DotRange: - case ExifTag.Indexed: - case ExifTag.OPIProxy: - case ExifTag.JPEGProc: - case ExifTag.JPEGRestartInterval: - case ExifTag.YCbCrPositioning: - case ExifTag.Rating: - case ExifTag.RatingPercent: - case ExifTag.ExposureProgram: - case ExifTag.Interlace: - case ExifTag.SelfTimerMode: - case ExifTag.SensitivityType: - case ExifTag.MeteringMode: - case ExifTag.LightSource: - case ExifTag.FocalPlaneResolutionUnit2: - case ExifTag.SensingMethod2: - case ExifTag.Flash: - case ExifTag.ColorSpace: - case ExifTag.FocalPlaneResolutionUnit: - case ExifTag.SensingMethod: - case ExifTag.CustomRendered: - case ExifTag.ExposureMode: - case ExifTag.WhiteBalance: - case ExifTag.FocalLengthIn35mmFilm: - case ExifTag.SceneCaptureType: - case ExifTag.GainControl: - case ExifTag.Contrast: - case ExifTag.Saturation: - case ExifTag.Sharpness: - case ExifTag.SubjectDistanceRange: - case ExifTag.GPSDifferential: - exifValue = new ExifValue(tag, ExifDataType.Short, false); - break; - - case ExifTag.Decode: - exifValue = new ExifValue(tag, ExifDataType.SignedRational, true); - break; - case ExifTag.ShutterSpeedValue: - case ExifTag.BrightnessValue: - case ExifTag.ExposureBiasValue: - case ExifTag.AmbientTemperature: - case ExifTag.WaterDepth: - case ExifTag.CameraElevationAngle: - exifValue = new ExifValue(tag, ExifDataType.SignedRational, false); - break; - - case ExifTag.JPEGTables: - case ExifTag.OECF: - case ExifTag.ExifVersion: - case ExifTag.ComponentsConfiguration: - case ExifTag.MakerNote: - case ExifTag.UserComment: - case ExifTag.FlashpixVersion: - case ExifTag.SpatialFrequencyResponse: - case ExifTag.SpatialFrequencyResponse2: - case ExifTag.Noise: - case ExifTag.CFAPattern: - case ExifTag.DeviceSettingDescription: - case ExifTag.ImageSourceData: - case ExifTag.GPSProcessingMethod: - case ExifTag.GPSAreaInformation: - exifValue = new ExifValue(tag, ExifDataType.Undefined, true); - break; - case ExifTag.FileSource: - case ExifTag.SceneType: - exifValue = new ExifValue(tag, ExifDataType.Undefined, false); - break; - - case ExifTag.StripOffsets: - case ExifTag.TileByteCounts: - case ExifTag.ImageLayer: - exifValue = CreateNumber(tag, type, true); - break; - case ExifTag.ImageWidth: - case ExifTag.ImageLength: - case ExifTag.TileWidth: - case ExifTag.TileLength: - case ExifTag.BadFaxLines: - case ExifTag.ConsecutiveBadFaxLines: - case ExifTag.PixelXDimension: - case ExifTag.PixelYDimension: - exifValue = CreateNumber(tag, type, false); - break; - - default: - throw new NotSupportedException(); - } - - exifValue.Value = value; - return exifValue; - } - - internal static uint GetSize(ExifDataType dataType) - { - switch (dataType) - { - case ExifDataType.Ascii: - case ExifDataType.Byte: - case ExifDataType.SignedByte: - case ExifDataType.Undefined: - return 1; - case ExifDataType.Short: - case ExifDataType.SignedShort: - return 2; - case ExifDataType.Long: - case ExifDataType.SignedLong: - case ExifDataType.SingleFloat: - return 4; - case ExifDataType.DoubleFloat: - case ExifDataType.Rational: - case ExifDataType.SignedRational: - return 8; - default: - throw new NotSupportedException(dataType.ToString()); - } - } - - private void CheckValue(object value) - { - if (value == null) - { - return; - } - - Type type = value.GetType(); - - if (this.DataType == ExifDataType.Ascii) - { - Guard.IsTrue(type == typeof(string), nameof(value), "Value should be a string."); - return; - } - - if (type.IsArray) - { - Guard.IsTrue(this.IsArray, nameof(value), "Value should not be an array."); - type = type.GetElementType(); - } - else - { - Guard.IsFalse(this.IsArray, nameof(value), "Value should not be an array."); - } - - switch (this.DataType) - { - case ExifDataType.Byte: - Guard.IsTrue(type == typeof(byte), nameof(value), $"Value should be a byte{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.DoubleFloat: - Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.Long: - Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.Rational: - Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.Short: - Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SignedByte: - Guard.IsTrue(type == typeof(sbyte), nameof(value), $"Value should be a signed byte{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SignedLong: - Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SignedRational: - Guard.IsTrue(type == typeof(SignedRational), nameof(value), $"Value should be a SignedRational{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SignedShort: - Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SingleFloat: - Guard.IsTrue(type == typeof(float), nameof(value), $"Value should be a float{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.Undefined: - Guard.IsTrue(type == typeof(byte), nameof(value), "Value should be a byte array."); - break; - default: - throw new NotSupportedException(); - } - } - - private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray) - { - if (type == null || type == typeof(ushort)) - { - return new ExifValue(tag, ExifDataType.Short, isArray); - } - - if (type == typeof(short)) - { - return new ExifValue(tag, ExifDataType.SignedShort, isArray); - } - - if (type == typeof(uint)) - { - return new ExifValue(tag, ExifDataType.Long, isArray); - } - - return new ExifValue(tag, ExifDataType.SignedLong, isArray); - } - - private string ToString(object value) - { - string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, value); - if (description != null) - { - return description; - } - - switch (this.DataType) - { - case ExifDataType.Ascii: - return (string)value; - case ExifDataType.Byte: - return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); - case ExifDataType.DoubleFloat: - return ((double)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.Long: - return ((uint)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.Rational: - return ((Rational)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.Short: - return ((ushort)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.SignedByte: - return ((sbyte)value).ToString("X2", CultureInfo.InvariantCulture); - case ExifDataType.SignedLong: - return ((int)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.SignedRational: - return ((Rational)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.SignedShort: - return ((short)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.SingleFloat: - return ((float)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.Undefined: - return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); - default: - throw new NotSupportedException(); - } - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(ExifValue exif) - { - int hashCode = exif.Tag.GetHashCode() ^ exif.DataType.GetHashCode(); - return hashCode ^ exif.exifValue?.GetHashCode() ?? hashCode; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/ExifWriter.cs b/src/ImageSharp46/Profiles/Exif/ExifWriter.cs deleted file mode 100644 index f7653b240..000000000 --- a/src/ImageSharp46/Profiles/Exif/ExifWriter.cs +++ /dev/null @@ -1,585 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Text; - - internal sealed class ExifWriter - { - private static readonly ExifTag[] IfdTags = new ExifTag[127] - { - ExifTag.SubfileType, - ExifTag.OldSubfileType, - ExifTag.ImageWidth, - ExifTag.ImageLength, - ExifTag.BitsPerSample, - ExifTag.Compression, - ExifTag.PhotometricInterpretation, - ExifTag.Thresholding, - ExifTag.CellWidth, - ExifTag.CellLength, - ExifTag.FillOrder, - ExifTag.DocumentName, - ExifTag.ImageDescription, - ExifTag.Make, - ExifTag.Model, - ExifTag.StripOffsets, - ExifTag.Orientation, - ExifTag.SamplesPerPixel, - ExifTag.RowsPerStrip, - ExifTag.StripByteCounts, - ExifTag.MinSampleValue, - ExifTag.MaxSampleValue, - ExifTag.XResolution, - ExifTag.YResolution, - ExifTag.PlanarConfiguration, - ExifTag.PageName, - ExifTag.XPosition, - ExifTag.YPosition, - ExifTag.FreeOffsets, - ExifTag.FreeByteCounts, - ExifTag.GrayResponseUnit, - ExifTag.GrayResponseCurve, - ExifTag.T4Options, - ExifTag.T6Options, - ExifTag.ResolutionUnit, - ExifTag.PageNumber, - ExifTag.ColorResponseUnit, - ExifTag.TransferFunction, - ExifTag.Software, - ExifTag.DateTime, - ExifTag.Artist, - ExifTag.HostComputer, - ExifTag.Predictor, - ExifTag.WhitePoint, - ExifTag.PrimaryChromaticities, - ExifTag.ColorMap, - ExifTag.HalftoneHints, - ExifTag.TileWidth, - ExifTag.TileLength, - ExifTag.TileOffsets, - ExifTag.TileByteCounts, - ExifTag.BadFaxLines, - ExifTag.CleanFaxData, - ExifTag.ConsecutiveBadFaxLines, - ExifTag.InkSet, - ExifTag.InkNames, - ExifTag.NumberOfInks, - ExifTag.DotRange, - ExifTag.TargetPrinter, - ExifTag.ExtraSamples, - ExifTag.SampleFormat, - ExifTag.SMinSampleValue, - ExifTag.SMaxSampleValue, - ExifTag.TransferRange, - ExifTag.ClipPath, - ExifTag.XClipPathUnits, - ExifTag.YClipPathUnits, - ExifTag.Indexed, - ExifTag.JPEGTables, - ExifTag.OPIProxy, - ExifTag.ProfileType, - ExifTag.FaxProfile, - ExifTag.CodingMethods, - ExifTag.VersionYear, - ExifTag.ModeNumber, - ExifTag.Decode, - ExifTag.DefaultImageColor, - ExifTag.T82ptions, - ExifTag.JPEGProc, - ExifTag.JPEGInterchangeFormat, - ExifTag.JPEGInterchangeFormatLength, - ExifTag.JPEGRestartInterval, - ExifTag.JPEGLosslessPredictors, - ExifTag.JPEGPointTransforms, - ExifTag.JPEGQTables, - ExifTag.JPEGDCTables, - ExifTag.JPEGACTables, - ExifTag.YCbCrCoefficients, - ExifTag.YCbCrSubsampling, - ExifTag.YCbCrSubsampling, - ExifTag.YCbCrPositioning, - ExifTag.ReferenceBlackWhite, - ExifTag.StripRowCounts, - ExifTag.XMP, - ExifTag.Rating, - ExifTag.RatingPercent, - ExifTag.ImageID, - ExifTag.CFARepeatPatternDim, - ExifTag.CFAPattern2, - ExifTag.BatteryLevel, - ExifTag.Copyright, - ExifTag.MDFileTag, - ExifTag.MDScalePixel, - ExifTag.MDLabName, - ExifTag.MDSampleInfo, - ExifTag.MDPrepDate, - ExifTag.MDPrepTime, - ExifTag.MDFileUnits, - ExifTag.PixelScale, - ExifTag.IntergraphPacketData, - ExifTag.IntergraphRegisters, - ExifTag.IntergraphMatrix, - ExifTag.ModelTiePoint, - ExifTag.SEMInfo, - ExifTag.ModelTransform, - ExifTag.ImageLayer, - ExifTag.FaxRecvParams, - ExifTag.FaxSubaddress, - ExifTag.FaxRecvTime, - ExifTag.ImageSourceData, - ExifTag.XPTitle, - ExifTag.XPComment, - ExifTag.XPAuthor, - ExifTag.XPKeywords, - ExifTag.XPSubject, - ExifTag.GDALMetadata, - ExifTag.GDALNoData - }; - - private static readonly ExifTag[] ExifTags = new ExifTag[92] - { - ExifTag.ExposureTime, - ExifTag.FNumber, - ExifTag.ExposureProgram, - ExifTag.SpectralSensitivity, - ExifTag.ISOSpeedRatings, - ExifTag.OECF, - ExifTag.Interlace, - ExifTag.TimeZoneOffset, - ExifTag.SelfTimerMode, - ExifTag.SensitivityType, - ExifTag.StandardOutputSensitivity, - ExifTag.RecommendedExposureIndex, - ExifTag.ISOSpeed, - ExifTag.ISOSpeedLatitudeyyy, - ExifTag.ISOSpeedLatitudezzz, - ExifTag.ExifVersion, - ExifTag.DateTimeOriginal, - ExifTag.DateTimeDigitized, - ExifTag.OffsetTime, - ExifTag.OffsetTimeOriginal, - ExifTag.OffsetTimeDigitized, - ExifTag.ComponentsConfiguration, - ExifTag.CompressedBitsPerPixel, - ExifTag.ShutterSpeedValue, - ExifTag.ApertureValue, - ExifTag.BrightnessValue, - ExifTag.ExposureBiasValue, - ExifTag.MaxApertureValue, - ExifTag.SubjectDistance, - ExifTag.MeteringMode, - ExifTag.LightSource, - ExifTag.Flash, - ExifTag.FocalLength, - ExifTag.FlashEnergy2, - ExifTag.SpatialFrequencyResponse2, - ExifTag.Noise, - ExifTag.FocalPlaneXResolution2, - ExifTag.FocalPlaneYResolution2, - ExifTag.FocalPlaneResolutionUnit2, - ExifTag.ImageNumber, - ExifTag.SecurityClassification, - ExifTag.ImageHistory, - ExifTag.SubjectArea, - ExifTag.ExposureIndex2, - ExifTag.TIFFEPStandardID, - ExifTag.SensingMethod2, - ExifTag.MakerNote, - ExifTag.UserComment, - ExifTag.SubsecTime, - ExifTag.SubsecTimeOriginal, - ExifTag.SubsecTimeDigitized, - ExifTag.AmbientTemperature, - ExifTag.Humidity, - ExifTag.Pressure, - ExifTag.WaterDepth, - ExifTag.Acceleration, - ExifTag.CameraElevationAngle, - ExifTag.FlashpixVersion, - ExifTag.ColorSpace, - ExifTag.PixelXDimension, - ExifTag.PixelYDimension, - ExifTag.RelatedSoundFile, - ExifTag.FlashEnergy, - ExifTag.SpatialFrequencyResponse, - ExifTag.FocalPlaneXResolution, - ExifTag.FocalPlaneYResolution, - ExifTag.FocalPlaneResolutionUnit, - ExifTag.SubjectLocation, - ExifTag.ExposureIndex, - ExifTag.SensingMethod, - ExifTag.FileSource, - ExifTag.SceneType, - ExifTag.CFAPattern, - ExifTag.CustomRendered, - ExifTag.ExposureMode, - ExifTag.WhiteBalance, - ExifTag.DigitalZoomRatio, - ExifTag.FocalLengthIn35mmFilm, - ExifTag.SceneCaptureType, - ExifTag.GainControl, - ExifTag.Contrast, - ExifTag.Saturation, - ExifTag.Sharpness, - ExifTag.DeviceSettingDescription, - ExifTag.SubjectDistanceRange, - ExifTag.ImageUniqueID, - ExifTag.OwnerName, - ExifTag.SerialNumber, - ExifTag.LensInfo, - ExifTag.LensMake, - ExifTag.LensModel, - ExifTag.LensSerialNumber - }; - - private static readonly ExifTag[] GPSTags = new ExifTag[31] - { - ExifTag.GPSVersionID, - ExifTag.GPSLatitudeRef, - ExifTag.GPSLatitude, - ExifTag.GPSLongitudeRef, - ExifTag.GPSLongitude, - ExifTag.GPSAltitudeRef, - ExifTag.GPSAltitude, - ExifTag.GPSTimestamp, - ExifTag.GPSSatellites, - ExifTag.GPSStatus, - ExifTag.GPSMeasureMode, - ExifTag.GPSDOP, - ExifTag.GPSSpeedRef, - ExifTag.GPSSpeed, - ExifTag.GPSTrackRef, - ExifTag.GPSTrack, - ExifTag.GPSImgDirectionRef, - ExifTag.GPSImgDirection, - ExifTag.GPSMapDatum, - ExifTag.GPSDestLatitudeRef, - ExifTag.GPSDestLatitude, - ExifTag.GPSDestLongitudeRef, - ExifTag.GPSDestLongitude, - ExifTag.GPSDestBearingRef, - ExifTag.GPSDestBearing, - ExifTag.GPSDestDistanceRef, - ExifTag.GPSDestDistance, - ExifTag.GPSProcessingMethod, - ExifTag.GPSAreaInformation, - ExifTag.GPSDateStamp, - ExifTag.GPSDifferential - }; - - private const int StartIndex = 6; - - private ExifParts allowedParts; - private Collection values; - private Collection dataOffsets; - private Collection ifdIndexes; - private Collection exifIndexes; - private Collection gpsIndexes; - - public ExifWriter(Collection values, ExifParts allowedParts) - { - this.values = values; - this.allowedParts = allowedParts; - this.ifdIndexes = this.GetIndexes(ExifParts.IfdTags, IfdTags); - this.exifIndexes = this.GetIndexes(ExifParts.ExifTags, ExifTags); - this.gpsIndexes = this.GetIndexes(ExifParts.GPSTags, GPSTags); - } - - public byte[] GetData() - { - uint length = 0; - int exifIndex = -1; - int gpsIndex = -1; - - if (this.exifIndexes.Count > 0) - { - exifIndex = (int)this.GetIndex(this.ifdIndexes, ExifTag.SubIFDOffset); - } - - if (this.gpsIndexes.Count > 0) - { - gpsIndex = (int)this.GetIndex(this.ifdIndexes, ExifTag.GPSIFDOffset); - } - - uint ifdLength = 2 + this.GetLength(this.ifdIndexes) + 4; - uint exifLength = this.GetLength(this.exifIndexes); - uint gpsLength = this.GetLength(this.gpsIndexes); - - if (exifLength > 0) - { - exifLength += 2; - } - - if (gpsLength > 0) - { - gpsLength += 2; - } - - length = ifdLength + exifLength + gpsLength; - - if (length == 6) - { - return null; - } - - length += 10 + 4 + 2; - - byte[] result = new byte[length]; - result[0] = (byte)'E'; - result[1] = (byte)'x'; - result[2] = (byte)'i'; - result[3] = (byte)'f'; - result[4] = 0x00; - result[5] = 0x00; - result[6] = (byte)'I'; - result[7] = (byte)'I'; - result[8] = 0x2A; - result[9] = 0x00; - - int i = 10; - uint ifdOffset = ((uint)i - StartIndex) + 4; - uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; - - if (exifLength > 0) - { - this.values[exifIndex].Value = ifdOffset + ifdLength; - } - - if (gpsLength > 0) - { - this.values[gpsIndex].Value = ifdOffset + ifdLength + exifLength; - } - - i = Write(BitConverter.GetBytes(ifdOffset), result, i); - i = this.WriteHeaders(this.ifdIndexes, result, i); - i = Write(BitConverter.GetBytes(thumbnailOffset), result, i); - i = this.WriteData(this.ifdIndexes, result, i); - - if (exifLength > 0) - { - i = this.WriteHeaders(this.exifIndexes, result, i); - i = this.WriteData(this.exifIndexes, result, i); - } - - if (gpsLength > 0) - { - i = this.WriteHeaders(this.gpsIndexes, result, i); - i = this.WriteData(this.gpsIndexes, result, i); - } - - Write(BitConverter.GetBytes((ushort)0), result, i); - - return result; - } - - private int GetIndex(Collection indexes, ExifTag tag) - { - foreach (int index in indexes) - { - if (this.values[index].Tag == tag) - { - return index; - } - } - - int newIndex = this.values.Count; - indexes.Add(newIndex); - this.values.Add(ExifValue.Create(tag, null)); - return newIndex; - } - - private Collection GetIndexes(ExifParts part, ExifTag[] tags) - { - if (((int)this.allowedParts & (int)part) == 0) - { - return new Collection(); - } - - Collection result = new Collection(); - for (int i = 0; i < this.values.Count; i++) - { - ExifValue value = this.values[i]; - - if (!value.HasValue) - { - continue; - } - - int index = Array.IndexOf(tags, value.Tag); - if (index > -1) - { - result.Add(i); - } - } - - return result; - } - - private uint GetLength(IEnumerable indexes) - { - uint length = 0; - - foreach (int index in indexes) - { - uint valueLength = (uint)this.values[index].Length; - - if (valueLength > 4) - { - length += 12 + valueLength; - } - else - { - length += 12; - } - } - - return length; - } - - private static int Write(byte[] source, byte[] destination, int offset) - { - Buffer.BlockCopy(source, 0, destination, offset, source.Length); - - return offset + source.Length; - } - - private int WriteArray(ExifValue value, byte[] destination, int offset) - { - if (value.DataType == ExifDataType.Ascii) - { - return this.WriteValue(ExifDataType.Ascii, value.Value, destination, offset); - } - - int newOffset = offset; - foreach (object obj in (Array)value.Value) - { - newOffset = this.WriteValue(value.DataType, obj, destination, newOffset); - } - - return newOffset; - } - - private int WriteData(Collection indexes, byte[] destination, int offset) - { - if (this.dataOffsets.Count == 0) - { - return offset; - } - - int newOffset = offset; - - int i = 0; - foreach (int index in indexes) - { - ExifValue value = this.values[index]; - if (value.Length > 4) - { - Write(BitConverter.GetBytes(newOffset - StartIndex), destination, this.dataOffsets[i++]); - newOffset = this.WriteValue(value, destination, newOffset); - } - } - - return newOffset; - } - - private int WriteHeaders(Collection indexes, byte[] destination, int offset) - { - this.dataOffsets = new Collection(); - - int newOffset = Write(BitConverter.GetBytes((ushort)indexes.Count), destination, offset); - - if (indexes.Count == 0) - { - return newOffset; - } - - foreach (int index in indexes) - { - ExifValue value = this.values[index]; - newOffset = Write(BitConverter.GetBytes((ushort)value.Tag), destination, newOffset); - newOffset = Write(BitConverter.GetBytes((ushort)value.DataType), destination, newOffset); - newOffset = Write(BitConverter.GetBytes((uint)value.NumberOfComponents), destination, newOffset); - - if (value.Length > 4) - { - this.dataOffsets.Add(newOffset); - } - else - { - this.WriteValue(value, destination, newOffset); - } - - newOffset += 4; - } - - return newOffset; - } - - private int WriteRational(Rational value, byte[] destination, int offset) - { - Write(BitConverter.GetBytes(value.Numerator), destination, offset); - Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4); - - return offset + 8; - } - - private int WriteSignedRational(SignedRational value, byte[] destination, int offset) - { - Write(BitConverter.GetBytes(value.Numerator), destination, offset); - Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4); - - return offset + 8; - } - - private int WriteValue(ExifDataType dataType, object value, byte[] destination, int offset) - { - switch (dataType) - { - case ExifDataType.Ascii: - return Write(Encoding.UTF8.GetBytes((string)value), destination, offset); - case ExifDataType.Byte: - case ExifDataType.Undefined: - destination[offset] = (byte)value; - return offset + 1; - case ExifDataType.DoubleFloat: - return Write(BitConverter.GetBytes((double)value), destination, offset); - case ExifDataType.Short: - return Write(BitConverter.GetBytes((ushort)value), destination, offset); - case ExifDataType.Long: - return Write(BitConverter.GetBytes((uint)value), destination, offset); - case ExifDataType.Rational: - return this.WriteRational((Rational)value, destination, offset); - case ExifDataType.SignedByte: - destination[offset] = unchecked((byte)((sbyte)value)); - return offset + 1; - case ExifDataType.SignedLong: - return Write(BitConverter.GetBytes((int)value), destination, offset); - case ExifDataType.SignedShort: - return Write(BitConverter.GetBytes((short)value), destination, offset); - case ExifDataType.SignedRational: - return this.WriteSignedRational((SignedRational)value, destination, offset); - case ExifDataType.SingleFloat: - return Write(BitConverter.GetBytes((float)value), destination, offset); - default: - throw new NotImplementedException(); - } - } - - private int WriteValue(ExifValue value, byte[] destination, int offset) - { - if (value.IsArray && value.DataType != ExifDataType.Ascii) - { - return this.WriteArray(value, destination, offset); - } - - return this.WriteValue(value.DataType, value.Value, destination, offset); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Profiles/Exif/README.md b/src/ImageSharp46/Profiles/Exif/README.md deleted file mode 100644 index b6e27b70c..000000000 --- a/src/ImageSharp46/Profiles/Exif/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Adapted from Magick.NET: - -https://github.com/dlemstra/Magick.NET/tree/784e23b1f5c824fc03d4b95d3387b3efe1ed510b/Magick.NET/Core/Profiles/Exif \ No newline at end of file diff --git a/src/ImageSharp46/ProgressEventArgs.cs b/src/ImageSharp46/ProgressEventArgs.cs deleted file mode 100644 index 585dd1b63..000000000 --- a/src/ImageSharp46/ProgressEventArgs.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Contains event data related to the progress made processing an image. - /// - public class ProgressEventArgs : System.EventArgs - { - /// - /// Gets or sets the number of rows processed. - /// - public int RowsProcessed { get; set; } - - /// - /// Gets or sets the total number of rows. - /// - public int TotalRows { get; set; } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Properties/AssemblyInfo.cs b/src/ImageSharp46/Properties/AssemblyInfo.cs deleted file mode 100644 index ec405e1d0..000000000 --- a/src/ImageSharp46/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ImageSharp")] -[assembly: AssemblyDescription("A cross-platform library for processing of image files; written in C#")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("James Jackson-South")] -[assembly: AssemblyProduct("ImageSharp")] -[assembly: AssemblyCopyright("Copyright (c) James Jackson-South and contributors.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0.0")] - -// Ensure the internals can be tested. -[assembly: InternalsVisibleTo("ImageSharp.Benchmarks")] -[assembly: InternalsVisibleTo("ImageSharp.Tests")] diff --git a/src/ImageSharp46/Quantizers/IQuantizer.cs b/src/ImageSharp46/Quantizers/IQuantizer.cs deleted file mode 100644 index 8b92af125..000000000 --- a/src/ImageSharp46/Quantizers/IQuantizer.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Quantizers -{ - /// - /// Provides methods for allowing quantization of images pixels. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public interface IQuantizer : IQuantizer - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Quantize an image and return the resulting output pixels. - /// - /// The image to quantize. - /// The maximum number of colors to return. - /// - /// A representing a quantized version of the image pixels. - /// - QuantizedImage Quantize(ImageBase image, int maxColors); - } - - /// - /// Provides methods for allowing quantization of images pixels. - /// - public interface IQuantizer - { - } -} diff --git a/src/ImageSharp46/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp46/Quantizers/Octree/OctreeQuantizer.cs deleted file mode 100644 index 5e06a721e..000000000 --- a/src/ImageSharp46/Quantizers/Octree/OctreeQuantizer.cs +++ /dev/null @@ -1,518 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Quantizers -{ - using System; - using System.Collections.Generic; - - /// - /// Encapsulates methods to calculate the colour palette if an image using an Octree pattern. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - public sealed class OctreeQuantizer : Quantizer - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Stores the tree - /// - private Octree octree; - - /// - /// Maximum allowed color depth - /// - private int colors; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, - /// the second pass quantizes a color based on the nodes in the tree - /// - public OctreeQuantizer() - : base(false) - { - } - - /// - public override QuantizedImage Quantize(ImageBase image, int maxColors) - { - this.colors = maxColors.Clamp(1, 256); - - if (this.octree == null) - { - // Construct the Octree - this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors)); - } - - return base.Quantize(image, maxColors); - } - - /// - /// Process the pixel in the first pass of the algorithm - /// - /// - /// The pixel to quantize - /// - /// - /// This function need only be overridden if your quantize algorithm needs two passes, - /// such as an Octree quantizer. - /// - protected override void InitialQuantizePixel(TColor pixel) - { - // Add the color to the Octree - this.octree.AddColor(pixel); - } - - /// - /// Override this to process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - protected override byte QuantizePixel(TColor pixel) - { - return (byte)this.octree.GetPaletteIndex(pixel); - } - - /// - /// Retrieve the palette for the quantized image. - /// - /// - /// The new color palette - /// - protected override List GetPalette() - { - return this.octree.Palletize(Math.Max(this.colors, 1)); - } - - /// - /// Returns how many bits are required to store the specified number of colors. - /// Performs a Log2() on the value. - /// - /// The number of colors. - /// - /// The - /// - private int GetBitsNeededForColorDepth(int colorCount) - { - return (int)Math.Ceiling(Math.Log(colorCount, 2)); - } - - /// - /// Class which does the actual quantization - /// - private class Octree - { - /// - /// Mask used when getting the appropriate pixels for a given node - /// - private static readonly int[] Mask = { 0x100, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; - - /// - /// The root of the Octree - /// - private readonly OctreeNode root; - - /// - /// Array of reducible nodes - /// - private readonly OctreeNode[] reducibleNodes; - - /// - /// Maximum number of significant bits in the image - /// - private readonly int maxColorBits; - - /// - /// Store the last node quantized - /// - private OctreeNode previousNode; - - /// - /// Cache the previous color quantized - /// - private TPacked previousColor; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The maximum number of significant bits in the image - /// - public Octree(int maxColorBits) - { - this.maxColorBits = maxColorBits; - this.Leaves = 0; - this.reducibleNodes = new OctreeNode[9]; - this.root = new OctreeNode(0, this.maxColorBits, this); - this.previousColor = default(TPacked); - this.previousNode = null; - } - - /// - /// Gets or sets the number of leaves in the tree - /// - private int Leaves { get; set; } - - /// - /// Gets the array of reducible nodes - /// - private OctreeNode[] ReducibleNodes => this.reducibleNodes; - - /// - /// Add a given color value to the Octree - /// - /// - /// The containing color information to add. - /// - public void AddColor(TColor pixel) - { - TPacked packed = pixel.PackedValue; - - // Check if this request is for the same color as the last - if (this.previousColor.Equals(packed)) - { - // If so, check if I have a previous node setup. This will only occur if the first color in the image - // happens to be black, with an alpha component of zero. - if (this.previousNode == null) - { - this.previousColor = packed; - this.root.AddColor(pixel, this.maxColorBits, 0, this); - } - else - { - // Just update the previous node - this.previousNode.Increment(pixel); - } - } - else - { - this.previousColor = packed; - this.root.AddColor(pixel, this.maxColorBits, 0, this); - } - } - - /// - /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors - /// - /// The maximum number of colors - /// - /// An with the palletized colors - /// - public List Palletize(int colorCount) - { - while (this.Leaves > colorCount) - { - this.Reduce(); - } - - // Now palletize the nodes - List palette = new List(this.Leaves); - int paletteIndex = 0; - this.root.ConstructPalette(palette, ref paletteIndex); - - // And return the palette - return palette; - } - - /// - /// Get the palette index for the passed color - /// - /// The containing the pixel data. - /// - /// The index of the given structure. - /// - public int GetPaletteIndex(TColor pixel) - { - return this.root.GetPaletteIndex(pixel, 0); - } - - /// - /// Keep track of the previous node that was quantized - /// - /// - /// The node last quantized - /// - protected void TrackPrevious(OctreeNode node) - { - this.previousNode = node; - } - - /// - /// Reduce the depth of the tree - /// - private void Reduce() - { - // Find the deepest level containing at least one reducible node - int index = this.maxColorBits - 1; - while ((index > 0) && (this.reducibleNodes[index] == null)) - { - index--; - } - - // Reduce the node most recently added to the list at level 'index' - OctreeNode node = this.reducibleNodes[index]; - this.reducibleNodes[index] = node.NextReducible; - - // Decrement the leaf count after reducing the node - this.Leaves -= node.Reduce(); - - // And just in case I've reduced the last color to be added, and the next color to - // be added is the same, invalidate the previousNode... - this.previousNode = null; - } - - /// - /// Class which encapsulates each node in the tree - /// - protected class OctreeNode - { - /// - /// Pointers to any child nodes - /// - private readonly OctreeNode[] children; - - /// - /// Flag indicating that this is a leaf node - /// - private bool leaf; - - /// - /// Number of pixels in this node - /// - private int pixelCount; - - /// - /// Red component - /// - private int red; - - /// - /// Green Component - /// - private int green; - - /// - /// Blue component - /// - private int blue; - - /// - /// Alpha component - /// - private int alpha; - - /// - /// The index of this node in the palette - /// - private int paletteIndex; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The level in the tree = 0 - 7 - /// - /// - /// The number of significant color bits in the image - /// - /// - /// The tree to which this node belongs - /// - public OctreeNode(int level, int colorBits, Octree octree) - { - // Construct the new node - this.leaf = level == colorBits; - - this.red = this.green = this.blue = this.alpha = 0; - this.pixelCount = 0; - - // If a leaf, increment the leaf count - if (this.leaf) - { - octree.Leaves++; - this.NextReducible = null; - this.children = null; - } - else - { - // Otherwise add this to the reducible nodes - this.NextReducible = octree.ReducibleNodes[level]; - octree.ReducibleNodes[level] = this; - this.children = new OctreeNode[8]; - } - } - - /// - /// Gets the next reducible node - /// - public OctreeNode NextReducible { get; } - - /// - /// Add a color into the tree - /// - /// The color - /// The number of significant color bits - /// The level in the tree - /// The tree to which this node belongs - public void AddColor(TColor pixel, int colorBits, int level, Octree octree) - { - // Update the color information if this is a leaf - if (this.leaf) - { - this.Increment(pixel); - - // Setup the previous node - octree.TrackPrevious(this); - } - else - { - // Go to the next level down in the tree - int shift = 7 - level; - Color color = new Color(pixel.ToVector4()); - int index = ((color.A & Mask[0]) >> (shift - 3)) | - ((color.B & Mask[level + 1]) >> (shift - 2)) | - ((color.G & Mask[level + 1]) >> (shift - 1)) | - ((color.R & Mask[level + 1]) >> shift); - - OctreeNode child = this.children[index]; - - if (child == null) - { - // Create a new child node and store it in the array - child = new OctreeNode(level + 1, colorBits, octree); - this.children[index] = child; - } - - // Add the color to the child node - child.AddColor(pixel, colorBits, level + 1, octree); - } - } - - /// - /// Reduce this node by removing all of its children - /// - /// The number of leaves removed - public int Reduce() - { - this.red = this.green = this.blue = this.alpha = 0; - int childNodes = 0; - - // Loop through all children and add their information to this node - for (int index = 0; index < 8; index++) - { - if (this.children[index] != null) - { - this.red += this.children[index].red; - this.green += this.children[index].green; - this.blue += this.children[index].blue; - this.alpha += this.children[index].alpha; - this.pixelCount += this.children[index].pixelCount; - ++childNodes; - this.children[index] = null; - } - } - - // Now change this to a leaf node - this.leaf = true; - - // Return the number of nodes to decrement the leaf count by - return childNodes - 1; - } - - /// - /// Traverse the tree, building up the color palette - /// - /// The palette - /// The current palette index - public void ConstructPalette(List palette, ref int index) - { - if (this.leaf) - { - // Consume the next palette index - this.paletteIndex = index++; - - byte r = (this.red / this.pixelCount).ToByte(); - byte g = (this.green / this.pixelCount).ToByte(); - byte b = (this.blue / this.pixelCount).ToByte(); - byte a = (this.alpha / this.pixelCount).ToByte(); - - // And set the color of the palette entry - TColor pixel = default(TColor); - pixel.PackFromVector4(new Color(r, g, b, a).ToVector4()); - palette.Add(pixel); - } - else - { - // Loop through children looking for leaves - for (int i = 0; i < 8; i++) - { - if (this.children[i] != null) - { - this.children[i].ConstructPalette(palette, ref index); - } - } - } - } - - /// - /// Return the palette index for the passed color - /// - /// The representing the pixel. - /// The level. - /// - /// The representing the index of the pixel in the palette. - /// - public int GetPaletteIndex(TColor pixel, int level) - { - int index = this.paletteIndex; - - if (!this.leaf) - { - int shift = 7 - level; - Color color = new Color(pixel.ToVector4()); - int pixelIndex = ((color.A & Mask[0]) >> (shift - 3)) | - ((color.B & Mask[level + 1]) >> (shift - 2)) | - ((color.G & Mask[level + 1]) >> (shift - 1)) | - ((color.R & Mask[level + 1]) >> shift); - - if (this.children[pixelIndex] != null) - { - index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1); - } - else - { - throw new Exception($"Cannot retrive a pixel at the given index {pixelIndex}."); - } - } - - return index; - } - - /// - /// Increment the pixel count and add to the color information - /// - /// - /// The pixel to add. - /// - public void Increment(TColor pixel) - { - this.pixelCount++; - Color color = new Color(pixel.ToVector4()); - this.red += color.R; - this.green += color.G; - this.blue += color.B; - this.alpha += color.A; - } - } - } - } -} diff --git a/src/ImageSharp46/Quantizers/Octree/Quantizer.cs b/src/ImageSharp46/Quantizers/Octree/Quantizer.cs deleted file mode 100644 index d88832634..000000000 --- a/src/ImageSharp46/Quantizers/Octree/Quantizer.cs +++ /dev/null @@ -1,143 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Quantizers -{ - using System.Collections.Generic; - using System.Threading.Tasks; - - /// - /// Encapsulates methods to calculate the color palette of an image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public abstract class Quantizer : IQuantizer - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Flag used to indicate whether a single pass or two passes are needed for quantization. - /// - private readonly bool singlePass; - - /// - /// Initializes a new instance of the class. - /// - /// - /// If true, the quantization only needs to loop through the source pixels once - /// - /// - /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image, - /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' - /// and then 'QuantizeImage'. - /// - protected Quantizer(bool singlePass) - { - this.singlePass = singlePass; - } - - /// - public virtual QuantizedImage Quantize(ImageBase image, int maxColors) - { - Guard.NotNull(image, nameof(image)); - - // Get the size of the source image - int height = image.Height; - int width = image.Width; - byte[] quantizedPixels = new byte[width * height]; - List palette; - - using (PixelAccessor pixels = image.Lock()) - { - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - this.FirstPass(pixels, width, height); - } - - // Get the palette - palette = this.GetPalette(); - - this.SecondPass(pixels, quantizedPixels, width, height); - } - - return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels); - } - - /// - /// Execute the first pass through the pixels in the image - /// - /// The source data - /// The width in pixels of the image. - /// The height in pixels of the image. - protected virtual void FirstPass(PixelAccessor source, int width, int height) - { - // Loop through each row - for (int y = 0; y < height; y++) - { - // And loop through each column - for (int x = 0; x < width; x++) - { - // Now I have the pixel, call the FirstPassQuantize function... - this.InitialQuantizePixel(source[x, y]); - } - } - } - - /// - /// Execute a second pass through the bitmap - /// - /// The source image. - /// The output pixel array - /// The width in pixels of the image - /// The height in pixels of the image - protected virtual void SecondPass(PixelAccessor source, byte[] output, int width, int height) - { - Parallel.For( - 0, - source.Height, - Bootstrapper.Instance.ParallelOptions, - y => - { - for (int x = 0; x < source.Width; x++) - { - TColor sourcePixel = source[x, y]; - output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel); - } - }); - } - - /// - /// Override this to process the pixel in the first pass of the algorithm - /// - /// The pixel to quantize - /// - /// This function need only be overridden if your quantize algorithm needs two passes, - /// such as an Octree quantizer. - /// - protected virtual void InitialQuantizePixel(TColor pixel) - { - } - - /// - /// Override this to process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - protected abstract byte QuantizePixel(TColor pixel); - - /// - /// Retrieve the palette for the quantized image - /// - /// - /// The new color palette - /// - protected abstract List GetPalette(); - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Quantizers/Options/Quantization.cs b/src/ImageSharp46/Quantizers/Options/Quantization.cs deleted file mode 100644 index 428c8e801..000000000 --- a/src/ImageSharp46/Quantizers/Options/Quantization.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Provides enumeration over how an image should be quantized. - /// - public enum Quantization - { - /// - /// An adaptive Octree quantizer. Fast with good quality. - /// - Octree, - - /// - /// Xiaolin Wu's Color Quantizer which generates high quality output. - /// - Wu, - - /// - /// Palette based, Uses the collection of web-safe colors by default. - /// - Palette - } -} diff --git a/src/ImageSharp46/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp46/Quantizers/Palette/PaletteQuantizer.cs deleted file mode 100644 index 73b7f1af2..000000000 --- a/src/ImageSharp46/Quantizers/Palette/PaletteQuantizer.cs +++ /dev/null @@ -1,130 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Quantizers -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - - /// - /// Encapsulates methods to create a quantized image based upon the given palette. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class PaletteQuantizer : Quantizer - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// A lookup table for colors - /// - private readonly ConcurrentDictionary colorMap = new ConcurrentDictionary(); - - /// - /// List of all colors in the palette - /// - private TColor[] colors; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The color palette. If none is given this will default to the web safe colors defined - /// in the CSS Color Module Level 4. - /// - public PaletteQuantizer(TColor[] palette = null) - : base(true) - { - if (palette == null) - { - Color[] constants = ColorConstants.WebSafeColors; - List safe = new List { default(TColor) }; - foreach (Color c in constants) - { - TColor packed = default(TColor); - packed.PackFromVector4(c.ToVector4()); - safe.Add(packed); - } - - this.colors = safe.ToArray(); - } - else - { - this.colors = palette; - } - } - - /// - public override QuantizedImage Quantize(ImageBase image, int maxColors) - { - Array.Resize(ref this.colors, maxColors.Clamp(1, 256)); - return base.Quantize(image, maxColors); - } - - /// - protected override byte QuantizePixel(TColor pixel) - { - byte colorIndex = 0; - string colorHash = pixel.ToString(); - - // Check if the color is in the lookup table - if (this.colorMap.ContainsKey(colorHash)) - { - colorIndex = this.colorMap[colorHash]; - } - else - { - // Not found - loop through the palette and find the nearest match. - Color color = new Color(pixel.ToVector4()); - - int leastDistance = int.MaxValue; - int red = color.R; - int green = color.G; - int blue = color.B; - int alpha = color.A; - - for (int index = 0; index < this.colors.Length; index++) - { - Color paletteColor = new Color(this.colors[index].ToVector4()); - int redDistance = paletteColor.R - red; - int greenDistance = paletteColor.G - green; - int blueDistance = paletteColor.B - blue; - int alphaDistance = paletteColor.A - alpha; - - int distance = (redDistance * redDistance) + - (greenDistance * greenDistance) + - (blueDistance * blueDistance) + - (alphaDistance * alphaDistance); - - if (distance < leastDistance) - { - colorIndex = (byte)index; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (distance == 0) - { - break; - } - } - } - - // Now I have the color, pop it into the cache for next time - this.colorMap.TryAdd(colorHash, colorIndex); - } - - return colorIndex; - } - - /// - protected override List GetPalette() - { - return this.colors.ToList(); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Quantizers/Quantize.cs b/src/ImageSharp46/Quantizers/Quantize.cs deleted file mode 100644 index 0a339a527..000000000 --- a/src/ImageSharp46/Quantizers/Quantize.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using ImageSharp.Quantizers; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Applies quantization to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The quantization mode to apply to perform the operation. - /// The maximum number of colors to return. Defaults to 256. - /// The . - public static Image Quantize(this Image source, Quantization mode = Quantization.Octree, int maxColors = 256) - where TColor : struct, IPackedPixel - where TPacked : struct - { - IQuantizer quantizer; - switch (mode) - { - case Quantization.Wu: - quantizer = new WuQuantizer(); - break; - - case Quantization.Palette: - quantizer = new PaletteQuantizer(); - break; - - default: - quantizer = new OctreeQuantizer(); - break; - } - - return Quantize(source, quantizer, maxColors); - } - - /// - /// Applies quantization to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The quantizer to apply to perform the operation. - /// The maximum number of colors to return. - /// The . - public static Image Quantize(this Image source, IQuantizer quantizer, int maxColors) - where TColor : struct, IPackedPixel - where TPacked : struct - { - QuantizedImage quantizedImage = quantizer.Quantize(source, maxColors); - source.SetPixels(source.Width, source.Height, quantizedImage.ToImage().Pixels); - return source; - } - } -} diff --git a/src/ImageSharp46/Quantizers/QuantizedImage.cs b/src/ImageSharp46/Quantizers/QuantizedImage.cs deleted file mode 100644 index 956557c62..000000000 --- a/src/ImageSharp46/Quantizers/QuantizedImage.cs +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Quantizers -{ - using System; - using System.Threading.Tasks; - - /// - /// Represents a quantized image where the pixels indexed by a color palette. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class QuantizedImage - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// The image width. - /// The image height. - /// The color palette. - /// The quantized pixels. - public QuantizedImage(int width, int height, TColor[] palette, byte[] pixels) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - Guard.NotNull(palette, nameof(palette)); - Guard.NotNull(pixels, nameof(pixels)); - - if (pixels.Length != width * height) - { - throw new ArgumentException( - $"Pixel array size must be {nameof(width)} * {nameof(height)}", nameof(pixels)); - } - - this.Width = width; - this.Height = height; - this.Palette = palette; - this.Pixels = pixels; - } - - /// - /// Gets the width of this . - /// - public int Width { get; } - - /// - /// Gets the height of this . - /// - public int Height { get; } - - /// - /// Gets the color palette of this . - /// - public TColor[] Palette { get; } - - /// - /// Gets the pixels of this . - /// - public byte[] Pixels { get; } - - /// - /// Converts this quantized image to a normal image. - /// - /// - /// The - /// - public Image ToImage() - { - Image image = new Image(); - - int pixelCount = this.Pixels.Length; - int palletCount = this.Palette.Length - 1; - TColor[] pixels = new TColor[pixelCount]; - - Parallel.For( - 0, - pixelCount, - Bootstrapper.Instance.ParallelOptions, - i => - { - TColor color = this.Palette[Math.Min(palletCount, this.Pixels[i])]; - pixels[i] = color; - }); - - image.SetPixels(this.Width, this.Height, pixels); - return image; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Quantizers/Wu/Box.cs b/src/ImageSharp46/Quantizers/Wu/Box.cs deleted file mode 100644 index e715c7839..000000000 --- a/src/ImageSharp46/Quantizers/Wu/Box.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Quantizers -{ - /// - /// Represents a box color cube. - /// - internal sealed class Box - { - /// - /// Gets or sets the min red value, exclusive. - /// - public int R0 { get; set; } - - /// - /// Gets or sets the max red value, inclusive. - /// - public int R1 { get; set; } - - /// - /// Gets or sets the min green value, exclusive. - /// - public int G0 { get; set; } - - /// - /// Gets or sets the max green value, inclusive. - /// - public int G1 { get; set; } - - /// - /// Gets or sets the min blue value, exclusive. - /// - public int B0 { get; set; } - - /// - /// Gets or sets the max blue value, inclusive. - /// - public int B1 { get; set; } - - /// - /// Gets or sets the min alpha value, exclusive. - /// - public int A0 { get; set; } - - /// - /// Gets or sets the max alpha value, inclusive. - /// - public int A1 { get; set; } - - /// - /// Gets or sets the volume. - /// - public int Volume { get; set; } - } -} diff --git a/src/ImageSharp46/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp46/Quantizers/Wu/WuQuantizer.cs deleted file mode 100644 index 8eabe38e6..000000000 --- a/src/ImageSharp46/Quantizers/Wu/WuQuantizer.cs +++ /dev/null @@ -1,777 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Quantizers -{ - using System; - using System.Collections.Generic; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// An implementation of Wu's color quantizer with alpha channel. - /// - /// - /// - /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) - /// (see Graphics Gems volume II, pages 126-133) - /// (). - /// - /// - /// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel - /// - /// - /// - /// Algorithm: Greedy orthogonal bipartition of RGB space for variance - /// minimization aided by inclusion-exclusion tricks. - /// For speed no nearest neighbor search is done. Slightly - /// better performance can be expected by more sophisticated - /// but more expensive versions. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - public sealed class WuQuantizer : IQuantizer - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The index bits. - /// - private const int IndexBits = 6; - - /// - /// The index alpha bits. - /// - private const int IndexAlphaBits = 3; - - /// - /// The index count. - /// - private const int IndexCount = (1 << IndexBits) + 1; - - /// - /// The index alpha count. - /// - private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; - - /// - /// The table length. - /// - private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - - /// - /// Moment of P(c). - /// - private readonly long[] vwt; - - /// - /// Moment of r*P(c). - /// - private readonly long[] vmr; - - /// - /// Moment of g*P(c). - /// - private readonly long[] vmg; - - /// - /// Moment of b*P(c). - /// - private readonly long[] vmb; - - /// - /// Moment of a*P(c). - /// - private readonly long[] vma; - - /// - /// Moment of c^2*P(c). - /// - private readonly double[] m2; - - /// - /// Color space tag. - /// - private readonly byte[] tag; - - /// - /// Initializes a new instance of the class. - /// - public WuQuantizer() - { - this.vwt = new long[TableLength]; - this.vmr = new long[TableLength]; - this.vmg = new long[TableLength]; - this.vmb = new long[TableLength]; - this.vma = new long[TableLength]; - this.m2 = new double[TableLength]; - this.tag = new byte[TableLength]; - } - - /// - public QuantizedImage Quantize(ImageBase image, int maxColors) - { - Guard.NotNull(image, nameof(image)); - - int colorCount = maxColors.Clamp(1, 256); - - this.Clear(); - - using (PixelAccessor imagePixels = image.Lock()) - { - this.Build3DHistogram(imagePixels); - this.Get3DMoments(); - - Box[] cube; - this.BuildCube(out cube, ref colorCount); - - return this.GenerateResult(imagePixels, colorCount, cube); - } - } - - /// - /// Gets an index. - /// - /// The red value. - /// The green value. - /// The blue value. - /// The alpha value. - /// The index. - private static int GetPaletteIndex(int r, int g, int b, int a) - { - return (r << ((IndexBits * 2) + IndexAlphaBits)) - + (r << (IndexBits + IndexAlphaBits + 1)) - + (g << (IndexBits + IndexAlphaBits)) - + (r << (IndexBits * 2)) - + (r << (IndexBits + 1)) - + (g << IndexBits) - + ((r + g + b) << IndexAlphaBits) - + r + g + b + a; - } - - /// - /// Computes sum over a box of any given statistic. - /// - /// The cube. - /// The moment. - /// The result. - private static double Volume(Box cube, long[] moment) - { - return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - } - - /// - /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (depending on direction). - /// - /// The cube. - /// The direction. - /// The moment. - /// The result. - private static long Bottom(Box cube, int direction, long[] moment) - { - switch (direction) - { - // Red - case 0: - return -moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Green - case 1: - return -moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Blue - case 2: - return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Alpha - case 3: - return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - default: - throw new ArgumentOutOfRangeException(nameof(direction)); - } - } - - /// - /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction). - /// - /// The cube. - /// The direction. - /// The position. - /// The moment. - /// The result. - private static long Top(Box cube, int direction, int position, long[] moment) - { - switch (direction) - { - // Red - case 0: - return moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A1)] - - moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A0)]; - - // Green - case 1: - return moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A0)]; - - // Blue - case 2: - return moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A0)]; - - // Alpha - case 3: - return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, position)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, position)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, position)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, position)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, position)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, position)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, position)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, position)]; - - default: - throw new ArgumentOutOfRangeException(nameof(direction)); - } - } - - /// - /// Clears the tables. - /// - private void Clear() - { - Array.Clear(this.vwt, 0, TableLength); - Array.Clear(this.vmr, 0, TableLength); - Array.Clear(this.vmg, 0, TableLength); - Array.Clear(this.vmb, 0, TableLength); - Array.Clear(this.vma, 0, TableLength); - Array.Clear(this.m2, 0, TableLength); - - Array.Clear(this.tag, 0, TableLength); - } - - /// - /// Builds a 3-D color histogram of counts, r/g/b, c^2. - /// - /// The pixel accessor. - private void Build3DHistogram(PixelAccessor pixels) - { - for (int y = 0; y < pixels.Height; y++) - { - for (int x = 0; x < pixels.Width; x++) - { - // Colors are expected in r->g->b->a format - Color color = new Color(pixels[x, y].ToVector4()); - - byte r = color.R; - byte g = color.G; - byte b = color.B; - byte a = color.A; - - int inr = r >> (8 - IndexBits); - int ing = g >> (8 - IndexBits); - int inb = b >> (8 - IndexBits); - int ina = a >> (8 - IndexAlphaBits); - - int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 1); - - this.vwt[ind]++; - this.vmr[ind] += r; - this.vmg[ind] += g; - this.vmb[ind] += b; - this.vma[ind] += a; - this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a); - } - } - } - - /// - /// Converts the histogram into moments so that we can rapidly calculate - /// the sums of the above quantities over any desired box. - /// - private void Get3DMoments() - { - long[] volume = new long[IndexCount * IndexAlphaCount]; - long[] volumeR = new long[IndexCount * IndexAlphaCount]; - long[] volumeG = new long[IndexCount * IndexAlphaCount]; - long[] volumeB = new long[IndexCount * IndexAlphaCount]; - long[] volumeA = new long[IndexCount * IndexAlphaCount]; - double[] volume2 = new double[IndexCount * IndexAlphaCount]; - - long[] area = new long[IndexAlphaCount]; - long[] areaR = new long[IndexAlphaCount]; - long[] areaG = new long[IndexAlphaCount]; - long[] areaB = new long[IndexAlphaCount]; - long[] areaA = new long[IndexAlphaCount]; - double[] area2 = new double[IndexAlphaCount]; - - for (int r = 1; r < IndexCount; r++) - { - Array.Clear(volume, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount); - Array.Clear(volume2, 0, IndexCount * IndexAlphaCount); - - for (int g = 1; g < IndexCount; g++) - { - Array.Clear(area, 0, IndexAlphaCount); - Array.Clear(areaR, 0, IndexAlphaCount); - Array.Clear(areaG, 0, IndexAlphaCount); - Array.Clear(areaB, 0, IndexAlphaCount); - Array.Clear(areaA, 0, IndexAlphaCount); - Array.Clear(area2, 0, IndexAlphaCount); - - for (int b = 1; b < IndexCount; b++) - { - long line = 0; - long lineR = 0; - long lineG = 0; - long lineB = 0; - long lineA = 0; - double line2 = 0; - - for (int a = 1; a < IndexAlphaCount; a++) - { - int ind1 = GetPaletteIndex(r, g, b, a); - - line += this.vwt[ind1]; - lineR += this.vmr[ind1]; - lineG += this.vmg[ind1]; - lineB += this.vmb[ind1]; - lineA += this.vma[ind1]; - line2 += this.m2[ind1]; - - area[a] += line; - areaR[a] += lineR; - areaG[a] += lineG; - areaB[a] += lineB; - areaA[a] += lineA; - area2[a] += line2; - - int inv = (b * IndexAlphaCount) + a; - - volume[inv] += area[a]; - volumeR[inv] += areaR[a]; - volumeG[inv] += areaG[a]; - volumeB[inv] += areaB[a]; - volumeA[inv] += areaA[a]; - volume2[inv] += area2[a]; - - int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); - - this.vwt[ind1] = this.vwt[ind2] + volume[inv]; - this.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; - this.vmg[ind1] = this.vmg[ind2] + volumeG[inv]; - this.vmb[ind1] = this.vmb[ind2] + volumeB[inv]; - this.vma[ind1] = this.vma[ind2] + volumeA[inv]; - this.m2[ind1] = this.m2[ind2] + volume2[inv]; - } - } - } - } - } - - /// - /// Computes the weighted variance of a box cube. - /// - /// The cube. - /// The . - private double Variance(Box cube) - { - double dr = Volume(cube, this.vmr); - double dg = Volume(cube, this.vmg); - double db = Volume(cube, this.vmb); - double da = Volume(cube, this.vma); - - double xx = - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(cube, this.vwt)); - } - - /// - /// We want to minimize the sum of the variances of two sub-boxes. - /// The sum(c^2) terms can be ignored since their sum over both sub-boxes - /// is the same (the sum for the whole box) no matter where we split. - /// The remaining terms have a minus sign in the variance formula, - /// so we drop the minus sign and maximize the sum of the two terms. - /// - /// The cube. - /// The direction. - /// The first position. - /// The last position. - /// The cutting point. - /// The whole red. - /// The whole green. - /// The whole blue. - /// The whole alpha. - /// The whole weight. - /// The . - private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW) - { - long baseR = Bottom(cube, direction, this.vmr); - long baseG = Bottom(cube, direction, this.vmg); - long baseB = Bottom(cube, direction, this.vmb); - long baseA = Bottom(cube, direction, this.vma); - long baseW = Bottom(cube, direction, this.vwt); - - double max = 0.0; - cut = -1; - - for (int i = first; i < last; i++) - { - double halfR = baseR + Top(cube, direction, i, this.vmr); - double halfG = baseG + Top(cube, direction, i, this.vmg); - double halfB = baseB + Top(cube, direction, i, this.vmb); - double halfA = baseA + Top(cube, direction, i, this.vma); - double halfW = baseW + Top(cube, direction, i, this.vwt); - - double temp; - - if (Math.Abs(halfW) < Epsilon) - { - continue; - } - - temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; - - halfR = wholeR - halfR; - halfG = wholeG - halfG; - halfB = wholeB - halfB; - halfA = wholeA - halfA; - halfW = wholeW - halfW; - - if (Math.Abs(halfW) < Epsilon) - { - continue; - } - - temp += ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; - - if (temp > max) - { - max = temp; - cut = i; - } - } - - return max; - } - - /// - /// Cuts a box. - /// - /// The first set. - /// The second set. - /// Returns a value indicating whether the box has been split. - private bool Cut(Box set1, Box set2) - { - double wholeR = Volume(set1, this.vmr); - double wholeG = Volume(set1, this.vmg); - double wholeB = Volume(set1, this.vmb); - double wholeA = Volume(set1, this.vma); - double wholeW = Volume(set1, this.vwt); - - int cutr; - int cutg; - int cutb; - int cuta; - - double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW); - - int dir; - - if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) - { - dir = 0; - - if (cutr < 0) - { - return false; - } - } - else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) - { - dir = 1; - } - else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) - { - dir = 2; - } - else - { - dir = 3; - } - - set2.R1 = set1.R1; - set2.G1 = set1.G1; - set2.B1 = set1.B1; - set2.A1 = set1.A1; - - switch (dir) - { - // Red - case 0: - set2.R0 = set1.R1 = cutr; - set2.G0 = set1.G0; - set2.B0 = set1.B0; - set2.A0 = set1.A0; - break; - - // Green - case 1: - set2.G0 = set1.G1 = cutg; - set2.R0 = set1.R0; - set2.B0 = set1.B0; - set2.A0 = set1.A0; - break; - - // Blue - case 2: - set2.B0 = set1.B1 = cutb; - set2.R0 = set1.R0; - set2.G0 = set1.G0; - set2.A0 = set1.A0; - break; - - // Alpha - case 3: - set2.A0 = set1.A1 = cuta; - set2.R0 = set1.R0; - set2.G0 = set1.G0; - set2.B0 = set1.B0; - break; - } - - set1.Volume = (set1.R1 - set1.R0) * (set1.G1 - set1.G0) * (set1.B1 - set1.B0) * (set1.A1 - set1.A0); - set2.Volume = (set2.R1 - set2.R0) * (set2.G1 - set2.G0) * (set2.B1 - set2.B0) * (set2.A1 - set2.A0); - - return true; - } - - /// - /// Marks a color space tag. - /// - /// The cube. - /// A label. - private void Mark(Box cube, byte label) - { - for (int r = cube.R0 + 1; r <= cube.R1; r++) - { - for (int g = cube.G0 + 1; g <= cube.G1; g++) - { - for (int b = cube.B0 + 1; b <= cube.B1; b++) - { - for (int a = cube.A0 + 1; a <= cube.A1; a++) - { - this.tag[GetPaletteIndex(r, g, b, a)] = label; - } - } - } - } - } - - /// - /// Builds the cube. - /// - /// The cube. - /// The color count. - private void BuildCube(out Box[] cube, ref int colorCount) - { - cube = new Box[colorCount]; - double[] vv = new double[colorCount]; - - for (int i = 0; i < colorCount; i++) - { - cube[i] = new Box(); - } - - cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; - cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1; - cube[0].A1 = IndexAlphaCount - 1; - - int next = 0; - - for (int i = 1; i < colorCount; i++) - { - if (this.Cut(cube[next], cube[i])) - { - vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0; - vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0; - } - else - { - vv[next] = 0.0; - i--; - } - - next = 0; - - double temp = vv[0]; - for (int k = 1; k <= i; k++) - { - if (vv[k] > temp) - { - temp = vv[k]; - next = k; - } - } - - if (temp <= 0.0) - { - colorCount = i + 1; - break; - } - } - } - - /// - /// Generates the quantized result. - /// - /// The image pixels. - /// The color count. - /// The cube. - /// The result. - private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube) - { - List pallette = new List(); - byte[] pixels = new byte[imagePixels.Width * imagePixels.Height]; - int width = imagePixels.Width; - int height = imagePixels.Height; - - for (int k = 0; k < colorCount; k++) - { - this.Mark(cube[k], (byte)k); - - double weight = Volume(cube[k], this.vwt); - - if (Math.Abs(weight) > Epsilon) - { - float r = (float)(Volume(cube[k], this.vmr) / weight); - float g = (float)(Volume(cube[k], this.vmg) / weight); - float b = (float)(Volume(cube[k], this.vmb) / weight); - float a = (float)(Volume(cube[k], this.vma) / weight); - - TColor color = default(TColor); - color.PackFromVector4(new Vector4(r, g, b, a) / 255F); - pallette.Add(color); - } - else - { - pallette.Add(default(TColor)); - } - } - - Parallel.For( - 0, - height, - Bootstrapper.Instance.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - // Expected order r->g->b->a - Color color = new Color(imagePixels[x, y].ToVector4()); - int r = color.R >> (8 - IndexBits); - int g = color.G >> (8 - IndexBits); - int b = color.B >> (8 - IndexBits); - int a = color.A >> (8 - IndexAlphaBits); - - int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - pixels[(y * width) + x] = this.tag[ind]; - } - }); - - return new QuantizedImage(width, height, pallette.ToArray(), pixels); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/AutoOrient.cs b/src/ImageSharp46/Samplers/AutoOrient.cs deleted file mode 100644 index 6195a2066..000000000 --- a/src/ImageSharp46/Samplers/AutoOrient.cs +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Adjusts an image so that its orientation is suitable for viewing. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to auto rotate. - /// The - public static Image AutoOrient(this Image source) - where TColor : struct, IPackedPixel - where TPacked : struct - { - Orientation orientation = GetExifOrientation(source); - - switch (orientation) - { - case Orientation.TopRight: - return source.Flip(FlipType.Horizontal); - - case Orientation.BottomRight: - return source.Rotate(RotateType.Rotate180); - - case Orientation.BottomLeft: - return source.Flip(FlipType.Vertical); - - case Orientation.LeftTop: - return source.Rotate(RotateType.Rotate90) - .Flip(FlipType.Horizontal); - - case Orientation.RightTop: - return source.Rotate(RotateType.Rotate90); - - case Orientation.RightBottom: - return source.Flip(FlipType.Vertical) - .Rotate(RotateType.Rotate270); - - case Orientation.LeftBottom: - return source.Rotate(RotateType.Rotate270); - - case Orientation.Unknown: - case Orientation.TopLeft: - default: - return source; - } - } - - /// - /// Returns the current EXIF orientation - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to auto rotate. - /// The - private static Orientation GetExifOrientation(Image source) - where TColor : struct, IPackedPixel - where TPacked : struct - { - if (source.ExifProfile == null) - { - return Orientation.Unknown; - } - - ExifValue value = source.ExifProfile.GetValue(ExifTag.Orientation); - if (value == null) - { - return Orientation.Unknown; - } - - Orientation orientation = (Orientation)value.Value; - - source.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); - - return orientation; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/BoxBlur.cs b/src/ImageSharp46/Samplers/BoxBlur.cs deleted file mode 100644 index 4c0aa8d1f..000000000 --- a/src/ImageSharp46/Samplers/BoxBlur.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Applies a box blur to the image. - /// - /// The pixel format. - /// The packed format. long, float. - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The . - public static Image BoxBlur(this Image source, int radius = 7) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return BoxBlur(source, radius, source.Bounds); - } - - /// - /// Applies a box blur to the image. - /// - /// The pixel format. - /// The packed format. long, float. - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static Image BoxBlur(this Image source, int radius, Rectangle rectangle) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return source.Process(rectangle, new BoxBlurProcessor(radius)); - } - } -} diff --git a/src/ImageSharp46/Samplers/Crop.cs b/src/ImageSharp46/Samplers/Crop.cs deleted file mode 100644 index a266db3b2..000000000 --- a/src/ImageSharp46/Samplers/Crop.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Crops an image to the given width and height. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to resize. - /// The target image width. - /// The target image height. - /// The - public static Image Crop(this Image source, int width, int height) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Crop(source, width, height, source.Bounds); - } - - /// - /// Crops an image to the given width and height with the given source rectangle. - /// - /// If the source rectangle is smaller than the target dimensions then the - /// area within the source is resized performing a zoomed crop. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to crop. - /// The target image width. - /// The target image height. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle) - where TColor : struct, IPackedPixel - where TPacked : struct - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - if (sourceRectangle.Width < width || sourceRectangle.Height < height) - { - // If the source rectangle is smaller than the target perform a - // cropped zoom. - source = source.Resize(sourceRectangle.Width, sourceRectangle.Height); - } - - CropProcessor processor = new CropProcessor(); - return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor); - } - } -} diff --git a/src/ImageSharp46/Samplers/DetectEdges.cs b/src/ImageSharp46/Samplers/DetectEdges.cs deleted file mode 100644 index c28bb3c81..000000000 --- a/src/ImageSharp46/Samplers/DetectEdges.cs +++ /dev/null @@ -1,161 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Detects any edges within the image. Uses the filter - /// operating in Grayscale mode. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The . - public static Image DetectEdges(this Image source) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return DetectEdges(source, source.Bounds, new SobelProcessor { Grayscale = true }); - } - - /// - /// Detects any edges within the image. Uses the filter - /// operating in Grayscale mode. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static Image DetectEdges(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return DetectEdges(source, rectangle, new SobelProcessor { Grayscale = true }); - } - - /// - /// Detects any edges within the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The filter for detecting edges. - /// Whether to convert the image to Grayscale first. Defaults to true. - /// The . - public static Image DetectEdges(this Image source, EdgeDetection filter, bool grayscale = true) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return DetectEdges(source, filter, source.Bounds, grayscale); - } - - /// - /// Detects any edges within the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The filter for detecting edges. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// Whether to convert the image to Grayscale first. Defaults to true. - /// The . - public static Image DetectEdges(this Image source, EdgeDetection filter, Rectangle rectangle, bool grayscale = true) - where TColor : struct, IPackedPixel - where TPacked : struct - { - IEdgeDetectorFilter processor; - - switch (filter) - { - case EdgeDetection.Kayyali: - processor = new KayyaliProcessor { Grayscale = grayscale }; - break; - - case EdgeDetection.Kirsch: - processor = new KirschProcessor { Grayscale = grayscale }; - break; - - case EdgeDetection.Lapacian3X3: - processor = new Laplacian3X3Processor { Grayscale = grayscale }; - break; - - case EdgeDetection.Lapacian5X5: - processor = new Laplacian5X5Processor { Grayscale = grayscale }; - break; - - case EdgeDetection.LaplacianOfGaussian: - processor = new LaplacianOfGaussianProcessor { Grayscale = grayscale }; - break; - - case EdgeDetection.Prewitt: - processor = new PrewittProcessor { Grayscale = grayscale }; - break; - - case EdgeDetection.RobertsCross: - processor = new RobertsCrossProcessor { Grayscale = grayscale }; - break; - - case EdgeDetection.Robinson: - processor = new RobinsonProcessor { Grayscale = grayscale }; - break; - - case EdgeDetection.Scharr: - processor = new ScharrProcessor { Grayscale = grayscale }; - break; - - default: - processor = new SobelProcessor { Grayscale = grayscale }; - break; - } - - return DetectEdges(source, rectangle, processor); - } - - /// - /// Detects any edges within the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The filter for detecting edges. - /// The . - public static Image DetectEdges(this Image source, IEdgeDetectorFilter filter) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return DetectEdges(source, source.Bounds, filter); - } - - /// - /// Detects any edges within the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The filter for detecting edges. - /// The . - public static Image DetectEdges(this Image source, Rectangle rectangle, IEdgeDetectorFilter filter) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return source.Process(rectangle, filter); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/EntropyCrop.cs b/src/ImageSharp46/Samplers/EntropyCrop.cs deleted file mode 100644 index 28a2ebf48..000000000 --- a/src/ImageSharp46/Samplers/EntropyCrop.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Crops an image to the area of greatest entropy. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to crop. - /// The threshold for entropic density. - /// The - public static Image EntropyCrop(this Image source, float threshold = .5f) - where TColor : struct, IPackedPixel - where TPacked : struct - { - EntropyCropProcessor processor = new EntropyCropProcessor(threshold); - return source.Process(source.Width, source.Height, source.Bounds, Rectangle.Empty, processor); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Flip.cs b/src/ImageSharp46/Samplers/Flip.cs deleted file mode 100644 index 0e50ad8ac..000000000 --- a/src/ImageSharp46/Samplers/Flip.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Flips an image by the given instructions. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to rotate, flip, or both. - /// The to perform the flip. - /// The - public static Image Flip(this Image source, FlipType flipType) - where TColor : struct, IPackedPixel - where TPacked : struct - { - FlipProcessor processor = new FlipProcessor(flipType); - return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/GuassianBlur.cs b/src/ImageSharp46/Samplers/GuassianBlur.cs deleted file mode 100644 index ef09a18f5..000000000 --- a/src/ImageSharp46/Samplers/GuassianBlur.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Applies a Guassian blur to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// The . - public static Image GuassianBlur(this Image source, float sigma = 3f) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return GuassianBlur(source, sigma, source.Bounds); - } - - /// - /// Applies a Guassian blur to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static Image GuassianBlur(this Image source, float sigma, Rectangle rectangle) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return source.Process(rectangle, new GuassianBlurProcessor(sigma)); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/GuassianSharpen.cs b/src/ImageSharp46/Samplers/GuassianSharpen.cs deleted file mode 100644 index 6b433f6ae..000000000 --- a/src/ImageSharp46/Samplers/GuassianSharpen.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Applies a Guassian sharpening filter to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// The . - public static Image GuassianSharpen(this Image source, float sigma = 3f) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return GuassianSharpen(source, sigma, source.Bounds); - } - - /// - /// Applies a Guassian sharpening filter to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static Image GuassianSharpen(this Image source, float sigma, Rectangle rectangle) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return source.Process(rectangle, new GuassianSharpenProcessor(sigma)); - } - } -} diff --git a/src/ImageSharp46/Samplers/OilPainting.cs b/src/ImageSharp46/Samplers/OilPainting.cs deleted file mode 100644 index a938b2325..000000000 --- a/src/ImageSharp46/Samplers/OilPainting.cs +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Alters the colors of the image recreating an oil painting effect. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image. - /// The number of neighbouring pixels used in calculating each individual pixel value. - /// The . - public static Image OilPaint(this Image source, int levels = 10, int brushSize = 15) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return OilPaint(source, levels, brushSize, source.Bounds); - } - - /// - /// Alters the colors of the image recreating an oil painting effect. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image. - /// The number of neighbouring pixels used in calculating each individual pixel value. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static Image OilPaint(this Image source, int levels, int brushSize, Rectangle rectangle) - where TColor : struct, IPackedPixel - where TPacked : struct - { - Guard.MustBeGreaterThan(levels, 0, nameof(levels)); - - if (brushSize <= 0 || brushSize > source.Height || brushSize > source.Width) - { - throw new ArgumentOutOfRangeException(nameof(brushSize)); - } - - return source.Process(rectangle, new OilPaintingProcessor(levels, brushSize)); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Options/AnchorPosition.cs b/src/ImageSharp46/Samplers/Options/AnchorPosition.cs deleted file mode 100644 index c0adb4da4..000000000 --- a/src/ImageSharp46/Samplers/Options/AnchorPosition.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Enumerated anchor positions to apply to resized images. - /// - public enum AnchorPosition - { - /// - /// Anchors the position of the image to the center of it's bounding container. - /// - Center, - - /// - /// Anchors the position of the image to the top of it's bounding container. - /// - Top, - - /// - /// Anchors the position of the image to the bottom of it's bounding container. - /// - Bottom, - - /// - /// Anchors the position of the image to the left of it's bounding container. - /// - Left, - - /// - /// Anchors the position of the image to the right of it's bounding container. - /// - Right, - - /// - /// Anchors the position of the image to the top left side of it's bounding container. - /// - TopLeft, - - /// - /// Anchors the position of the image to the top right side of it's bounding container. - /// - TopRight, - - /// - /// Anchors the position of the image to the bottom right side of it's bounding container. - /// - BottomRight, - - /// - /// Anchors the position of the image to the bottom left side of it's bounding container. - /// - BottomLeft - } -} diff --git a/src/ImageSharp46/Samplers/Options/FlipType.cs b/src/ImageSharp46/Samplers/Options/FlipType.cs deleted file mode 100644 index 5a9668316..000000000 --- a/src/ImageSharp46/Samplers/Options/FlipType.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Provides enumeration over how a image should be flipped. - /// - public enum FlipType - { - /// - /// Don't flip the image. - /// - None, - - /// - /// Flip the image horizontally. - /// - Horizontal, - - /// - /// Flip the image vertically. - /// - Vertical, - } -} diff --git a/src/ImageSharp46/Samplers/Options/Orientation.cs b/src/ImageSharp46/Samplers/Options/Orientation.cs deleted file mode 100644 index dfe57a4b0..000000000 --- a/src/ImageSharp46/Samplers/Options/Orientation.cs +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - internal enum Orientation : ushort - { - Unknown = 0, - TopLeft = 1, - TopRight = 2, - BottomRight = 3, - BottomLeft = 4, - LeftTop = 5, - RightTop = 6, - RightBottom = 7, - LeftBottom = 8 - } -} diff --git a/src/ImageSharp46/Samplers/Options/ResizeHelper.cs b/src/ImageSharp46/Samplers/Options/ResizeHelper.cs deleted file mode 100644 index 33aa32528..000000000 --- a/src/ImageSharp46/Samplers/Options/ResizeHelper.cs +++ /dev/null @@ -1,441 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Linq; - - /// - /// Provides methods to help calculate the target rectangle when resizing using the - /// enumeration. - /// - internal static class ResizeHelper - { - /// - /// Calculates the target location and bounds to perform the resize operation against. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The source image. - /// The resize options. - /// - /// The . - /// - public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel - where TPacked : struct - { - switch (options.Mode) - { - case ResizeMode.Crop: - return CalculateCropRectangle(source, options); - case ResizeMode.Pad: - return CalculatePadRectangle(source, options); - case ResizeMode.BoxPad: - return CalculateBoxPadRectangle(source, options); - case ResizeMode.Max: - return CalculateMaxRectangle(source, options); - case ResizeMode.Min: - return CalculateMinRectangle(source, options); - - // Last case ResizeMode.Stretch: - default: - return new Rectangle(0, 0, options.Size.Width, options.Size.Height); - } - } - - /// - /// Calculates the target rectangle for crop mode. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel - where TPacked : struct - { - int width = options.Size.Width; - int height = options.Size.Height; - - if (width <= 0 || height <= 0) - { - return new Rectangle(0, 0, source.Width, source.Height); - } - - double ratio; - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int destinationX = 0; - int destinationY = 0; - int destinationWidth = width; - int destinationHeight = height; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - if (percentHeight < percentWidth) - { - ratio = percentWidth; - - if (options.CenterCoordinates.Any()) - { - double center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); - destinationY = (int)center + (height / 2); - - if (destinationY > 0) - { - destinationY = 0; - } - - if (destinationY < (int)(height - (sourceHeight * ratio))) - { - destinationY = (int)(height - (sourceHeight * ratio)); - } - } - else - { - switch (options.Position) - { - case AnchorPosition.Top: - case AnchorPosition.TopLeft: - case AnchorPosition.TopRight: - destinationY = 0; - break; - case AnchorPosition.Bottom: - case AnchorPosition.BottomLeft: - case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } - - destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); - } - else - { - ratio = percentHeight; - - if (options.CenterCoordinates.Any()) - { - double center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; - destinationX = (int)center + (width / 2); - - if (destinationX > 0) - { - destinationX = 0; - } - - if (destinationX < (int)(width - (sourceWidth * ratio))) - { - destinationX = (int)(width - (sourceWidth * ratio)); - } - } - else - { - switch (options.Position) - { - case AnchorPosition.Left: - case AnchorPosition.TopLeft: - case AnchorPosition.BottomLeft: - destinationX = 0; - break; - case AnchorPosition.Right: - case AnchorPosition.TopRight: - case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = (int)((width - (sourceWidth * ratio)) / 2); - break; - } - } - - destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); - } - - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - } - - /// - /// Calculates the target rectangle for pad mode. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel - where TPacked : struct - { - int width = options.Size.Width; - int height = options.Size.Height; - - if (width <= 0 || height <= 0) - { - return new Rectangle(0, 0, source.Width, source.Height); - } - - double ratio; - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int destinationX = 0; - int destinationY = 0; - int destinationWidth = width; - int destinationHeight = height; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - if (percentHeight < percentWidth) - { - ratio = percentHeight; - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - - switch (options.Position) - { - case AnchorPosition.Left: - case AnchorPosition.TopLeft: - case AnchorPosition.BottomLeft: - destinationX = 0; - break; - case AnchorPosition.Right: - case AnchorPosition.TopRight: - case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); - break; - } - } - else - { - ratio = percentWidth; - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - - switch (options.Position) - { - case AnchorPosition.Top: - case AnchorPosition.TopLeft: - case AnchorPosition.TopRight: - destinationY = 0; - break; - case AnchorPosition.Bottom: - case AnchorPosition.BottomLeft: - case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } - - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - } - - /// - /// Calculates the target rectangle for box pad mode. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel - where TPacked : struct - { - int width = options.Size.Width; - int height = options.Size.Height; - - if (width <= 0 || height <= 0) - { - return new Rectangle(0, 0, source.Width, source.Height); - } - - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); - int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); - - // Only calculate if upscaling. - if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) - { - int destinationX; - int destinationY; - int destinationWidth = sourceWidth; - int destinationHeight = sourceHeight; - width = boxPadWidth; - height = boxPadHeight; - - switch (options.Position) - { - case AnchorPosition.Left: - destinationY = (height - sourceHeight) / 2; - destinationX = 0; - break; - case AnchorPosition.Right: - destinationY = (height - sourceHeight) / 2; - destinationX = width - sourceWidth; - break; - case AnchorPosition.TopRight: - destinationY = 0; - destinationX = width - sourceWidth; - break; - case AnchorPosition.Top: - destinationY = 0; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPosition.TopLeft: - destinationY = 0; - destinationX = 0; - break; - case AnchorPosition.BottomRight: - destinationY = height - sourceHeight; - destinationX = width - sourceWidth; - break; - case AnchorPosition.Bottom: - destinationY = height - sourceHeight; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPosition.BottomLeft: - destinationY = height - sourceHeight; - destinationX = 0; - break; - default: - destinationY = (height - sourceHeight) / 2; - destinationX = (width - sourceWidth) / 2; - break; - } - - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - } - - // Switch to pad mode to downscale and calculate from there. - return CalculatePadRectangle(source, options); - } - - /// - /// Calculates the target rectangle for max mode. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel - where TPacked : struct - { - int width = options.Size.Width; - int height = options.Size.Height; - int destinationWidth = width; - int destinationHeight = height; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)source.Height); - double percentWidth = Math.Abs(width / (double)source.Width); - - // Integers must be cast to doubles to get needed precision - double ratio = (double)options.Size.Height / options.Size.Width; - double sourceRatio = (double)source.Height / source.Width; - - if (sourceRatio < ratio) - { - destinationHeight = Convert.ToInt32(source.Height * percentWidth); - height = destinationHeight; - } - else - { - destinationWidth = Convert.ToInt32(source.Width * percentHeight); - width = destinationWidth; - } - - // Replace the size to match the rectangle. - options.Size = new Size(width, height); - return new Rectangle(0, 0, destinationWidth, destinationHeight); - } - - /// - /// Calculates the target rectangle for min mode. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel - where TPacked : struct - { - int width = options.Size.Width; - int height = options.Size.Height; - int destinationWidth; - int destinationHeight; - - // Don't upscale - if (width > source.Width || height > source.Height) - { - options.Size = new Size(source.Width, source.Height); - return new Rectangle(0, 0, source.Width, source.Height); - } - - double sourceRatio = (double)source.Height / source.Width; - - // Find the shortest distance to go. - int widthDiff = source.Width - width; - int heightDiff = source.Height - height; - - if (widthDiff < heightDiff) - { - destinationHeight = Convert.ToInt32(width * sourceRatio); - height = destinationHeight; - destinationWidth = width; - } - else if (widthDiff > heightDiff) - { - destinationWidth = Convert.ToInt32(height / sourceRatio); - destinationHeight = height; - width = destinationWidth; - } - else - { - destinationWidth = width; - destinationHeight = height; - } - - // Replace the size to match the rectangle. - options.Size = new Size(width, height); - return new Rectangle(0, 0, destinationWidth, destinationHeight); - } - } -} diff --git a/src/ImageSharp46/Samplers/Options/ResizeMode.cs b/src/ImageSharp46/Samplers/Options/ResizeMode.cs deleted file mode 100644 index 7a1cc3c94..000000000 --- a/src/ImageSharp46/Samplers/Options/ResizeMode.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Enumerated resize modes to apply to resized images. - /// - public enum ResizeMode - { - /// - /// Crops the resized image to fit the bounds of its container. - /// - Crop, - - /// - /// Pads the resized image to fit the bounds of its container. - /// If only one dimension is passed, will maintain the original aspect ratio. - /// - Pad, - - /// - /// Pads the image to fit the bound of the container without resizing the - /// original source. - /// When downscaling, performs the same functionality as - /// - BoxPad, - - /// - /// Constrains the resized image to fit the bounds of its container maintaining - /// the original aspect ratio. - /// - Max, - - /// - /// Resizes the image until the shortest side reaches the set given dimension. - /// Upscaling is disabled in this mode and the original image will be returned - /// if attempted. - /// - Min, - - /// - /// Stretches the resized image to fit the bounds of its container. - /// - Stretch - } -} diff --git a/src/ImageSharp46/Samplers/Options/ResizeOptions.cs b/src/ImageSharp46/Samplers/Options/ResizeOptions.cs deleted file mode 100644 index 3cfb3d6c1..000000000 --- a/src/ImageSharp46/Samplers/Options/ResizeOptions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System.Collections.Generic; - using System.Linq; - - /// - /// The resize options for resizing images against certain modes. - /// - public class ResizeOptions - { - /// - /// Gets or sets the resize mode. - /// - public ResizeMode Mode { get; set; } = ResizeMode.Crop; - - /// - /// Gets or sets the anchor position. - /// - public AnchorPosition Position { get; set; } = AnchorPosition.Center; - - /// - /// Gets or sets the center coordinates. - /// - public IEnumerable CenterCoordinates { get; set; } = Enumerable.Empty(); - - /// - /// Gets or sets the target size. - /// - public Size Size { get; set; } - - /// - /// Gets or sets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; set; } = new BicubicResampler(); - - /// - /// Gets or sets a value indicating whether to compress - /// or expand individual pixel colors the value on processing. - /// - public bool Compand { get; set; } - } -} diff --git a/src/ImageSharp46/Samplers/Options/RotateType.cs b/src/ImageSharp46/Samplers/Options/RotateType.cs deleted file mode 100644 index 0545aa910..000000000 --- a/src/ImageSharp46/Samplers/Options/RotateType.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Provides enumeration over how the image should be rotated. - /// - public enum RotateType - { - /// - /// Do not rotate the image. - /// - None, - - /// - /// Rotate the image by 90 degrees clockwise. - /// - Rotate90 = 90, - - /// - /// Rotate the image by 180 degrees clockwise. - /// - Rotate180 = 180, - - /// - /// Rotate the image by 270 degrees clockwise. - /// - Rotate270 = 270 - } -} diff --git a/src/ImageSharp46/Samplers/Pad.cs b/src/ImageSharp46/Samplers/Pad.cs deleted file mode 100644 index 3c4db20bd..000000000 --- a/src/ImageSharp46/Samplers/Pad.cs +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Evenly pads an image to fit the new dimensions. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The source image to pad. - /// The new width. - /// The new height. - /// The . - public static Image Pad(this Image source, int width, int height) - where TColor : struct, IPackedPixel - where TPacked : struct - { - ResizeOptions options = new ResizeOptions - { - Size = new Size(width, height), - Mode = ResizeMode.BoxPad, - Sampler = new NearestNeighborResampler() - }; - - return Resize(source, options); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Pixelate.cs b/src/ImageSharp46/Samplers/Pixelate.cs deleted file mode 100644 index e7bd84c31..000000000 --- a/src/ImageSharp46/Samplers/Pixelate.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Pixelates an image with the given pixel size. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The size of the pixels. - /// The . - public static Image Pixelate(this Image source, int size = 4) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Pixelate(source, size, source.Bounds); - } - - /// - /// Pixelates an image with the given pixel size. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image this method extends. - /// The size of the pixels. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static Image Pixelate(this Image source, int size, Rectangle rectangle) - where TColor : struct, IPackedPixel - where TPacked : struct - { - if (size <= 0 || size > source.Height || size > source.Width) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - return source.Process(rectangle, new PixelateProcessor(size)); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/CompandingResizeProcessor.cs b/src/ImageSharp46/Samplers/Processors/CompandingResizeProcessor.cs deleted file mode 100644 index 2e7a8ec9a..000000000 --- a/src/ImageSharp46/Samplers/Processors/CompandingResizeProcessor.cs +++ /dev/null @@ -1,159 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// This version will expand and compress the image to and from a linear color space during processing. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class CompandingResizeProcessor : ResamplingWeightedProcessor - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The sampler to perform the resize operation. - /// - public CompandingResizeProcessor(IResampler sampler) - : base(sampler) - { - } - - /// - public override bool Compand { get; set; } = true; - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - // Jump out, we'll deal with that later. - if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) - { - return; - } - - int width = target.Width; - int height = target.Height; - int sourceHeight = sourceRectangle.Height; - int targetX = target.Bounds.X; - int targetY = target.Bounds.Y; - int targetRight = target.Bounds.Right; - int targetBottom = target.Bounds.Bottom; - int startX = targetRectangle.X; - int endX = targetRectangle.Right; - - int minX = Math.Max(targetX, startX); - int maxX = Math.Min(targetRight, endX); - int minY = Math.Max(targetY, startY); - int maxY = Math.Min(targetBottom, endY); - - if (this.Sampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - - for (int x = minX; x < maxX; x++) - { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; - } - }); - } - - // Break out now. - return; - } - - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - Image firstPass = new Image(target.Width, source.Height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - minX = Math.Max(0, startX); - maxX = Math.Min(width, endX); - minY = Math.Max(0, startY); - maxY = Math.Min(height, endY); - - Parallel.For( - 0, - sourceHeight, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < horizontalValues.Length; i++) - { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index, y].ToVector4().Expand() * xw.Value; - } - - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - firstPassPixels[x, y] = d; - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - - for (int x = 0; x < width; x++) - { - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < verticalValues.Length; i++) - { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index].ToVector4().Expand() * yw.Value; - } - - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - targetPixels[x, y] = d; - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/BoxBlurProcessor.cs deleted file mode 100644 index 7b87a3d1b..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/BoxBlurProcessor.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - /// - /// Applies a Box blur filter to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class BoxBlurProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The maximum size of the kernel in either direction. - /// - private readonly int kernelSize; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public BoxBlurProcessor(int radius = 7) - { - this.kernelSize = (radius * 2) + 1; - this.KernelX = this.CreateBoxKernel(true); - this.KernelY = this.CreateBoxKernel(false); - } - - /// - /// Gets the horizontal gradient operator. - /// - public float[][] KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public float[][] KernelY { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - new Convolution2PassFilter(this.KernelX, this.KernelY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); - } - - /// - /// Create a 1 dimensional Box kernel. - /// - /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateBoxKernel(bool horizontal) - { - int size = this.kernelSize; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; - - if (horizontal) - { - kernel[0] = new float[size]; - } - - float sum = 0.0f; - - for (int i = 0; i < size; i++) - { - float x = 1; - sum += x; - if (horizontal) - { - kernel[0][i] = x; - } - else - { - kernel[i] = new[] { x }; - } - } - - // Normalise kernel so that the sum of all weights equals 1 - if (horizontal) - { - for (int i = 0; i < size; i++) - { - kernel[0][i] = kernel[0][i] / sum; - } - } - else - { - for (int i = 0; i < size; i++) - { - kernel[i][0] = kernel[i][0] / sum; - } - } - - return kernel; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2DFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2DFilter.cs deleted file mode 100644 index 1b77e41eb..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2DFilter.cs +++ /dev/null @@ -1,129 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Defines a filter that uses two one-dimensional matrices to perform convolution against an image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class Convolution2DFilter : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// The horizontal gradient operator. - /// The vertical gradient operator. - public Convolution2DFilter(float[][] kernelX, float[][] kernelY) - { - this.KernelX = kernelX; - this.KernelY = kernelY; - } - - /// - /// Gets the horizontal gradient operator. - /// - public float[][] KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public float[][] KernelY { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - int kernelYHeight = this.KernelY.Length; - int kernelYWidth = this.KernelY[0].Length; - int kernelXHeight = this.KernelX.Length; - int kernelXWidth = this.KernelX[0].Length; - int radiusY = kernelYHeight >> 1; - int radiusX = kernelXWidth >> 1; - - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = sourceBottom - 1; - int maxX = endX - 1; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - float rX = 0; - float gX = 0; - float bX = 0; - float rY = 0; - float gY = 0; - float bY = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelYHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelXWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - float r = currentColor.X; - float g = currentColor.Y; - float b = currentColor.Z; - - if (fy < kernelXHeight) - { - rX += this.KernelX[fy][fx] * r; - gX += this.KernelX[fy][fx] * g; - bX += this.KernelX[fy][fx] * b; - } - - if (fx < kernelYWidth) - { - rY += this.KernelY[fy][fx] * r; - gY += this.KernelY[fy][fx] * g; - bY += this.KernelY[fy][fx] * b; - } - } - } - - float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); - float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); - float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); - - Vector4 targetColor = targetPixels[x, y].ToVector4(); - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, targetColor.Z)); - targetPixels[x, y] = packed; - } - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2PassFilter.cs deleted file mode 100644 index 4cab6f0b2..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/Convolution2PassFilter.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Defines a filter that uses two one-dimensional matrices to perform two-pass convolution against an image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class Convolution2PassFilter : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// The horizontal gradient operator. - /// The vertical gradient operator. - public Convolution2PassFilter(float[][] kernelX, float[][] kernelY) - { - this.KernelX = kernelX; - this.KernelY = kernelY; - } - - /// - /// Gets the horizontal gradient operator. - /// - public float[][] KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public float[][] KernelY { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - float[][] kernelX = this.KernelX; - float[][] kernelY = this.KernelY; - - ImageBase firstPass = new Image(source.Width, source.Height); - this.ApplyConvolution(firstPass, source, sourceRectangle, startY, endY, kernelX); - this.ApplyConvolution(target, firstPass, sourceRectangle, startY, endY, kernelY); - } - - /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The index of the row within the source image to start processing. - /// The index of the row within the source image to end processing. - /// The kernel operator. - private void ApplyConvolution(ImageBase target, ImageBase source, Rectangle sourceRectangle, int startY, int endY, float[][] kernel) - { - int kernelHeight = kernel.Length; - int kernelWidth = kernel[0].Length; - int radiusY = kernelHeight >> 1; - int radiusX = kernelWidth >> 1; - - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = sourceBottom - 1; - int maxX = endX - 1; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => - { - for (int x = startX; x < endX; x++) - { - Vector4 destination = default(Vector4); - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - destination += kernel[fy][fx] * currentColor; - } - } - - TColor packed = default(TColor); - packed.PackFromVector4(destination); - targetPixels[x, y] = packed; - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/ConvolutionFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/ConvolutionFilter.cs deleted file mode 100644 index c94bee764..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/ConvolutionFilter.cs +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Defines a filter that uses a 2 dimensional matrix to perform convolution against an image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class ConvolutionFilter : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// The 2d gradient operator. - public ConvolutionFilter(float[][] kernelXY) - { - this.KernelXY = kernelXY; - } - - /// - /// Gets the 2d gradient operator. - /// - public virtual float[][] KernelXY { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - float[][] kernelX = this.KernelXY; - int kernelLength = kernelX.GetLength(0); - int radius = kernelLength >> 1; - - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = sourceBottom - 1; - int maxX = endX - 1; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - float rX = 0; - float gX = 0; - float bX = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelLength; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelLength; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - float r = currentColor.X; - float g = currentColor.Y; - float b = currentColor.Z; - - rX += kernelX[fy][fx] * r; - gX += kernelX[fy][fx] * g; - bX += kernelX[fy][fx] * b; - } - } - - float red = rX; - float green = gX; - float blue = bX; - - Vector4 targetColor = targetPixels[x, y].ToVector4(); - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, targetColor.Z)); - targetPixels[x, y] = packed; - } - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs deleted file mode 100644 index a689415f6..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - /// - /// Defines a filter that detects edges within an image using two - /// one-dimensional matrices. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public abstract class EdgeDetector2DFilter : ImageSampler, IEdgeDetectorFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Gets the horizontal gradient operator. - /// - public abstract float[][] KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public abstract float[][] KernelY { get; } - - /// - public bool Grayscale { get; set; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - // TODO: Figure out a way to pass event handlers to child classes. - new Convolution2DFilter(this.KernelX, this.KernelY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); - } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (this.Grayscale) - { - new GrayscaleBt709Processor().Apply(source, sourceRectangle); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorCompassFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorCompassFilter.cs deleted file mode 100644 index 6d5053111..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorCompassFilter.cs +++ /dev/null @@ -1,139 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Defines a filter that detects edges within an image using a eight two dimensional matrices. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public abstract class EdgeDetectorCompassFilter : ImageSampler, IEdgeDetectorFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Gets the North gradient operator - /// - public abstract float[][] North { get; } - - /// - /// Gets the NorthWest gradient operator - /// - public abstract float[][] NorthWest { get; } - - /// - /// Gets the West gradient operator - /// - public abstract float[][] West { get; } - - /// - /// Gets the SouthWest gradient operator - /// - public abstract float[][] SouthWest { get; } - - /// - /// Gets the South gradient operator - /// - public abstract float[][] South { get; } - - /// - /// Gets the SouthEast gradient operator - /// - public abstract float[][] SouthEast { get; } - - /// - /// Gets the East gradient operator - /// - public abstract float[][] East { get; } - - /// - /// Gets the NorthEast gradient operator - /// - public abstract float[][] NorthEast { get; } - - /// - public bool Grayscale { get; set; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - float[][][] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; - - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - // First run. - new ConvolutionFilter(kernels[0]).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); - - if (kernels.Length == 1) - { - return; - } - - int shiftY = startY; - int shiftX = startX; - - // Reset offset if necessary. - if (minX > 0) - { - shiftX = 0; - } - - if (minY > 0) - { - shiftY = 0; - } - - // Additional runs. - for (int i = 1; i < kernels.Length; i++) - { - ImageBase pass = new Image(source.Width, source.Height); - new ConvolutionFilter(kernels[i]).Apply(pass, source, sourceRectangle, targetRectangle, startY, endY); - - using (PixelAccessor passPixels = pass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - shiftY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - shiftX; - - // Grab the max components of the two pixels - TColor packed = default(TColor); - packed.PackFromVector4(Vector4.Max(passPixels[offsetX, offsetY].ToVector4(), targetPixels[offsetX, offsetY].ToVector4())); - targetPixels[offsetX, offsetY] = packed; - } - }); - } - } - } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (this.Grayscale) - { - new GrayscaleBt709Processor().Apply(source, sourceRectangle); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs deleted file mode 100644 index a565dbfc1..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - /// - /// Defines a filter that detects edges within an image using a single two dimensional matrix. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public abstract class EdgeDetectorFilter : ImageSampler, IEdgeDetectorFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - public bool Grayscale { get; set; } - - /// - /// Gets the 2d gradient operator. - /// - public abstract float[][] KernelXY { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - new ConvolutionFilter(this.KernelXY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); - } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (this.Grayscale) - { - new GrayscaleBt709Processor().Apply(source, sourceRectangle); - } - } - } -} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs deleted file mode 100644 index 700201ea4..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - /// - /// Provides properties and methods allowing the detection of edges within an image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public interface IEdgeDetectorFilter : IImageSampler, IEdgeDetectorFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - } - - /// - /// Provides properties and methods allowing the detection of edges within an image. - /// - public interface IEdgeDetectorFilter - { - /// - /// Gets or sets a value indicating whether to convert the image to grayscale before performing edge detection. - /// - bool Grayscale { get; set; } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs deleted file mode 100644 index cbe45f8b6..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Kayyali operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class KayyaliProcessor : EdgeDetector2DFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The horizontal gradient operator. - /// - private static readonly float[][] KayyaliX = - { - new float[] { 6, 0, -6 }, - new float[] { 0, 0, 0 }, - new float[] { -6, 0, 6 } - }; - - /// - /// The vertical gradient operator. - /// - private static readonly float[][] KayyaliY = - { - new float[] { -6, 0, 6 }, - new float[] { 0, 0, 0 }, - new float[] { 6, 0, -6 } - }; - - /// - public override float[][] KernelX => KayyaliX; - - /// - public override float[][] KernelY => KayyaliY; - } -} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KirschProcessor.cs deleted file mode 100644 index b8ead4192..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/KirschProcessor.cs +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Kirsch operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class KirschProcessor : EdgeDetectorCompassFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The North gradient operator - /// - private static readonly float[][] KirschNorth = - { - new float[] { 5, 5, 5 }, - new float[] { -3, 0, -3 }, - new float[] { -3, -3, -3 } - }; - - /// - /// The NorthWest gradient operator - /// - private static readonly float[][] KirschNorthWest = - { - new float[] { 5, 5, -3 }, - new float[] { 5, 0, -3 }, - new float[] { -3, -3, -3 } - }; - - /// - /// The West gradient operator - /// - private static readonly float[][] KirschWest = - { - new float[] { 5, -3, -3 }, - new float[] { 5, 0, -3 }, - new float[] { 5, -3, -3 } - }; - - /// - /// The SouthWest gradient operator - /// - private static readonly float[][] KirschSouthWest = - { - new float[] { -3, -3, -3 }, - new float[] { 5, 0, -3 }, - new float[] { 5, 5, -3 } - }; - - /// - /// The South gradient operator - /// - private static readonly float[][] KirschSouth = - { - new float[] { -3, -3, -3 }, - new float[] { -3, 0, -3 }, - new float[] { 5, 5, 5 } - }; - - /// - /// The SouthEast gradient operator - /// - private static readonly float[][] KirschSouthEast = - { - new float[] { -3, -3, -3 }, - new float[] { -3, 0, 5 }, - new float[] { -3, 5, 5 } - }; - - /// - /// The East gradient operator - /// - private static readonly float[][] KirschEast = - { - new float[] { -3, -3, 5 }, - new float[] { -3, 0, 5 }, - new float[] { -3, -3, 5 } - }; - - /// - /// The NorthEast gradient operator - /// - private static readonly float[][] KirschNorthEast = - { - new float[] { -3, 5, 5 }, - new float[] { -3, 0, 5 }, - new float[] { -3, -3, -3 } - }; - - /// - public override float[][] North => KirschNorth; - - /// - public override float[][] NorthWest => KirschNorthWest; - - /// - public override float[][] West => KirschWest; - - /// - public override float[][] SouthWest => KirschSouthWest; - - /// - public override float[][] South => KirschSouth; - - /// - public override float[][] SouthEast => KirschSouthEast; - - /// - public override float[][] East => KirschEast; - - /// - public override float[][] NorthEast => KirschNorthEast; - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs deleted file mode 100644 index 9fbf71674..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Laplacian 3 x 3 operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class Laplacian3X3Processor : EdgeDetectorFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The 2d gradient operator. - /// - private static readonly float[][] Laplacian3X3XY = new float[][] - { - new float[] { -1, -1, -1 }, - new float[] { -1, 8, -1 }, - new float[] { -1, -1, -1 } - }; - - /// - public override float[][] KernelXY => Laplacian3X3XY; - } -} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs deleted file mode 100644 index fdff0ec3a..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Laplacian 5 x 5 operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class Laplacian5X5Processor : EdgeDetectorFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The 2d gradient operator. - /// - private static readonly float[][] Laplacian5X5XY = - { - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, 24, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 } - }; - - /// - public override float[][] KernelXY => Laplacian5X5XY; - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs deleted file mode 100644 index 75e0efbd2..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Laplacian of Gaussian operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class LaplacianOfGaussianProcessor : EdgeDetectorFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The 2d gradient operator. - /// - private static readonly float[][] LaplacianOfGaussianXY = - { - new float[] { 0, 0, -1, 0, 0 }, - new float[] { 0, -1, -2, -1, 0 }, - new float[] { -1, -2, 16, -2, -1 }, - new float[] { 0, -1, -2, -1, 0 }, - new float[] { 0, 0, -1, 0, 0 } - }; - - /// - public override float[][] KernelXY => LaplacianOfGaussianXY; - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/PrewittProcessor.cs deleted file mode 100644 index 70b763e63..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/PrewittProcessor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Prewitt operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class PrewittProcessor : EdgeDetector2DFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The horizontal gradient operator. - /// - private static readonly float[][] PrewittX = - { - new float[] { -1, 0, 1 }, - new float[] { -1, 0, 1 }, - new float[] { -1, 0, 1 } - }; - - /// - /// The vertical gradient operator. - /// - private static readonly float[][] PrewittY = - { - new float[] { 1, 1, 1 }, - new float[] { 0, 0, 0 }, - new float[] { -1, -1, -1 } - }; - - /// - public override float[][] KernelX => PrewittX; - - /// - public override float[][] KernelY => PrewittY; - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs deleted file mode 100644 index 8eb3aac3b..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Roberts Cross operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class RobertsCrossProcessor : EdgeDetector2DFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The horizontal gradient operator. - /// - private static readonly float[][] RobertsCrossX = - { - new float[] { 1, 0 }, - new float[] { 0, -1 } - }; - - /// - /// The vertical gradient operator. - /// - private static readonly float[][] RobertsCrossY = - { - new float[] { 0, 1 }, - new float[] { -1, 0 } - }; - - /// - public override float[][] KernelX => RobertsCrossX; - - /// - public override float[][] KernelY => RobertsCrossY; - } -} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs deleted file mode 100644 index 189215f08..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Kirsch operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class RobinsonProcessor : EdgeDetectorCompassFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The North gradient operator - /// - private static readonly float[][] RobinsonNorth = - { - new float[] { 1, 2, 1 }, - new float[] { 0, 0, 0 }, - new float[] { -1, -2, -1 } - }; - - /// - /// The NorthWest gradient operator - /// - private static readonly float[][] RobinsonNorthWest = - { - new float[] { 2, 1, 0 }, - new float[] { 1, 0, -1 }, - new float[] { 0, -1, -2 } - }; - - /// - /// The West gradient operator - /// - private static readonly float[][] RobinsonWest = - { - new float[] { 1, 0, -1 }, - new float[] { 2, 0, -2 }, - new float[] { 1, 0, -1 } - }; - - /// - /// The SouthWest gradient operator - /// - private static readonly float[][] RobinsonSouthWest = - { - new float[] { 0, -1, -2 }, - new float[] { 1, 0, -1 }, - new float[] { 2, 1, 0 } - }; - - /// - /// The South gradient operator - /// - private static readonly float[][] RobinsonSouth = - { - new float[] { -1, -2, -1 }, - new float[] { 0, 0, 0 }, - new float[] { 1, 2, 1 } - }; - - /// - /// The SouthEast gradient operator - /// - private static readonly float[][] RobinsonSouthEast = - { - new float[] { -2, -1, 0 }, - new float[] { -1, 0, 1 }, - new float[] { 0, 1, 2 } - }; - - /// - /// The East gradient operator - /// - private static readonly float[][] RobinsonEast = - { - new float[] { -1, 0, 1 }, - new float[] { -2, 0, 2 }, - new float[] { -1, 0, 1 } - }; - - /// - /// The NorthEast gradient operator - /// - private static readonly float[][] RobinsonNorthEast = - { - new float[] { 0, 1, 2 }, - new float[] { -1, 0, 1 }, - new float[] { -2, -1, 0 } - }; - - /// - public override float[][] North => RobinsonNorth; - - /// - public override float[][] NorthWest => RobinsonNorthWest; - - /// - public override float[][] West => RobinsonWest; - - /// - public override float[][] SouthWest => RobinsonSouthWest; - - /// - public override float[][] South => RobinsonSouth; - - /// - public override float[][] SouthEast => RobinsonSouthEast; - - /// - public override float[][] East => RobinsonEast; - - /// - public override float[][] NorthEast => RobinsonNorthEast; - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/ScharrProcessor.cs deleted file mode 100644 index 22e7d8084..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/ScharrProcessor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Scharr operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class ScharrProcessor : EdgeDetector2DFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The horizontal gradient operator. - /// - private static readonly float[][] ScharrX = new float[3][] - { - new float[] { -3, 0, 3 }, - new float[] { -10, 0, 10 }, - new float[] { -3, 0, 3 } - }; - - /// - /// The vertical gradient operator. - /// - private static readonly float[][] ScharrY = new float[3][] - { - new float[] { 3, 10, 3 }, - new float[] { 0, 0, 0 }, - new float[] { -3, -10, -3 } - }; - - /// - public override float[][] KernelX => ScharrX; - - /// - public override float[][] KernelY => ScharrY; - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/SobelProcessor.cs deleted file mode 100644 index 7d9521c69..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// The Sobel operator filter. - /// - /// - /// The pixel format. - /// The packed format. uint, long, float. - [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class SobelProcessor : EdgeDetector2DFilter - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The horizontal gradient operator. - /// - private static readonly float[][] SobelX = - { - new float[] { -1, 0, 1 }, - new float[] { -2, 0, 2 }, - new float[] { -1, 0, 1 } - }; - - /// - /// The vertical gradient operator. - /// - private static readonly float[][] SobelY = - { - new float[] { -1, -2, -1 }, - new float[] { 0, 0, 0 }, - new float[] { 1, 2, 1 } - }; - - /// - public override float[][] KernelX => SobelX; - - /// - public override float[][] KernelY => SobelY; - } -} diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/GuassianBlurProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/GuassianBlurProcessor.cs deleted file mode 100644 index 856239385..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/GuassianBlurProcessor.cs +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - - /// - /// Applies a Gaussian blur filter to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class GuassianBlurProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The maximum size of the kernel in either direction. - /// - private readonly int kernelSize; - - /// - /// The spread of the blur. - /// - private readonly float sigma; - - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - public GuassianBlurProcessor(float sigma = 3f) - { - this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; - this.sigma = sigma; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public GuassianBlurProcessor(int radius) - { - this.kernelSize = (radius * 2) + 1; - this.sigma = radius; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - public GuassianBlurProcessor(float sigma, int radius) - { - this.kernelSize = (radius * 2) + 1; - this.sigma = sigma; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); - } - - /// - /// Gets the horizontal gradient operator. - /// - public float[][] KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public float[][] KernelY { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - new Convolution2PassFilter(this.KernelX, this.KernelY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); - } - - /// - /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function - /// - /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateGaussianKernel(bool horizontal) - { - int size = this.kernelSize; - float weight = this.sigma; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; - - if (horizontal) - { - kernel[0] = new float[size]; - } - - float sum = 0.0f; - - float midpoint = (size - 1) / 2f; - for (int i = 0; i < size; i++) - { - float x = i - midpoint; - float gx = ImageMaths.Gaussian(x, weight); - sum += gx; - if (horizontal) - { - kernel[0][i] = gx; - } - else - { - kernel[i] = new[] { gx }; - } - } - - // Normalise kernel so that the sum of all weights equals 1 - if (horizontal) - { - for (int i = 0; i < size; i++) - { - kernel[0][i] = kernel[0][i] / sum; - } - } - else - { - for (int i = 0; i < size; i++) - { - kernel[i][0] = kernel[i][0] / sum; - } - } - - return kernel; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Convolution/GuassianSharpenProcessor.cs b/src/ImageSharp46/Samplers/Processors/Convolution/GuassianSharpenProcessor.cs deleted file mode 100644 index 3c86fcfee..000000000 --- a/src/ImageSharp46/Samplers/Processors/Convolution/GuassianSharpenProcessor.cs +++ /dev/null @@ -1,180 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class GuassianSharpenProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The maximum size of the kernel in either direction. - /// - private readonly int kernelSize; - - /// - /// The spread of the blur. - /// - private readonly float sigma; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the sharpening. - /// - public GuassianSharpenProcessor(float sigma = 3f) - { - this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; - this.sigma = sigma; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public GuassianSharpenProcessor(int radius) - { - this.kernelSize = (radius * 2) + 1; - this.sigma = radius; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the sharpen. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - public GuassianSharpenProcessor(float sigma, int radius) - { - this.kernelSize = (radius * 2) + 1; - this.sigma = sigma; - this.KernelX = this.CreateGaussianKernel(true); - this.KernelY = this.CreateGaussianKernel(false); - } - - /// - /// Gets the horizontal gradient operator. - /// - public float[][] KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public float[][] KernelY { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - new Convolution2PassFilter(this.KernelX, this.KernelY).Apply(target, source, targetRectangle, sourceRectangle, startY, endY); - } - - /// - /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function - /// - /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateGaussianKernel(bool horizontal) - { - int size = this.kernelSize; - float weight = this.sigma; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; - - if (horizontal) - { - kernel[0] = new float[size]; - } - - float sum = 0; - - float midpoint = (size - 1) / 2f; - for (int i = 0; i < size; i++) - { - float x = i - midpoint; - float gx = ImageMaths.Gaussian(x, weight); - sum += gx; - if (horizontal) - { - kernel[0][i] = gx; - } - else - { - kernel[i] = new[] { gx }; - } - } - - // Invert the kernel for sharpening. - int midpointRounded = (int)midpoint; - - if (horizontal) - { - for (int i = 0; i < size; i++) - { - if (i == midpointRounded) - { - // Calculate central value - kernel[0][i] = (2f * sum) - kernel[0][i]; - } - else - { - // invert value - kernel[0][i] = -kernel[0][i]; - } - } - } - else - { - for (int i = 0; i < size; i++) - { - if (i == midpointRounded) - { - // Calculate central value - kernel[i][0] = (2 * sum) - kernel[i][0]; - } - else - { - // invert value - kernel[i][0] = -kernel[i][0]; - } - } - } - - // Normalise kernel so that the sum of all weights equals 1 - if (horizontal) - { - for (int i = 0; i < size; i++) - { - kernel[0][i] = kernel[0][i] / sum; - } - } - else - { - for (int i = 0; i < size; i++) - { - kernel[i][0] = kernel[i][0] / sum; - } - } - - return kernel; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/CropProcessor.cs b/src/ImageSharp46/Samplers/Processors/CropProcessor.cs deleted file mode 100644 index 0e8510837..000000000 --- a/src/ImageSharp46/Samplers/Processors/CropProcessor.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Threading.Tasks; - - /// - /// Provides methods to allow the cropping of an image. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class CropProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - int startX = targetRectangle.X; - int endX = targetRectangle.Right; - int sourceX = sourceRectangle.X; - int sourceY = sourceRectangle.Y; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => - { - for (int x = startX; x < endX; x++) - { - targetPixels[x, y] = sourcePixels[x + sourceX, y + sourceY]; - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageSharp46/Samplers/Processors/EntropyCropProcessor.cs deleted file mode 100644 index dbb99d1c4..000000000 --- a/src/ImageSharp46/Samplers/Processors/EntropyCropProcessor.cs +++ /dev/null @@ -1,107 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Threading.Tasks; - - /// - /// Provides methods to allow the cropping of an image to preserve areas of highest - /// entropy. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class EntropyCropProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The rectangle for cropping - /// - private Rectangle cropRectangle; - - /// - /// Initializes a new instance of the class. - /// - /// The threshold to split the image. Must be between 0 and 1. - /// - /// is less than 0 or is greater than 1. - /// - public EntropyCropProcessor(float threshold) - { - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - this.Value = threshold; - } - - /// - /// Gets the threshold value. - /// - public float Value { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - // Jump out, we'll deal with that later. - if (source.Bounds == target.Bounds) - { - return; - } - - int targetY = this.cropRectangle.Y; - int targetBottom = this.cropRectangle.Bottom; - int startX = this.cropRectangle.X; - int endX = this.cropRectangle.Right; - - int minY = Math.Max(targetY, startY); - int maxY = Math.Min(targetBottom, endY); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = startX; x < endX; x++) - { - targetPixels[x - startX, y - targetY] = sourcePixels[x, y]; - } - }); - } - } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - ImageBase temp = new Image(source.Width, source.Height); - - // Detect the edges. - new SobelProcessor().Apply(temp, source, sourceRectangle); - - // Apply threshold binarization filter. - new BinaryThresholdProcessor(.5f).Apply(temp, sourceRectangle); - - // Search for the first white pixels - Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); - - // Reset the target pixel to the correct size. - target.SetPixels(rectangle.Width, rectangle.Height, new TColor[rectangle.Width * rectangle.Height]); - this.cropRectangle = rectangle; - } - - /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - // Copy the pixels over. - if (source.Bounds == target.Bounds) - { - target.ClonePixels(target.Width, target.Height, source.Pixels); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/FlipProcessor.cs b/src/ImageSharp46/Samplers/Processors/FlipProcessor.cs deleted file mode 100644 index d6efccec7..000000000 --- a/src/ImageSharp46/Samplers/Processors/FlipProcessor.cs +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the flipping of an image around its center point. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class FlipProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// The used to perform flipping. - public FlipProcessor(FlipType flipType) - { - this.FlipType = flipType; - } - - /// - /// Gets the used to perform flipping. - /// - public FlipType FlipType { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - target.ClonePixels(target.Width, target.Height, source.Pixels); - - switch (this.FlipType) - { - // No default needed as we have already set the pixels. - case FlipType.Vertical: - this.FlipX(target); - break; - case FlipType.Horizontal: - this.FlipY(target); - break; - } - } - - /// - /// Swaps the image at the X-axis, which goes horizontally through the middle - /// at half the height of the image. - /// - /// Target image to apply the process to. - private void FlipX(ImageBase target) - { - int width = target.Width; - int height = target.Height; - int halfHeight = (int)Math.Ceiling(target.Height * .5F); - Image temp = new Image(width, height); - temp.ClonePixels(width, height, target.Pixels); - - using (PixelAccessor targetPixels = target.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) - { - Parallel.For( - 0, - halfHeight, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - int newY = height - y - 1; - targetPixels[x, y] = tempPixels[x, newY]; - targetPixels[x, newY] = tempPixels[x, y]; - } - }); - } - } - - /// - /// Swaps the image at the Y-axis, which goes vertically through the middle - /// at half of the width of the image. - /// - /// Target image to apply the process to. - private void FlipY(ImageBase target) - { - int width = target.Width; - int height = target.Height; - int halfWidth = (int)Math.Ceiling(width * .5F); - Image temp = new Image(width, height); - temp.ClonePixels(width, height, target.Pixels); - - using (PixelAccessor targetPixels = target.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < halfWidth; x++) - { - int newX = width - x - 1; - targetPixels[x, y] = tempPixels[newX, y]; - targetPixels[newX, y] = tempPixels[x, y]; - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/IImageSampler.cs b/src/ImageSharp46/Samplers/Processors/IImageSampler.cs deleted file mode 100644 index 891a810d1..000000000 --- a/src/ImageSharp46/Samplers/Processors/IImageSampler.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - /// - /// Encapsulates methods to alter the pixels of an image. The processor creates a copy of the original image to operate on. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public interface IImageSampler : IImageProcessor - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Applies the process to the specified portion of the specified . - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The method keeps the source image unchanged and returns the - /// the result of image processing filter as new image. - /// - /// - /// is null or is null. - /// - /// - /// doesnt fit the dimension of the image. - /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle); - - /// - /// Applies the process to the specified portion of the specified at the specified - /// location and with the specified size. - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// The target width. - /// The target height. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The method keeps the source image unchanged and returns the - /// the result of image process as new image. - /// - void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle); - } -} diff --git a/src/ImageSharp46/Samplers/Processors/ImageSampler.cs b/src/ImageSharp46/Samplers/Processors/ImageSampler.cs deleted file mode 100644 index 98ffe3b88..000000000 --- a/src/ImageSharp46/Samplers/Processors/ImageSampler.cs +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - - /// - /// Encapsulates methods to alter the pixels of an image. The processor creates a copy of the original image to operate on. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public abstract class ImageSampler : ImageProcessor, IImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - { - try - { - this.OnApply(target, source, target.Bounds, sourceRectangle); - - this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); - - this.AfterApply(target, source, target.Bounds, sourceRectangle); - } - catch (Exception ex) - { - throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); - } - } - - /// - public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - { - try - { - TColor[] pixels = new TColor[width * height]; - target.SetPixels(width, height, pixels); - - // Ensure we always have bounds. - if (sourceRectangle == Rectangle.Empty) - { - sourceRectangle = source.Bounds; - } - - if (targetRectangle == Rectangle.Empty) - { - targetRectangle = target.Bounds; - } - - this.OnApply(target, source, targetRectangle, sourceRectangle); - - this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); - - this.AfterApply(target, source, target.Bounds, sourceRectangle); - } - catch (Exception ex) - { - throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); - } - } - - /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The index of the row within the source image to start processing. - /// The index of the row within the source image to end processing. - /// - /// The method keeps the source image unchanged and returns the the result of image process as new image. - /// - public abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY); - - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - } - - /// - /// This method is called after the process is applied to prepare the processor. - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/Matrix3x2Processor.cs b/src/ImageSharp46/Samplers/Processors/Matrix3x2Processor.cs deleted file mode 100644 index a9b460d43..000000000 --- a/src/ImageSharp46/Samplers/Processors/Matrix3x2Processor.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System.Numerics; - - /// - /// Provides methods to transform an image using a . - /// - /// The pixel format. - /// The packed format. uint, long, float. - public abstract class Matrix3x2Processor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Creates a new target to contain the results of the matrix transform. - /// - /// Target image to apply the process to. - /// The source rectangle. - /// The processing matrix. - protected static void CreateNewTarget(ImageBase target, Rectangle sourceRectangle, Matrix3x2 processMatrix) - { - Matrix3x2 sizeMatrix; - if (Matrix3x2.Invert(processMatrix, out sizeMatrix)) - { - Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix); - target.SetPixels(rectangle.Width, rectangle.Height, new TColor[rectangle.Width * rectangle.Height]); - } - } - - /// - /// Gets a transform matrix adjusted to center upon the target image bounds. - /// - /// Target image to apply the process to. - /// The source image. - /// The transform matrix. - /// - /// The . - /// - protected static Matrix3x2 GetCenteredMatrix(ImageBase target, ImageBase source, Matrix3x2 matrix) - { - Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-target.Width * .5F, -target.Height * .5F); - Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); - return (translationToTargetCenter * matrix) * translateToSourceCenter; - } - } -} diff --git a/src/ImageSharp46/Samplers/Processors/OilPaintingProcessor.cs b/src/ImageSharp46/Samplers/Processors/OilPaintingProcessor.cs deleted file mode 100644 index ee0d4121a..000000000 --- a/src/ImageSharp46/Samplers/Processors/OilPaintingProcessor.cs +++ /dev/null @@ -1,151 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// An to apply an oil painting effect to an . - /// - /// Adapted from by Dewald Esterhuizen. - /// The pixel format. - /// The packed format. uint, long, float. - public class OilPaintingProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image. - /// The number of neighbouring pixels used in calculating each individual pixel value. - public OilPaintingProcessor(int levels, int brushSize) - { - Guard.MustBeGreaterThan(levels, 0, nameof(levels)); - Guard.MustBeGreaterThan(brushSize, 0, nameof(brushSize)); - - this.Levels = levels; - this.BrushSize = brushSize; - } - - /// - /// Gets the intensity levels - /// - public int Levels { get; } - - /// - /// Gets the brush size - /// - public int BrushSize { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int radius = this.BrushSize >> 1; - int levels = this.Levels; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = startX; x < endX; x++) - { - int maxIntensity = 0; - int maxIndex = 0; - - int[] intensityBin = new int[levels]; - float[] redBin = new float[levels]; - float[] blueBin = new float[levels]; - float[] greenBin = new float[levels]; - - for (int fy = 0; fy <= radius; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; - - // Skip the current row - if (offsetY < minY) - { - continue; - } - - // Outwith the current bounds so break. - if (offsetY >= maxY) - { - break; - } - - for (int fx = 0; fx <= radius; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - - // Skip the column - if (offsetX < 0) - { - continue; - } - - if (offsetX < maxX) - { - // ReSharper disable once AccessToDisposedClosure - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - - float sourceRed = color.X; - float sourceBlue = color.Z; - float sourceGreen = color.Y; - - int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); - - intensityBin[currentIntensity] += 1; - blueBin[currentIntensity] += sourceBlue; - greenBin[currentIntensity] += sourceGreen; - redBin[currentIntensity] += sourceRed; - - if (intensityBin[currentIntensity] > maxIntensity) - { - maxIntensity = intensityBin[currentIntensity]; - maxIndex = currentIntensity; - } - } - } - - float red = Math.Abs(redBin[maxIndex] / maxIntensity); - float green = Math.Abs(greenBin[maxIndex] / maxIntensity); - float blue = Math.Abs(blueBin[maxIndex] / maxIntensity); - - Vector4 targetColor = targetPixels[x, y].ToVector4(); - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, targetColor.Z)); - targetPixels[x, y] = packed; - } - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/PixelateProcessor.cs b/src/ImageSharp46/Samplers/Processors/PixelateProcessor.cs deleted file mode 100644 index 2c1875909..000000000 --- a/src/ImageSharp46/Samplers/Processors/PixelateProcessor.cs +++ /dev/null @@ -1,111 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - - /// - /// An to pixelate the colors of an . - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class PixelateProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// The size of the pixels. Must be greater than 0. - /// - /// is less than 0 or equal to 0. - /// - public PixelateProcessor(int size) - { - Guard.MustBeGreaterThan(size, 0, nameof(size)); - this.Value = size; - } - - /// - /// Gets or the pixel size. - /// - public int Value { get; } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int size = this.Value; - int offset = this.Value / 2; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - - // Get the range on the y-plane to choose from. - IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.ForEach( - range, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - int offsetPy = offset; - - for (int x = minX; x < maxX; x += size) - { - int offsetX = x - startX; - int offsetPx = offset; - - // Make sure that the offset is within the boundary of the image. - while (offsetY + offsetPy >= maxY) - { - offsetPy--; - } - - while (x + offsetPx >= maxX) - { - offsetPx--; - } - - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - TColor pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; - - // For each pixel in the pixelate size, set it to the centre color. - for (int l = offsetY; l < offsetY + size && l < maxY; l++) - { - for (int k = offsetX; k < offsetX + size && k < maxX; k++) - { - targetPixels[k, l] = pixel; - } - } - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/ResamplingWeightedProcessor.cs b/src/ImageSharp46/Samplers/Processors/ResamplingWeightedProcessor.cs deleted file mode 100644 index 1d9d3da35..000000000 --- a/src/ImageSharp46/Samplers/Processors/ResamplingWeightedProcessor.cs +++ /dev/null @@ -1,171 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// Adapted from - /// - /// The pixel format. - /// The packed format. uint, long, float. - public abstract class ResamplingWeightedProcessor : ImageSampler - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The sampler to perform the resize operation. - /// - protected ResamplingWeightedProcessor(IResampler sampler) - { - Guard.NotNull(sampler, nameof(sampler)); - - this.Sampler = sampler; - } - - /// - /// Gets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets or sets the horizontal weights. - /// - protected Weights[] HorizontalWeights { get; set; } - - /// - /// Gets or sets the vertical weights. - /// - protected Weights[] VerticalWeights { get; set; } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (!(this.Sampler is NearestNeighborResampler)) - { - this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); - this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); - } - } - - /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - // Copy the pixels over. - if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) - { - target.ClonePixels(target.Width, target.Height, source.Pixels); - } - } - - /// - /// Computes the weights to apply at each pixel when resizing. - /// - /// The destination section size. - /// The source section size. - /// - /// The . - /// - protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) - { - float ratio = (float)sourceSize / destinationSize; - float scale = ratio; - - if (scale < 1F) - { - scale = 1F; - } - - IResampler sampler = this.Sampler; - float radius = (float)Math.Ceiling(scale * sampler.Radius); - Weights[] result = new Weights[destinationSize]; - - for (int i = 0; i < destinationSize; i++) - { - float center = ((i + .5F) * ratio) - .5F; - - // Keep inside bounds. - int left = (int)Math.Ceiling(center - radius); - if (left < 0) - { - left = 0; - } - - int right = (int)Math.Floor(center + radius); - if (right > sourceSize - 1) - { - right = sourceSize - 1; - } - - float sum = 0; - result[i] = new Weights(); - Weight[] weights = new Weight[right - left + 1]; - - for (int j = left; j <= right; j++) - { - float weight = sampler.GetValue((j - center) / scale); - sum += weight; - weights[j - left] = new Weight(j, weight); - } - - // Normalise, best to do it here rather than in the pixel loop later on. - if (sum > 0) - { - for (int w = 0; w < weights.Length; w++) - { - weights[w].Value = weights[w].Value / sum; - } - } - - result[i].Values = weights; - } - - return result; - } - - /// - /// Represents the weight to be added to a scaled pixel. - /// - protected class Weight - { - /// - /// Initializes a new instance of the class. - /// - /// The index. - /// The value. - public Weight(int index, float value) - { - this.Index = index; - this.Value = value; - } - - /// - /// Gets the pixel index. - /// - public int Index { get; } - - /// - /// Gets or sets the result of the interpolation algorithm. - /// - public float Value { get; set; } - } - - /// - /// Represents a collection of weights and their sum. - /// - protected class Weights - { - /// - /// Gets or sets the values. - /// - public Weight[] Values { get; set; } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/ResizeProcessor.cs b/src/ImageSharp46/Samplers/Processors/ResizeProcessor.cs deleted file mode 100644 index 82e949148..000000000 --- a/src/ImageSharp46/Samplers/Processors/ResizeProcessor.cs +++ /dev/null @@ -1,158 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// - /// - /// This version and the have been separated out to improve performance. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class ResizeProcessor : ResamplingWeightedProcessor - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The sampler to perform the resize operation. - /// - public ResizeProcessor(IResampler sampler) - : base(sampler) - { - } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - // Jump out, we'll deal with that later. - if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) - { - return; - } - - int width = target.Width; - int height = target.Height; - int sourceHeight = sourceRectangle.Height; - int targetX = target.Bounds.X; - int targetY = target.Bounds.Y; - int targetRight = target.Bounds.Right; - int targetBottom = target.Bounds.Bottom; - int startX = targetRectangle.X; - int endX = targetRectangle.Right; - - int minX = Math.Max(targetX, startX); - int maxX = Math.Min(targetRight, endX); - int minY = Math.Max(targetY, startY); - int maxY = Math.Min(targetBottom, endY); - - if (this.Sampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - - for (int x = minX; x < maxX; x++) - { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; - } - }); - } - - // Break out now. - return; - } - - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - Image firstPass = new Image(target.Width, source.Height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - minX = Math.Max(0, startX); - maxX = Math.Min(width, endX); - minY = Math.Max(0, startY); - maxY = Math.Min(height, endY); - - Parallel.For( - 0, - sourceHeight, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < horizontalValues.Length; i++) - { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index, y].ToVector4() * xw.Value; - } - - TColor d = default(TColor); - d.PackFromVector4(destination); - firstPassPixels[x, y] = d; - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - - for (int x = 0; x < width; x++) - { - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < verticalValues.Length; i++) - { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index].ToVector4() * yw.Value; - } - - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/RotateProcessor.cs b/src/ImageSharp46/Samplers/Processors/RotateProcessor.cs deleted file mode 100644 index 0fdc187bd..000000000 --- a/src/ImageSharp46/Samplers/Processors/RotateProcessor.cs +++ /dev/null @@ -1,215 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the rotating of images. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class RotateProcessor : Matrix3x2Processor - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The tranform matrix to apply. - /// - private Matrix3x2 processMatrix; - - /// - /// Gets or sets the angle of processMatrix in degrees. - /// - public float Angle { get; set; } - - /// - /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image. - /// - public bool Expand { get; set; } = true; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - const float Epsilon = .0001F; - - if (Math.Abs(this.Angle) < Epsilon || Math.Abs(this.Angle - 90) < Epsilon || Math.Abs(this.Angle - 180) < Epsilon || Math.Abs(this.Angle - 270) < Epsilon) - { - return; - } - - this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle); - if (this.Expand) - { - CreateNewTarget(target, sourceRectangle, this.processMatrix); - } - } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - if (this.OptimizedApply(target, source)) - { - return; - } - - int height = target.Height; - int width = target.Width; - Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - Point transformedPoint = Point.Rotate(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } - } - }); - } - } - - /// - /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. - /// - /// The target image. - /// The source image. - /// - private bool OptimizedApply(ImageBase target, ImageBase source) - { - const float Epsilon = .0001F; - if (Math.Abs(this.Angle) < Epsilon) - { - target.ClonePixels(target.Width, target.Height, source.Pixels); - return true; - } - - if (Math.Abs(this.Angle - 90) < Epsilon) - { - this.Rotate90(target, source); - return true; - } - - if (Math.Abs(this.Angle - 180) < Epsilon) - { - this.Rotate180(target, source); - return true; - } - - if (Math.Abs(this.Angle - 270) < Epsilon) - { - this.Rotate270(target, source); - return true; - } - - return false; - } - - /// - /// Rotates the image 270 degrees clockwise at the centre point. - /// - /// The target image. - /// The source image. - private void Rotate270(ImageBase target, ImageBase source) - { - int width = source.Width; - int height = source.Height; - Image temp = new Image(height, width); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - tempPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } - - target.SetPixels(height, width, temp.Pixels); - } - - /// - /// Rotates the image 180 degrees clockwise at the centre point. - /// - /// The target image. - /// The source image. - private void Rotate180(ImageBase target, ImageBase source) - { - int width = source.Width; - int height = source.Height; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - int newX = width - x - 1; - int newY = height - y - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } - } - - /// - /// Rotates the image 90 degrees clockwise at the centre point. - /// - /// The target image. - /// The source image. - private void Rotate90(ImageBase target, ImageBase source) - { - int width = source.Width; - int height = source.Height; - Image temp = new Image(height, width); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - tempPixels[newX, x] = sourcePixels[x, y]; - } - }); - } - - target.SetPixels(height, width, temp.Pixels); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Processors/SkewProcessor.cs b/src/ImageSharp46/Samplers/Processors/SkewProcessor.cs deleted file mode 100644 index b21301a87..000000000 --- a/src/ImageSharp46/Samplers/Processors/SkewProcessor.cs +++ /dev/null @@ -1,79 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the skewing of images. - /// - /// The pixel format. - /// The packed format. uint, long, float. - public class SkewProcessor : Matrix3x2Processor - where TColor : struct, IPackedPixel - where TPacked : struct - { - /// - /// The tranform matrix to apply. - /// - private Matrix3x2 processMatrix; - - /// - /// Gets or sets the angle of rotation along the x-axis in degrees. - /// - public float AngleX { get; set; } - - /// - /// Gets or sets the angle of rotation along the y-axis in degrees. - /// - public float AngleY { get; set; } - - /// - /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image. - /// - public bool Expand { get; set; } = true; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY); - if (this.Expand) - { - CreateNewTarget(target, sourceRectangle, this.processMatrix); - } - } - - /// - public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - int height = target.Height; - int width = target.Width; - Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - Point transformedPoint = Point.Skew(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp46/Samplers/Resamplers/BicubicResampler.cs b/src/ImageSharp46/Samplers/Resamplers/BicubicResampler.cs deleted file mode 100644 index a0bfc1413..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/BicubicResampler.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the bicubic kernel algorithm W(x) as described on - /// Wikipedia - /// A commonly used algorithm within imageprocessing that preserves sharpness better than triangle interpolation. - /// - public class BicubicResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - float result = 0; - - // Given the coefficient "a" as -0.5F. - if (x <= 1F) - { - // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; - result = (((1.5F * x) - 2.5F) * x * x) + 1; - } - else if (x < 2F) - { - // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); - result = (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; - } - - return result; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/BoxResampler.cs b/src/ImageSharp46/Samplers/Resamplers/BoxResampler.cs deleted file mode 100644 index adb238a19..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/BoxResampler.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the box algorithm. Similar to nearest neighbour when upscaling. - /// When downscaling the pixels will average, merging together. - /// - public class BoxResampler : IResampler - { - /// - public float Radius => 0.5F; - - /// - public float GetValue(float x) - { - if (x > -0.5F && x <= 0.5F) - { - return 1; - } - - return 0; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/CatmullRomResampler.cs b/src/ImageSharp46/Samplers/Resamplers/CatmullRomResampler.cs deleted file mode 100644 index 7751aa08b..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/CatmullRomResampler.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. - /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large - /// scale image enlargements that a 'Lagrange' filter can produce. - /// - /// - public class CatmullRomResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0; - const float C = 0.5F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/HermiteResampler.cs b/src/ImageSharp46/Samplers/Resamplers/HermiteResampler.cs deleted file mode 100644 index 9af35e8e8..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/HermiteResampler.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processors -{ - /// - /// The Hermite filter is type of smoothed triangular interpolation Filter, - /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. - /// - /// - public class HermiteResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0F; - const float C = 0F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/IResampler.cs b/src/ImageSharp46/Samplers/Resamplers/IResampler.cs deleted file mode 100644 index ce100a8c7..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/IResampler.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Encapsulates an interpolation algorithm for resampling images. - /// - public interface IResampler - { - /// - /// Gets the radius in which to sample pixels. - /// - float Radius { get; } - - /// - /// Gets the result of the interpolation algorithm. - /// - /// The value to process. - /// - /// The - /// - float GetValue(float x); - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp46/Samplers/Resamplers/Lanczos2Resampler.cs deleted file mode 100644 index 3d25cf859..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/Lanczos2Resampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 2 pixels. - /// - public class Lanczos2Resampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 2F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F); - } - - return 0F; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp46/Samplers/Resamplers/Lanczos3Resampler.cs deleted file mode 100644 index f771de1a5..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/Lanczos3Resampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 3 pixels. - /// - public class Lanczos3Resampler : IResampler - { - /// - public float Radius => 3; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 3F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F); - } - - return 0F; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp46/Samplers/Resamplers/Lanczos5Resampler.cs deleted file mode 100644 index 4584b258f..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/Lanczos5Resampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 5 pixels. - /// - public class Lanczos5Resampler : IResampler - { - /// - public float Radius => 5; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 5F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F); - } - - return 0F; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp46/Samplers/Resamplers/Lanczos8Resampler.cs deleted file mode 100644 index 03f5a6d7b..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/Lanczos8Resampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 8 pixels. - /// - public class Lanczos8Resampler : IResampler - { - /// - public float Radius => 8; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 8F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F); - } - - return 0F; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp46/Samplers/Resamplers/MitchellNetravaliResampler.cs deleted file mode 100644 index f4bc3a6f2..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/MitchellNetravaliResampler.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the mitchell algorithm as described on - /// Wikipedia - /// - public class MitchellNetravaliResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.3333333F; - const float C = 0.3333333F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp46/Samplers/Resamplers/NearestNeighborResampler.cs deleted file mode 100644 index ec2417f9c..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/NearestNeighborResampler.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the nearest neighbour algorithm. This uses an unscaled filter - /// which will select the closest pixel to the new pixels position. - /// - public class NearestNeighborResampler : IResampler - { - /// - public float Radius => 1; - - /// - public float GetValue(float x) - { - return x; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageSharp46/Samplers/Resamplers/RobidouxResampler.cs deleted file mode 100644 index 6ef90bda4..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/RobidouxResampler.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the Robidoux algorithm. - /// - /// - public class RobidouxResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.37821575509399867F; - const float C = 0.31089212245300067F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp46/Samplers/Resamplers/RobidouxSharpResampler.cs deleted file mode 100644 index 8755bf3e4..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/RobidouxSharpResampler.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the Robidoux Sharp algorithm. - /// - /// - public class RobidouxSharpResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.2620145123990142F; - const float C = 0.3689927438004929F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/SplineResampler.cs b/src/ImageSharp46/Samplers/Resamplers/SplineResampler.cs deleted file mode 100644 index 921ea23a8..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/SplineResampler.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the spline algorithm. - /// - /// - public class SplineResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 1F; - const float C = 0F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/TriangleResampler.cs b/src/ImageSharp46/Samplers/Resamplers/TriangleResampler.cs deleted file mode 100644 index c53d5a146..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/TriangleResampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the triangle (bilinear) algorithm. - /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, - /// so that one can calculate and assign appropriate intensity values to pixels. - /// - public class TriangleResampler : IResampler - { - /// - public float Radius => 1; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 1F) - { - return 1F - x; - } - - return 0F; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resamplers/WelchResampler.cs b/src/ImageSharp46/Samplers/Resamplers/WelchResampler.cs deleted file mode 100644 index eb5506151..000000000 --- a/src/ImageSharp46/Samplers/Resamplers/WelchResampler.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// The function implements the welch algorithm. - /// - /// - public class WelchResampler : IResampler - { - /// - public float Radius => 3; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 3F) - { - return ImageMaths.SinC(x) * (1F - (x * x / 9.0F)); - } - - return 0F; - } - } -} diff --git a/src/ImageSharp46/Samplers/Resize.cs b/src/ImageSharp46/Samplers/Resize.cs deleted file mode 100644 index ee3da5ec6..000000000 --- a/src/ImageSharp46/Samplers/Resize.cs +++ /dev/null @@ -1,168 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Resizes an image in accordance with the given . - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to resize. - /// The resize options. - /// The - /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, ResizeOptions options) - where TColor : struct, IPackedPixel - where TPacked : struct - { - // Ensure size is populated across both dimensions. - if (options.Size.Width == 0 && options.Size.Height > 0) - { - options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height); - } - - if (options.Size.Height == 0 && options.Size.Width > 0) - { - options.Size = new Size(options.Size.Width, source.Height * options.Size.Width / source.Width); - } - - Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(source, options); - - return Resize(source, options.Size.Width, options.Size.Height, options.Sampler, source.Bounds, targetRectangle, options.Compand); - } - - /// - /// Resizes an image to the given width and height. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to resize. - /// The target image width. - /// The target image height. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Resize(source, width, height, new BicubicResampler(), false); - } - - /// - /// Resizes an image to the given width and height. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to resize. - /// The target image width. - /// The target image height. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, bool compand) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Resize(source, width, height, new BicubicResampler(), compand); - } - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Resize(source, width, height, sampler, false); - } - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand); - } - - /// - /// Resizes an image to the given width and height with the given sampler and - /// source rectangle. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false) - where TColor : struct, IPackedPixel - where TPacked : struct - { - if (width == 0 && height > 0) - { - width = source.Width * height / source.Height; - targetRectangle.Width = width; - } - - if (height == 0 && width > 0) - { - height = source.Height * width / source.Width; - targetRectangle.Height = height; - } - - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - ResamplingWeightedProcessor processor; - - if (compand) - { - processor = new CompandingResizeProcessor(sampler); - } - else - { - processor = new ResizeProcessor(sampler); - } - - return source.Process(width, height, sourceRectangle, targetRectangle, processor); - } - } -} diff --git a/src/ImageSharp46/Samplers/Rotate.cs b/src/ImageSharp46/Samplers/Rotate.cs deleted file mode 100644 index f8f0a59e8..000000000 --- a/src/ImageSharp46/Samplers/Rotate.cs +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to rotate. - /// The angle in degrees to perform the rotation. - /// The - public static Image Rotate(this Image source, float degrees) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Rotate(source, degrees, true); - } - - /// - /// Rotates and flips an image by the given instructions. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to rotate. - /// The to perform the rotation. - /// The - public static Image Rotate(this Image source, RotateType rotateType) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Rotate(source, (float)rotateType, false); - } - - /// - /// Rotates an image by the given angle in degrees. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to rotate. - /// The angle in degrees to perform the rotation. - /// Whether to expand the image to fit the rotated result. - /// The - public static Image Rotate(this Image source, float degrees, bool expand) - where TColor : struct, IPackedPixel - where TPacked : struct - { - RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; - return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); - } - } -} diff --git a/src/ImageSharp46/Samplers/RotateFlip.cs b/src/ImageSharp46/Samplers/RotateFlip.cs deleted file mode 100644 index 5adef987c..000000000 --- a/src/ImageSharp46/Samplers/RotateFlip.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Rotates and flips an image by the given instructions. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to rotate, flip, or both. - /// The to perform the rotation. - /// The to perform the flip. - /// The - public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return source.Rotate(rotateType).Flip(flipType); - } - } -} diff --git a/src/ImageSharp46/Samplers/Skew.cs b/src/ImageSharp46/Samplers/Skew.cs deleted file mode 100644 index 4aa756842..000000000 --- a/src/ImageSharp46/Samplers/Skew.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Skews an image by the given angles in degrees, expanding the image to fit the skewed result. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to skew. - /// The angle in degrees to perform the rotation along the x-axis. - /// The angle in degrees to perform the rotation along the y-axis. - /// The - public static Image Skew(this Image source, float degreesX, float degreesY) - where TColor : struct, IPackedPixel - where TPacked : struct - { - return Skew(source, degreesX, degreesY, true); - } - - /// - /// Skews an image by the given angles in degrees. - /// - /// The pixel format. - /// The packed format. uint, long, float. - /// The image to skew. - /// The angle in degrees to perform the rotation along the x-axis. - /// The angle in degrees to perform the rotation along the y-axis. - /// Whether to expand the image to fit the skewed result. - /// The - public static Image Skew(this Image source, float degreesX, float degreesY, bool expand) - where TColor : struct, IPackedPixel - where TPacked : struct - { - SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; - return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); - } - } -} diff --git a/src/ImageSharp46/app.config b/src/ImageSharp46/app.config deleted file mode 100644 index 5e95024db..000000000 --- a/src/ImageSharp46/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ImageSharp46/packages.config b/src/ImageSharp46/packages.config deleted file mode 100644 index 6ace6583b..000000000 --- a/src/ImageSharp46/packages.config +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Benchmark/DecodeJpegBenchmark.cs b/tests/ImageSharp.Tests46/Benchmark/DecodeJpegBenchmark.cs deleted file mode 100644 index 6de820f8b..000000000 --- a/tests/ImageSharp.Tests46/Benchmark/DecodeJpegBenchmark.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using ImageSharp.Tests; -using Xunit; -using Xunit.Abstractions; - -namespace ImageSharp.Tests46.Benchmark -{ - using System.Drawing; - using System.IO; - - using CoreImage = ImageSharp.Image; - using CoreSize = ImageSharp.Size; - - public class DecodeJpegBenchmark - { - private static byte[] jpegBytes = File.ReadAllBytes(TestImages.Jpeg.Calliphora); - - private ITestOutputHelper _output; - - public DecodeJpegBenchmark(ITestOutputHelper output) - { - _output = output; - } - - private void DoBenchmark(int times, Action action, [CallerMemberName]string method = null) - { - _output.WriteLine($"{method} x {times} ... "); - Stopwatch sw = Stopwatch.StartNew(); - for (int i = 0; i < times; i++) - { - using (MemoryStream memoryStream = new MemoryStream(jpegBytes)) - { - action(memoryStream); - } - } - sw.Stop(); - var millis = sw.ElapsedMilliseconds; - - _output.WriteLine($"{method} finished in {millis}ms"); - } - - [Theory] - [InlineData(10)] - [InlineData(100)] - public void JpegSystemDrawing(int times) - { - DoBenchmark(times, memoryStream => - { - Image image = Image.FromStream(memoryStream); - image.Dispose(); - }); - } - - [Theory] - [InlineData(10)] - [InlineData(100)] - public void JpegCore(int times) - { - DoBenchmark(times, memoryStream => - { - CoreImage image = new CoreImage(memoryStream); - }); - } - - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Colors/ColorConversionTests.cs b/tests/ImageSharp.Tests46/Colors/ColorConversionTests.cs deleted file mode 100644 index f1b6d5f9b..000000000 --- a/tests/ImageSharp.Tests46/Colors/ColorConversionTests.cs +++ /dev/null @@ -1,511 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System; - using System.Diagnostics.CodeAnalysis; - - using Xunit; - - /// - /// Test conversion between the various color structs. - /// - /// - /// Output values have been compared with - /// and for accuracy. - /// - public class ColorConversionTests - { - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToYCbCr() - { - // White - Color color = Color.White; - YCbCr yCbCr = color; - - Assert.Equal(255, yCbCr.Y); - Assert.Equal(128, yCbCr.Cb); - Assert.Equal(128, yCbCr.Cr); - - // Black - Color color2 = Color.Black; - YCbCr yCbCr2 = color2; - Assert.Equal(0, yCbCr2.Y); - Assert.Equal(128, yCbCr2.Cb); - Assert.Equal(128, yCbCr2.Cr); - - // Gray - Color color3 = Color.Gray; - YCbCr yCbCr3 = color3; - Assert.Equal(128, yCbCr3.Y); - Assert.Equal(128, yCbCr3.Cb); - Assert.Equal(128, yCbCr3.Cr); - - //Assert.Equal(255, yCbCr.Y, 0); - //Assert.Equal(128, yCbCr.Cb, 0); - //Assert.Equal(128, yCbCr.Cr, 0); - - //// Black - //Color color2 = Color.Black; - //YCbCr yCbCr2 = color2; - //Assert.Equal(0, yCbCr2.Y, 0); - //Assert.Equal(128, yCbCr2.Cb, 0); - //Assert.Equal(128, yCbCr2.Cr, 0); - - //// Gray - //Color color3 = Color.Gray; - //YCbCr yCbCr3 = color3; - //Assert.Equal(128, yCbCr3.Y, 0); - //Assert.Equal(128, yCbCr3.Cb, 0); - //Assert.Equal(128, yCbCr3.Cr, 0); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void YCbCrToColor() - { - // White - YCbCr yCbCr = new YCbCr(255, 128, 128); - Color color = yCbCr; - - Assert.Equal(255, color.R); - Assert.Equal(255, color.G); - Assert.Equal(255, color.B); - Assert.Equal(255, color.A); - - // Black - YCbCr yCbCr2 = new YCbCr(0, 128, 128); - Color color2 = yCbCr2; - - Assert.Equal(0, color2.R); - Assert.Equal(0, color2.G); - Assert.Equal(0, color2.B); - Assert.Equal(255, color2.A); - - // Gray - YCbCr yCbCr3 = new YCbCr(128, 128, 128); - Color color3 = yCbCr3; - - Assert.Equal(128, color3.R); - Assert.Equal(128, color3.G); - Assert.Equal(128, color3.B); - Assert.Equal(255, color3.A); - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-xyz - /// - [Fact] - public void ColorToCieXyz() - { - // White - Color color = Color.White; - CieXyz ciexyz = color; - - Assert.Equal(95.05f, ciexyz.X, 3); - Assert.Equal(100.0f, ciexyz.Y, 3); - Assert.Equal(108.900f, ciexyz.Z, 3); - - // Black - Color color2 = Color.Black; - CieXyz ciexyz2 = color2; - Assert.Equal(0, ciexyz2.X, 3); - Assert.Equal(0, ciexyz2.Y, 3); - Assert.Equal(0, ciexyz2.Z, 3); - - // Gray - Color color3 = Color.Gray; - CieXyz ciexyz3 = color3; - Assert.Equal(20.518, ciexyz3.X, 3); - Assert.Equal(21.586, ciexyz3.Y, 3); - Assert.Equal(23.507, ciexyz3.Z, 3); - - // Cyan - Color color4 = Color.Cyan; - CieXyz ciexyz4 = color4; - Assert.Equal(53.810f, ciexyz4.X, 3); - Assert.Equal(78.740f, ciexyz4.Y, 3); - Assert.Equal(106.970f, ciexyz4.Z, 3); - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-xyz - /// - [Fact] - public void CieXyzToColor() - { - // Dark moderate pink. - CieXyz ciexyz = new CieXyz(13.337f, 9.297f, 14.727f); - Color color = ciexyz; - - Assert.Equal(128, color.R); - Assert.Equal(64, color.G); - Assert.Equal(106, color.B); - - // Ochre - CieXyz ciexyz2 = new CieXyz(31.787f, 26.147f, 4.885f); - Color color2 = ciexyz2; - - Assert.Equal(204, color2.R); - Assert.Equal(119, color2.G); - Assert.Equal(34, color2.B); - - // Black - CieXyz ciexyz3 = new CieXyz(0, 0, 0); - Color color3 = ciexyz3; - - Assert.Equal(0, color3.R); - Assert.Equal(0, color3.G); - Assert.Equal(0, color3.B); - - //// Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // CieXyz ciexyz4 = color4; - // Assert.Equal(color4, (Color)ciexyz4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToHsv() - { - // Black - Color b = Color.Black; - Hsv h = b; - - Assert.Equal(0, h.H, 1); - Assert.Equal(0, h.S, 1); - Assert.Equal(0, h.V, 1); - - // White - Color color = Color.White; - Hsv hsv = color; - - Assert.Equal(0f, hsv.H, 1); - Assert.Equal(0f, hsv.S, 1); - Assert.Equal(1f, hsv.V, 1); - - // Dark moderate pink. - Color color2 = new Color(128, 64, 106); - Hsv hsv2 = color2; - - Assert.Equal(320.6f, hsv2.H, 1); - Assert.Equal(0.5f, hsv2.S, 1); - Assert.Equal(0.502f, hsv2.V, 2); - - // Ochre. - Color color3 = new Color(204, 119, 34); - Hsv hsv3 = color3; - - Assert.Equal(30f, hsv3.H, 1); - Assert.Equal(0.833f, hsv3.S, 3); - Assert.Equal(0.8f, hsv3.V, 1); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void HsvToColor() - { - // Dark moderate pink. - Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f); - Color color = hsv; - - Assert.Equal(color.R, 128); - Assert.Equal(color.G, 64); - Assert.Equal(color.B, 106); - - // Ochre - Hsv hsv2 = new Hsv(30, 0.833f, 0.8f); - Color color2 = hsv2; - - Assert.Equal(color2.R, 204); - Assert.Equal(color2.G, 119); - Assert.Equal(color2.B, 34); - - // White - Hsv hsv3 = new Hsv(0, 0, 1); - Color color3 = hsv3; - - Assert.Equal(color3.B, 255); - Assert.Equal(color3.G, 255); - Assert.Equal(color3.R, 255); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Hsv hsv4 = color4; - // Assert.Equal(color4, (Color)hsv4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToHsl() - { - // Black - Color b = Color.Black; - Hsl h = b; - - Assert.Equal(0, h.H, 1); - Assert.Equal(0, h.S, 1); - Assert.Equal(0, h.L, 1); - - // White - Color color = Color.White; - Hsl hsl = color; - - Assert.Equal(0f, hsl.H, 1); - Assert.Equal(0f, hsl.S, 1); - Assert.Equal(1f, hsl.L, 1); - - // Dark moderate pink. - Color color2 = new Color(128, 64, 106); - Hsl hsl2 = color2; - - Assert.Equal(320.6f, hsl2.H, 1); - Assert.Equal(0.33f, hsl2.S, 1); - Assert.Equal(0.376f, hsl2.L, 2); - - // Ochre. - Color color3 = new Color(204, 119, 34); - Hsl hsl3 = color3; - - Assert.Equal(30f, hsl3.H, 1); - Assert.Equal(0.714f, hsl3.S, 3); - Assert.Equal(0.467f, hsl3.L, 3); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void HslToColor() - { - // Dark moderate pink. - Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f); - Color color = hsl; - - Assert.Equal(color.R, 128); - Assert.Equal(color.G, 64); - Assert.Equal(color.B, 106); - - // Ochre - Hsl hsl2 = new Hsl(30, 0.714f, 0.467f); - Color color2 = hsl2; - - Assert.Equal(color2.R, 204); - Assert.Equal(color2.G, 119); - Assert.Equal(color2.B, 34); - - // White - Hsl hsl3 = new Hsl(0, 0, 1); - Color color3 = hsl3; - - Assert.Equal(color3.R, 255); - Assert.Equal(color3.G, 255); - Assert.Equal(color3.B, 255); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Hsl hsl4 = color4; - // Assert.Equal(color4, (Color)hsl4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToCmyk() - { - // White - Color color = Color.White; - Cmyk cmyk = color; - - Assert.Equal(0, cmyk.C, 1); - Assert.Equal(0, cmyk.M, 1); - Assert.Equal(0, cmyk.Y, 1); - Assert.Equal(0, cmyk.K, 1); - - // Black - Color color2 = Color.Black; - Cmyk cmyk2 = color2; - Assert.Equal(0, cmyk2.C, 1); - Assert.Equal(0, cmyk2.M, 1); - Assert.Equal(0, cmyk2.Y, 1); - Assert.Equal(1, cmyk2.K, 1); - - // Gray - Color color3 = Color.Gray; - Cmyk cmyk3 = color3; - Assert.Equal(0f, cmyk3.C, 1); - Assert.Equal(0f, cmyk3.M, 1); - Assert.Equal(0f, cmyk3.Y, 1); - Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters. - - // Cyan - Color color4 = Color.Cyan; - Cmyk cmyk4 = color4; - Assert.Equal(1, cmyk4.C, 1); - Assert.Equal(0f, cmyk4.M, 1); - Assert.Equal(0f, cmyk4.Y, 1); - Assert.Equal(0f, cmyk4.K, 1); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void CmykToColor() - { - // Dark moderate pink. - Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f); - Color color = cmyk; - - Assert.Equal(color.R, 128); - Assert.Equal(color.G, 64); - Assert.Equal(color.B, 106); - - // Ochre - Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f); - Color color2 = cmyk2; - - Assert.Equal(color2.R, 204); - Assert.Equal(color2.G, 119); - Assert.Equal(color2.B, 34); - - // White - Cmyk cmyk3 = new Cmyk(0, 0, 0, 0); - Color color3 = cmyk3; - - Assert.Equal(color3.R, 255); - Assert.Equal(color3.G, 255); - Assert.Equal(color3.B, 255); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Cmyk cmyk4 = color4; - // Assert.Equal(color4, (Color)cmyk4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-lab - /// - [Fact] - public void ColorToCieLab() - { - // White - Color color = Color.White; - CieLab cielab = color; - - Assert.Equal(100, cielab.L, 3); - Assert.Equal(0.005, cielab.A, 3); - Assert.Equal(-0.010, cielab.B, 3); - - // Black - Color color2 = Color.Black; - CieLab cielab2 = color2; - Assert.Equal(0, cielab2.L, 3); - Assert.Equal(0, cielab2.A, 3); - Assert.Equal(0, cielab2.B, 3); - - // Gray - Color color3 = Color.Gray; - CieLab cielab3 = color3; - Assert.Equal(53.585, cielab3.L, 3); - Assert.Equal(0.003, cielab3.A, 3); - Assert.Equal(-0.006, cielab3.B, 3); - - // Cyan - Color color4 = Color.Cyan; - CieLab cielab4 = color4; - Assert.Equal(91.117, cielab4.L, 3); - Assert.Equal(-48.080, cielab4.A, 3); - Assert.Equal(-14.138, cielab4.B, 3); - } - - /// - /// Tests the implicit conversion from to . - /// - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-lab - [Fact] - public void CieLabToColor() - { - // Dark moderate pink. - CieLab cielab = new CieLab(36.5492f, 33.3173f, -12.0615f); - Color color = cielab; - - Assert.Equal(color.R, 128); - Assert.Equal(color.G, 64); - Assert.Equal(color.B, 106); - - // Ochre - CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f); - Color color2 = cielab2; - - Assert.Equal(color2.R, 204); - Assert.Equal(color2.G, 119); - Assert.Equal(color2.B, 34); - - // Black - CieLab cielab3 = new CieLab(0, 0, 0); - Color color3 = cielab3; - - Assert.Equal(color3.R, 0); - Assert.Equal(color3.G, 0); - Assert.Equal(color3.B, 0); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // CieLab cielab4 = color4; - // Assert.Equal(color4, (Color)cielab4); - //} - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Colors/ColorTests.cs b/tests/ImageSharp.Tests46/Colors/ColorTests.cs deleted file mode 100644 index 23ebbec98..000000000 --- a/tests/ImageSharp.Tests46/Colors/ColorTests.cs +++ /dev/null @@ -1,135 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using System.Numerics; - -namespace ImageSharp.Tests -{ - using System; - using Xunit; - - /// - /// Tests the struct. - /// - public class ColorTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Color color1 = new Color(0, 0, 0); - Color color2 = new Color(0, 0, 0, 1F); - Color color3 = new Color("#000"); - Color color4 = new Color("#000F"); - Color color5 = new Color("#000000"); - Color color6 = new Color("#000000FF"); - - Assert.Equal(color1, color2); - Assert.Equal(color1, color3); - Assert.Equal(color1, color4); - Assert.Equal(color1, color5); - Assert.Equal(color1, color6); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Color color1 = new Color(255, 0, 0, 255); - Color color2 = new Color(0, 0, 0, 255); - Color color3 = new Color("#000"); - Color color4 = new Color("#000000"); - Color color5 = new Color("#FF000000"); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color1, color3); - Assert.NotEqual(color1, color4); - Assert.NotEqual(color1, color5); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Color color1 = new Color(1, .1f, .133f, .864f); - Assert.Equal(255, color1.R); - Assert.Equal((byte)Math.Round(.1f * 255), color1.G); - Assert.Equal((byte)Math.Round(.133f * 255), color1.B); - Assert.Equal((byte)Math.Round(.864f * 255), color1.A); - - Color color2 = new Color(1, .1f, .133f); - Assert.Equal(255, color2.R); - Assert.Equal(Math.Round(.1f * 255), color2.G); - Assert.Equal(Math.Round(.133f * 255), color2.B); - Assert.Equal(255, color2.A); - - Color color3 = new Color("#FF0000"); - Assert.Equal(255, color3.R); - Assert.Equal(0, color3.G); - Assert.Equal(0, color3.B); - Assert.Equal(255, color3.A); - - Color color4 = new Color(new Vector3(1, .1f, .133f)); - Assert.Equal(255, color4.R); - Assert.Equal(Math.Round(.1f * 255), color4.G); - Assert.Equal(Math.Round(.133f * 255), color4.B); - Assert.Equal(255, color4.A); - - Color color5 = new Color(new Vector4(1, .1f, .133f, .5f)); - Assert.Equal(255, color5.R); - Assert.Equal(Math.Round(.1f * 255), color5.G); - Assert.Equal(Math.Round(.133f * 255), color5.B); - Assert.Equal(Math.Round(.5f * 255), color5.A); - } - - /// - /// Tests whether FromHex and ToHex work correctly. - /// - [Fact] - public void FromAndToHex() - { - Color color = Color.FromHex("#AABBCCDD"); - Assert.Equal(170, color.R); - Assert.Equal(187, color.G); - Assert.Equal(204, color.B); - Assert.Equal(221, color.A); - - color.A = 170; - color.B = 187; - color.G = 204; - color.R = 221; - - Assert.Equal("DDCCBBAA", color.ToHex()); - - color.R = 0; - - Assert.Equal("00CCBBAA", color.ToHex()); - - color.A = 255; - - Assert.Equal("00CCBBFF", color.ToHex()); - } - - /// - /// Tests that the individual byte elements are layed out in RGBA order. - /// - [Fact] - public unsafe void ByteLayout() - { - Color color = new Color(1, 2, 3, 4); - byte* colorBase = (byte*)&color; - Assert.Equal(1, colorBase[0]); - Assert.Equal(2, colorBase[1]); - Assert.Equal(3, colorBase[2]); - Assert.Equal(4, colorBase[3]); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/DctSandbox.cs b/tests/ImageSharp.Tests46/DctSandbox.cs deleted file mode 100644 index d3b1bacb2..000000000 --- a/tests/ImageSharp.Tests46/DctSandbox.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System.Numerics; -using System.Text; -using ImageSharp.Formats; -using Xunit; -using Xunit.Abstractions; - -namespace ImageSharp.Tests -{ - public class DctSandbox - { - - private ITestOutputHelper Output { get; } - - public DctSandbox(ITestOutputHelper output) - { - Output = output; - } - - private float[] CreateTestData() - { - float[] result =new float[64]; - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 8; j++) - { - result[i*8 + j] = i*10 + j; - } - } - return result; - } - - private void Print(float[] data) - { - StringBuilder bld = new StringBuilder(); - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 8; j++) - { - bld.Append($"{data[i * 8 + j],3} "); - } - bld.AppendLine(); - } - - Output.WriteLine(bld.ToString()); - } - - [Fact] - public void Mennyi() - { - Output.WriteLine(Vector.IsHardwareAccelerated.ToString()); - Output.WriteLine(Vector.Count.ToString()); - } - - [Fact] - public void CheckTestData() - { - var data = CreateTestData(); - - Print(data); - } - - [Fact] - public void Load_Store() - { - var data = CreateTestData(); - - var m = MagicDCT.Load(data, 1, 1); - m = Matrix4x4.Transpose(m); - - MagicDCT.Store(m, data, 4, 4); - - Print(data); - } - - [Fact] - public void Transpose8x8() - { - var data = CreateTestData(); - - Span result = new Span(64); - - MagicDCT.Transpose8x8(data, result); - - Print(result.Data); - } - - [Fact] - public void Transpose8x8_Inplace() - { - var data = CreateTestData(); - - MagicDCT.Transpose8x8(data); - - Print(data); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/FileTestBase.cs b/tests/ImageSharp.Tests46/FileTestBase.cs deleted file mode 100644 index b9a226fc8..000000000 --- a/tests/ImageSharp.Tests46/FileTestBase.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.Collections.Generic; - using System.IO; - - /// - /// The test base class for reading and writing to files. - /// - public abstract class FileTestBase - { - /// - /// The collection of image files to test against. - /// - protected static readonly List Files = new List - { - // new TestFile(TestImages.Png.P1), // Perf: Enable for local testing only - // new TestFile(TestImages.Png.Pd), // Perf: Enable for local testing only - // new TestFile(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only - new TestFile(TestImages.Jpeg.Calliphora), - // new TestFile(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only - new TestFile(TestImages.Jpeg.Turtle), - // new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only - // new TestFile(TestImages.Jpeg.Progress), // Perf: Enable for local testing only - // new TestFile(TestImages.Jpeg.GammaDalaiLamaGray), // Perf: Enable for local testing only - new TestFile(TestImages.Bmp.Car), - // new TestFile(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only - // new TestFile(TestImages.Png.Blur), // Perf: Enable for local testing only - // new TestFile(TestImages.Png.Indexed), // Perf: Enable for local testing only - new TestFile(TestImages.Png.Splash), - new TestFile(TestImages.Gif.Rings), - // new TestFile(TestImages.Gif.Giphy) // Perf: Enable for local testing only - }; - - protected string CreateOutputDirectory(string path) - { - path = "TestOutput/" + path; - - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - - return path; - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests46/Formats/Bmp/BitmapTests.cs deleted file mode 100644 index c91b0ad1b..000000000 --- a/tests/ImageSharp.Tests46/Formats/Bmp/BitmapTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using ImageSharp.Formats; - -namespace ImageSharp.Tests -{ - using System.IO; - - using Formats; - - using Xunit; - - public class BitmapTests : FileTestBase - { - public static readonly TheoryData BitsPerPixel - = new TheoryData - { - BmpBitsPerPixel.Pixel24 , - BmpBitsPerPixel.Pixel32 - }; - - [Theory] - [MemberData("BitsPerPixel")] - public void BitmapCanEncodeDifferentBitRates(BmpBitsPerPixel bitsPerPixel) - { - string path = CreateOutputDirectory("Bmp"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileNameWithoutExtension(bitsPerPixel); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}.bmp")) - { - image.Save(output, new BmpEncoder { BitsPerPixel = bitsPerPixel }); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests46/Formats/GeneralFormatTests.cs deleted file mode 100644 index 7d88ca6e1..000000000 --- a/tests/ImageSharp.Tests46/Formats/GeneralFormatTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System; - using System.IO; - - using Xunit; - - public class GeneralFormatTests : FileTestBase - { - [Fact] - public void ResolutionShouldChange() - { - string path = CreateOutputDirectory("Resolution"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.VerticalResolution = 150; - image.HorizontalResolution = 150; - image.Save(output); - } - } - } - - [Fact] - public void ImageCanEncodeToString() - { - string path = CreateOutputDirectory("ToString"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; - File.WriteAllText(filename, image.ToBase64String()); - } - } - - [Fact] - public void DecodeThenEncodeImageFromStreamShouldSucceed() - { - string path = CreateOutputDirectory("Encode"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Save(output); - } - } - } - - [Fact] - public void QuantizeImageShouldPreserveMaximumColorPrecision() - { - string path = CreateOutputDirectory("Quantize"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - // Copy the original pixels to save decoding time. - Color[] pixels = new Color[image.Width * image.Height]; - Array.Copy(image.Pixels, pixels, image.Pixels.Length); - - using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) - { - image.Quantize(Quantization.Octree) - .Save(output, image.CurrentImageFormat); - - } - - image.SetPixels(image.Width, image.Height, pixels); - using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) - { - image.Quantize(Quantization.Wu) - .Save(output, image.CurrentImageFormat); - } - - image.SetPixels(image.Width, image.Height, pixels); - using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) - { - image.Quantize(Quantization.Palette) - .Save(output, image.CurrentImageFormat); - } - } - } - - [Fact] - public void ImageCanConvertFormat() - { - string path = CreateOutputDirectory("Format"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) - { - image.SaveAsGif(output); - } - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) - { - image.SaveAsBmp(output); - } - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) - { - image.SaveAsJpeg(output); - } - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.SaveAsPng(output); - } - } - } - - [Fact] - public void ImageShouldPreservePixelByteOrderWhenSerialized() - { - string path = CreateOutputDirectory("Serialized"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - byte[] serialized; - using (MemoryStream memoryStream = new MemoryStream()) - { - image.Save(memoryStream); - memoryStream.Flush(); - serialized = memoryStream.ToArray(); - } - - using (MemoryStream memoryStream = new MemoryStream(serialized)) - { - Image image2 = new Image(memoryStream); - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image2.Save(output); - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests46/Formats/Jpg/JpegTests.cs deleted file mode 100644 index 57bce1504..000000000 --- a/tests/ImageSharp.Tests46/Formats/Jpg/JpegTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using ImageSharp.Formats; -using Xunit; -using Xunit.Abstractions; - -namespace ImageSharp.Tests.Formats.Jpg -{ - public class JpegTests - { - - public const string TestOutputDirectory = "TestOutput/Jpeg"; - - private ITestOutputHelper Output { get; } - - public JpegTests(ITestOutputHelper output) - { - Output = output; - } - - protected string CreateTestOutputFile(string fileName) - { - if (!Directory.Exists(TestOutputDirectory)) - { - Directory.CreateDirectory(TestOutputDirectory); - } - - //string id = Guid.NewGuid().ToString().Substring(0, 4); - - string ext = Path.GetExtension(fileName); - fileName = Path.GetFileNameWithoutExtension(fileName); - - return $"{TestOutputDirectory}/{fileName}{ext}"; - } - - protected Stream CreateOutputStream(string fileName) - { - fileName = CreateTestOutputFile(fileName); - Output?.WriteLine("Opened for write: "+fileName); - return File.OpenWrite(fileName); - } - - public static IEnumerable AllJpegFiles - => TestImages.Jpeg.All.Select(fn => new object[] {fn}); - - [Theory] - [MemberData(nameof(AllJpegFiles))] - public void OpenJpeg_SaveBmp(string jpegPath) - { - string bmpFileName = Path.GetFileNameWithoutExtension(jpegPath) + ".bmp"; - - using (var inputStream = File.OpenRead(jpegPath)) - { - var image = new Image(inputStream); - - using (var outputStream = CreateOutputStream(bmpFileName)) - { - image.Save(outputStream, new BmpFormat()); - } - } - } - - public static IEnumerable AllBmpFiles - => TestImages.Jpeg.All.Select(fn => new object[] {fn}); - - [Theory] - [MemberData(nameof(AllBmpFiles))] - public void OpenBmp_SaveJpeg(string bmpPath) - { - string jpegPath = Path.GetFileNameWithoutExtension(bmpPath) + ".jpeg"; - - using (var inputStream = File.OpenRead(bmpPath)) - { - var image = new Image(inputStream); - - using (var outputStream = CreateOutputStream(jpegPath)) - { - image.Save(outputStream, new JpegFormat()); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs deleted file mode 100644 index 3c37ce898..000000000 --- a/tests/ImageSharp.Tests46/Formats/Png/PngTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using ImageSharp.Formats; - -namespace ImageSharp.Tests -{ - using System.IO; - - using Formats; - - using Xunit; - - public class PngTests : FileTestBase - { - - - [Fact] - public void ImageCanSaveIndexedPng() - { - string path = CreateOutputDirectory("Png"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.Quality = 256; - image.Save(output, new PngFormat()); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Helpers/GuardTests.cs b/tests/ImageSharp.Tests46/Helpers/GuardTests.cs deleted file mode 100644 index ba6d5687c..000000000 --- a/tests/ImageSharp.Tests46/Helpers/GuardTests.cs +++ /dev/null @@ -1,242 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests.Helpers -{ - using System; - using System.Diagnostics.CodeAnalysis; - - using Xunit; - - /// - /// Tests the helper. - /// - public class GuardTests - { - /// - /// Tests that the method throws when the argument is null. - /// - [Fact] - public void NotNullThrowsWhenArgIsNull() - { - Assert.Throws(() => Guard.NotNull(null, "foo")); - } - - /// - /// Tests that the method throws when the argument name is empty. - /// - [Fact] - public void NotNullThrowsWhenArgNameEmpty() - { - Assert.Throws(() => Guard.NotNull(null, string.Empty)); - } - - /// - /// Tests that the method throws when the argument is empty. - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1122:UseStringEmptyForEmptyStrings", Justification = "Reviewed. Suppression is OK here.")] - public void NotEmptyThrowsWhenEmpty() - { - Assert.Throws(() => Guard.NotNullOrEmpty("", string.Empty)); - } - - /// - /// Tests that the method throws when the argument is whitespace. - /// - [Fact] - public void NotEmptyThrowsWhenWhitespace() - { - Assert.Throws(() => Guard.NotNullOrEmpty(" ", string.Empty)); - } - - /// - /// Tests that the method throws when the argument name is null. - /// - [Fact] - public void NotEmptyThrowsWhenParameterNameNull() - { - Assert.Throws(() => Guard.NotNullOrEmpty(null, null)); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void LessThanThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeLessThan(1, 0, "foo")); - } - - /// - /// Tests that the method throws when the argument is equal. - /// - [Fact] - public void LessThanThrowsWhenArgIsEqual() - { - Assert.Throws(() => Guard.MustBeLessThan(1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void LessThanOrEqualToThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeLessThanOrEqualTo(1, 0, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is less. - /// - [Fact] - public void LessThanOrEqualToDoesNotThrowWhenArgIsLess() - { - Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(0, 1, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void LessThanOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(1, 1, "foo")); - Assert.Equal(1, 1); - Assert.Null(ex); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void GreaterThanThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeGreaterThan(0, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void GreaterThanThrowsWhenArgIsEqual() - { - Assert.Throws(() => Guard.MustBeGreaterThan(1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument name is greater. - /// - [Fact] - public void GreaterThanOrEqualToThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeGreaterThanOrEqualTo(0, 1, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is less. - /// - [Fact] - public void GreaterThanOrEqualToDoesNotThrowWhenArgIsGreater() - { - Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 0, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void GreaterThanOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 1, "foo")); - Assert.Equal(1, 1); - Assert.Null(ex); - } - - /// - /// Tests that the method throws when the argument is less. - /// - [Fact] - public void BetweenOrEqualToThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(-2, -1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void BetweenOrEqualToThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(2, -1, 1, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void BetweenOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(1, 1, 1, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void BetweenOrEqualToDoesNotThrowWhenArgIsBetween() - { - Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(0, -1, 1, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method throws when the argument is false. - /// - [Fact] - public void IsTrueThrowsWhenArgIsFalse() - { - Assert.Throws(() => Guard.IsTrue(false, "foo", "message")); - } - - /// - /// Tests that the method does not throw when the argument is true. - /// - [Fact] - public void IsTrueDoesThrowsWhenArgIsTrue() - { - Exception ex = Record.Exception(() => Guard.IsTrue(true, "foo", "message")); - Assert.Null(ex); - } - - /// - /// Tests that the method throws when the argument is true. - /// - [Fact] - public void IsFalseThrowsWhenArgIsFalse() - { - Assert.Throws(() => Guard.IsFalse(true, "foo", "message")); - } - - /// - /// Tests that the method does not throw when the argument is false. - /// - [Fact] - public void IsFalseDoesThrowsWhenArgIsTrue() - { - Exception ex = Record.Exception(() => Guard.IsFalse(false, "foo", "message")); - Assert.Null(ex); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Image/ImagePropertyTests.cs b/tests/ImageSharp.Tests46/Image/ImagePropertyTests.cs deleted file mode 100644 index 8b4c6ea10..000000000 --- a/tests/ImageSharp.Tests46/Image/ImagePropertyTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System; - using Xunit; - - /// - /// Tests the class. - /// - public class ImagePropertyTests - { - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreEqual() - { - ImageProperty property1 = new ImageProperty("Foo", "Bar"); - ImageProperty property2 = new ImageProperty("Foo", "Bar"); - ImageProperty property3 = null; - - Assert.Equal(property1, property2); - Assert.True(property1 == property2); - Assert.Equal(property3, null); - } - - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreNotEqual() - { - ImageProperty property1 = new ImageProperty("Foo", "Bar"); - ImageProperty property2 = new ImageProperty("Foo", "Foo"); - ImageProperty property3 = new ImageProperty("Bar", "Bar"); - ImageProperty property4 = new ImageProperty("Foo", null); - - Assert.False(property1.Equals("Foo")); - - Assert.NotEqual(property1, null); - - Assert.NotEqual(property1, property2); - Assert.True(property1 != property2); - - Assert.NotEqual(property1, property3); - Assert.NotEqual(property1, property4); - } - - /// - /// Tests whether the constructor throws an exception when the property name is null or empty. - /// - [Fact] - public void ConstructorThrowsWhenNameIsNullOrEmpty() - { - Assert.Throws(() => new ImageProperty(null, "Foo")); - - Assert.Throws(() => new ImageProperty(string.Empty, "Foo")); - } - - /// - /// Tests whether the constructor correctly assigns properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - ImageProperty property = new ImageProperty("Foo", null); - Assert.Equal("Foo", property.Name); - Assert.Equal(null, property.Value); - - property = new ImageProperty("Foo", string.Empty); - Assert.Equal(string.Empty, property.Value); - } - } -} diff --git a/tests/ImageSharp.Tests46/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests46/Image/PixelAccessorTests.cs deleted file mode 100644 index ab71d8de7..000000000 --- a/tests/ImageSharp.Tests46/Image/PixelAccessorTests.cs +++ /dev/null @@ -1,168 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Xunit; - - /// - /// Tests the class. - /// - public class PixelAccessorTests - { - [Fact] - public void CopyFromZYX() - { - CopyFromZYX(new Image(1, 1)); - } - - [Fact] - public void CopyFromZYXOptimized() - { - CopyFromZYX(new Image(1, 1)); - } - - [Fact] - public void CopyFromZYXW() - { - CopyFromZYXW(new Image(1, 1)); - } - - [Fact] - public void CopyFromZYXWOptimized() - { - CopyFromZYXW(new Image(1, 1)); - } - - [Fact] - public void CopyToZYX() - { - CopyToZYX(new Image(1, 1)); - } - - [Fact] - public void CopyToZYXOptimized() - { - CopyToZYX(new Image(1, 1)); - } - - [Fact] - public void CopyToZYXW() - { - CopyToZYXW(new Image(1, 1)); - } - - [Fact] - public void CopyToZYXWOptimized() - { - CopyToZYXW(new Image(1, 1)); - } - - private static void CopyFromZYX(Image image) - where TColor : struct, IPackedPixel - where TPacked : struct - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 255; - - using (PixelRow row = new PixelRow(1, ComponentOrder.ZYX)) - { - row.Bytes[0] = blue; - row.Bytes[1] = green; - row.Bytes[2] = red; - - pixels.CopyFrom(row, 0); - - Color color = (Color) (object) pixels[0, 0]; - Assert.Equal(red, color.R); - Assert.Equal(green, color.G); - Assert.Equal(blue, color.B); - Assert.Equal(alpha, color.A); - } - } - } - - private static void CopyFromZYXW(Image image) - where TColor : struct, IPackedPixel - where TPacked : struct - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 4; - - using (PixelRow row = new PixelRow(1, ComponentOrder.ZYXW)) - { - row.Bytes[0] = blue; - row.Bytes[1] = green; - row.Bytes[2] = red; - row.Bytes[3] = alpha; - - pixels.CopyFrom(row, 0); - - Color color = (Color) (object) pixels[0, 0]; - Assert.Equal(red, color.R); - Assert.Equal(green, color.G); - Assert.Equal(blue, color.B); - Assert.Equal(alpha, color.A); - } - } - } - - private static void CopyToZYX(Image image) - where TColor : struct, IPackedPixel - where TPacked : struct - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - - using (PixelRow row = new PixelRow(1, ComponentOrder.ZYX)) - { - pixels[0, 0] = (TColor) (object) new Color(red, green, blue); - - pixels.CopyTo(row, 0); - - Assert.Equal(blue, row.Bytes[0]); - Assert.Equal(green, row.Bytes[1]); - Assert.Equal(red, row.Bytes[2]); - } - } - } - - private static void CopyToZYXW(Image image) - where TColor : struct, IPackedPixel - where TPacked : struct - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 4; - - using (PixelRow row = new PixelRow(1, ComponentOrder.ZYXW)) - { - pixels[0, 0] = (TColor) (object) new Color(red, green, blue, alpha); - - pixels.CopyTo(row, 0); - - Assert.Equal(blue, row.Bytes[0]); - Assert.Equal(green, row.Bytes[1]); - Assert.Equal(red, row.Bytes[2]); - Assert.Equal(alpha, row.Bytes[3]); - } - } - } - } -} diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj deleted file mode 100644 index ab80f3d93..000000000 --- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - Debug - AnyCPU - {635E0A15-3893-4763-A7F6-FCCFF85BCCA4} - Library - Properties - ImageSharp.Tests - ImageSharp.Tests - v4.6.1 - 512 - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - - - - - - ..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll - True - - - - - - - - - ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll - True - - - ..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll - True - - - ..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {fba0b5f6-09c2-4317-8ef6-6adb9b20e6b1} - ImageSharp46 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/PointTests.cs b/tests/ImageSharp.Tests46/Numerics/PointTests.cs deleted file mode 100644 index 82b26b946..000000000 --- a/tests/ImageSharp.Tests46/Numerics/PointTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class PointTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Point first = new Point(100, 100); - Point second = new Point(100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Point first = new Point(0, 100); - Point second = new Point(100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the point constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Point first = new Point(4, 5); - Assert.Equal(4, first.X); - Assert.Equal(5, first.Y); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/RationalTests.cs b/tests/ImageSharp.Tests46/Numerics/RationalTests.cs deleted file mode 100644 index 3d80b88fe..000000000 --- a/tests/ImageSharp.Tests46/Numerics/RationalTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class RationalTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Rational r1 = new Rational(3, 2); - Rational r2 = new Rational(3, 2); - - Assert.Equal(r1, r2); - Assert.True(r1 == r2); - - Rational r3 = new Rational(7.55); - Rational r4 = new Rational(755, 100); - Rational r5 = new Rational(151, 20); - - Assert.Equal(r3, r4); - Assert.Equal(r4, r5); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Rational first = new Rational(0, 100); - Rational second = new Rational(100, 100); - - Assert.NotEqual(first, second); - Assert.True(first != second); - } - - /// - /// Tests whether the Rational constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Rational rational = new Rational(7, 55); - Assert.Equal(7U, rational.Numerator); - Assert.Equal(55U, rational.Denominator); - - rational = new Rational(755, 100); - Assert.Equal(151U, rational.Numerator); - Assert.Equal(20U, rational.Denominator); - - rational = new Rational(755, 100, false); - Assert.Equal(755U, rational.Numerator); - Assert.Equal(100U, rational.Denominator); - - rational = new Rational(-7.55); - Assert.Equal(151U, rational.Numerator); - Assert.Equal(20U, rational.Denominator); - - rational = new Rational(7); - Assert.Equal(7U, rational.Numerator); - Assert.Equal(1U, rational.Denominator); - } - - [Fact] - public void Fraction() - { - Rational first = new Rational(1.0 / 1600); - Rational second = new Rational(1.0 / 1600, true); - Assert.False(first.Equals(second)); - } - - [Fact] - public void ToDouble() - { - Rational rational = new Rational(0, 0); - Assert.Equal(double.NaN, rational.ToDouble()); - - rational = new Rational(2, 0); - Assert.Equal(double.PositiveInfinity, rational.ToDouble()); - } - - [Fact] - public void ToStringRepresention() - { - Rational rational = new Rational(0, 0); - Assert.Equal("[ Indeterminate ]", rational.ToString()); - - rational = new Rational(double.PositiveInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new Rational(double.NegativeInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new Rational(0, 1); - Assert.Equal("0", rational.ToString()); - - rational = new Rational(2, 1); - Assert.Equal("2", rational.ToString()); - - rational = new Rational(1, 2); - Assert.Equal("1/2", rational.ToString()); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/RectangleTests.cs b/tests/ImageSharp.Tests46/Numerics/RectangleTests.cs deleted file mode 100644 index 2f9ad3d37..000000000 --- a/tests/ImageSharp.Tests46/Numerics/RectangleTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class RectangleTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Rectangle first = new Rectangle(1, 1, 100, 100); - Rectangle second = new Rectangle(1, 1, 100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Rectangle first = new Rectangle(1, 1, 0, 100); - Rectangle second = new Rectangle(1, 1, 100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the rectangle constructors correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Rectangle first = new Rectangle(1, 1, 50, 100); - Assert.Equal(1, first.X); - Assert.Equal(1, first.Y); - Assert.Equal(50, first.Width); - Assert.Equal(100, first.Height); - Assert.Equal(1, first.Top); - Assert.Equal(51, first.Right); - Assert.Equal(101, first.Bottom); - Assert.Equal(1, first.Left); - - Rectangle second = new Rectangle(new Point(1, 1), new Size(50, 100)); - Assert.Equal(1, second.X); - Assert.Equal(1, second.Y); - Assert.Equal(50, second.Width); - Assert.Equal(100, second.Height); - Assert.Equal(1, second.Top); - Assert.Equal(51, second.Right); - Assert.Equal(101, second.Bottom); - Assert.Equal(1, second.Left); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/SignedRationalTests.cs b/tests/ImageSharp.Tests46/Numerics/SignedRationalTests.cs deleted file mode 100644 index cb7e21db0..000000000 --- a/tests/ImageSharp.Tests46/Numerics/SignedRationalTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class SignedRationalTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - SignedRational r1 = new SignedRational(3, 2); - SignedRational r2 = new SignedRational(3, 2); - - Assert.Equal(r1, r2); - Assert.True(r1 == r2); - - SignedRational r3 = new SignedRational(7.55); - SignedRational r4 = new SignedRational(755, 100); - SignedRational r5 = new SignedRational(151, 20); - - Assert.Equal(r3, r4); - Assert.Equal(r4, r5); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - SignedRational first = new SignedRational(0, 100); - SignedRational second = new SignedRational(100, 100); - - Assert.NotEqual(first, second); - Assert.True(first != second); - } - - /// - /// Tests whether the Rational constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - SignedRational rational = new SignedRational(7, -55); - Assert.Equal(7, rational.Numerator); - Assert.Equal(-55, rational.Denominator); - - rational = new SignedRational(-755, 100); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(20, rational.Denominator); - - rational = new SignedRational(-755, -100, false); - Assert.Equal(-755, rational.Numerator); - Assert.Equal(-100, rational.Denominator); - - rational = new SignedRational(-151, -20); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(-20, rational.Denominator); - - rational = new SignedRational(-7.55); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(20, rational.Denominator); - - rational = new SignedRational(7); - Assert.Equal(7, rational.Numerator); - Assert.Equal(1, rational.Denominator); - } - - [Fact] - public void Fraction() - { - SignedRational first = new SignedRational(1.0 / 1600); - SignedRational second = new SignedRational(1.0 / 1600, true); - Assert.False(first.Equals(second)); - } - - [Fact] - public void ToDouble() - { - SignedRational rational = new SignedRational(0, 0); - Assert.Equal(double.NaN, rational.ToDouble()); - - rational = new SignedRational(2, 0); - Assert.Equal(double.PositiveInfinity, rational.ToDouble()); - - rational = new SignedRational(-2, 0); - Assert.Equal(double.NegativeInfinity, rational.ToDouble()); - } - - [Fact] - public void ToStringRepresention() - { - SignedRational rational = new SignedRational(0, 0); - Assert.Equal("[ Indeterminate ]", rational.ToString()); - - rational = new SignedRational(double.PositiveInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new SignedRational(double.NegativeInfinity); - Assert.Equal("[ NegativeInfinity ]", rational.ToString()); - - rational = new SignedRational(0, 1); - Assert.Equal("0", rational.ToString()); - - rational = new SignedRational(2, 1); - Assert.Equal("2", rational.ToString()); - - rational = new SignedRational(1, 2); - Assert.Equal("1/2", rational.ToString()); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Numerics/SizeTests.cs b/tests/ImageSharp.Tests46/Numerics/SizeTests.cs deleted file mode 100644 index 29eb768d9..000000000 --- a/tests/ImageSharp.Tests46/Numerics/SizeTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class SizeTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Size first = new Size(100, 100); - Size second = new Size(100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Size first = new Size(0, 100); - Size second = new Size(100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the size constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Size first = new Size(4, 5); - Assert.Equal(4, first.Width); - Assert.Equal(5, first.Height); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/AlphaTest.cs deleted file mode 100644 index efbfe75a8..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/AlphaTest.cs +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class AlphaTest : FileTestBase - { - public static readonly TheoryData AlphaValues - = new TheoryData - { - 20 , - 80 - }; - - [Theory] - [MemberData("AlphaValues")] - public void ImageShouldApplyAlphaFilter(int value) - { - string path = CreateOutputDirectory("Alpha"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Alpha(value) - .Save(output); - } - } - } - - [Theory] - [MemberData("AlphaValues")] - public void ImageShouldApplyAlphaFilterInBox(int value) - { - string path = CreateOutputDirectory("Alpha"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Alpha(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BackgroundColorTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BackgroundColorTest.cs deleted file mode 100644 index a7ecf6c08..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/BackgroundColorTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class BackgroundColorTest : FileTestBase - { - [Fact] - public void ImageShouldApplyBackgroundColorFilter() - { - string path = CreateOutputDirectory("BackgroundColor"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.BackgroundColor(Color.HotPink) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BinaryThreshold.cs b/tests/ImageSharp.Tests46/Processors/Filters/BinaryThreshold.cs deleted file mode 100644 index 10b174cd5..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/BinaryThreshold.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class BinaryThresholdTest : FileTestBase - { - public static readonly TheoryData BinaryThresholdValues - = new TheoryData - { - .25f , - .75f , - }; - - [Theory] - [MemberData("BinaryThresholdValues")] - public void ImageShouldApplyBinaryThresholdFilter(float value) - { - string path = CreateOutputDirectory("BinaryThreshold"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.BinaryThreshold(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BlackWhiteTest.cs deleted file mode 100644 index d4af4ad38..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/BlackWhiteTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class BlackWhiteTest : FileTestBase - { - [Fact] - public void ImageShouldApplyBlackWhiteFilter() - { - string path = CreateOutputDirectory("BlackWhite"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.BlackWhite() - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BlendTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BlendTest.cs deleted file mode 100644 index 653524e20..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/BlendTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class BlendTest : FileTestBase - { - [Fact] - public void ImageShouldApplyBlendFilter() - { - string path = CreateOutputDirectory("Blend"); - - Image blend; - using (FileStream stream = File.OpenRead(TestImages.Bmp.Car)) - { - blend = new Image(stream); - } - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Blend(blend) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs deleted file mode 100644 index 453d56863..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/BoxBlurTest.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - - public class BoxBlurTest : FileTestBase - { - public static readonly TheoryData BoxBlurValues - = new TheoryData - { - 3 , - 5 , - }; - - [Theory] - [MemberData("BoxBlurValues")] - public void ImageShouldApplyBoxBlurFilter(int value) - { - string path = CreateOutputDirectory("BoxBlur"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.BoxBlur(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/BrightnessTest.cs deleted file mode 100644 index e32c7d35e..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/BrightnessTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class BrightnessTest : FileTestBase - { - public static readonly TheoryData BrightnessValues - = new TheoryData - { - 50 , - -50 , - }; - - [Theory] - [MemberData("BrightnessValues")] - public void ImageShouldApplyBrightnessFilter(int value) - { - string path = CreateOutputDirectory("Brightness"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Brightness(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/ColorBlindnessTest.cs deleted file mode 100644 index 966514577..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/ColorBlindnessTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class ColorBlindnessTest : FileTestBase - { - public static readonly TheoryData ColorBlindnessFilters - = new TheoryData - { - ColorBlindness.Achromatomaly, - ColorBlindness.Achromatopsia, - ColorBlindness.Deuteranomaly, - ColorBlindness.Deuteranopia, - ColorBlindness.Protanomaly, - ColorBlindness.Protanopia, - ColorBlindness.Tritanomaly, - ColorBlindness.Tritanopia - }; - - [Theory] - [MemberData("ColorBlindnessFilters")] - public void ImageShouldApplyColorBlindnessFilter(ColorBlindness colorBlindness) - { - string path = CreateOutputDirectory("ColorBlindness"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(colorBlindness); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.ColorBlindness(colorBlindness) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/ContrastTest.cs deleted file mode 100644 index 3c83fd892..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/ContrastTest.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class ContrastTest : FileTestBase - { - public static readonly TheoryData ContrastValues - = new TheoryData - { - 50 , - -50 , - }; - - [Theory] - [MemberData("ContrastValues")] - public void ImageShouldApplyContrastFilter(int value) - { - string path = CreateOutputDirectory("Contrast"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Contrast(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/GlowTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/GlowTest.cs deleted file mode 100644 index 1d317795d..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/GlowTest.cs +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class GlowTest : FileTestBase - { - [Fact] - public void ImageShouldApplyGlowFilter() - { - string path = CreateOutputDirectory("Glow"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Glow() - .Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyGlowFilterColor() - { - string path = CreateOutputDirectory("Glow"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("Color"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Glow(Color.HotPink) - .Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyGlowFilterRadius() - { - string path = CreateOutputDirectory("Glow"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("Radius"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Glow(image.Width / 4) - .Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyGlowFilterInBox() - { - string path = CreateOutputDirectory("Glow"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Glow(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/GrayscaleTest.cs deleted file mode 100644 index 6796ba617..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/GrayscaleTest.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Processors; - using System.IO; - - using Xunit; - - public class GrayscaleTest : FileTestBase - { - public static readonly TheoryData GrayscaleValues - = new TheoryData - { - GrayscaleMode.Bt709 , - GrayscaleMode.Bt601 , - }; - - [Theory] - [MemberData("GrayscaleValues")] - public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value) - { - string path = CreateOutputDirectory("Grayscale"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Grayscale(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/HueTest.cs deleted file mode 100644 index a56aec9ec..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/HueTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class HueTest : FileTestBase - { - public static readonly TheoryData HueValues - = new TheoryData - { - 180 , - -180 , - }; - - [Theory] - [MemberData("HueValues")] - public void ImageShouldApplyHueFilter(int value) - { - string path = CreateOutputDirectory("Hue"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Hue(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/InvertTest.cs deleted file mode 100644 index 55bfa852b..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/InvertTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class InvertTest : FileTestBase - { - [Fact] - public void ImageShouldApplyInvertFilter() - { - string path = CreateOutputDirectory("Invert"); - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Invert() - .Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyInvertFilterInBox() - { - string path = CreateOutputDirectory("Invert"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Invert(new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/KodachromeTest.cs deleted file mode 100644 index adb7cb36d..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/KodachromeTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class KodachromeTest : FileTestBase - { - [Fact] - public void ImageShouldApplyKodachromeFilter() - { - string path = CreateOutputDirectory("Kodachrome"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Kodachrome() - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/LomographTest.cs deleted file mode 100644 index 79a7aa3ba..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/LomographTest.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class LomographTest : FileTestBase - { - [Fact] - public void ImageShouldApplyLomographFilter() - { - string path = CreateOutputDirectory("Lomograph"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Lomograph() - .Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyLomographFilterInBox() - { - string path = CreateOutputDirectory("Lomograph"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Lomograph(new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/PolaroidTest.cs deleted file mode 100644 index dc9d3a150..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/PolaroidTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class PolaroidTest : FileTestBase - { - [Fact] - public void ImageShouldApplyPolaroidFilter() - { - string path = CreateOutputDirectory("Polaroid"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Polaroid() - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/SaturationTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/SaturationTest.cs deleted file mode 100644 index 5fe4c3e00..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/SaturationTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class SaturationTest : FileTestBase - { - public static readonly TheoryData SaturationValues - = new TheoryData - { - 50 , - -50 , - }; - - [Theory] - [MemberData("SaturationValues")] - public void ImageShouldApplySaturationFilter(int value) - { - string path = CreateOutputDirectory("Saturation"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Saturation(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/SepiaTest.cs deleted file mode 100644 index b5e4d3105..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/SepiaTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class SepiaTest : FileTestBase - { - [Fact] - public void ImageShouldApplySepiaFilter() - { - string path = CreateOutputDirectory("Sepia"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Sepia() - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Filters/VignetteTest.cs b/tests/ImageSharp.Tests46/Processors/Filters/VignetteTest.cs deleted file mode 100644 index 3fddad1da..000000000 --- a/tests/ImageSharp.Tests46/Processors/Filters/VignetteTest.cs +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class VignetteTest : FileTestBase - { - [Fact] - public void ImageShouldApplyVignetteFilter() - { - string path = CreateOutputDirectory("Vignette"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Vignette() - .Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyVignetteFilterColor() - { - string path = CreateOutputDirectory("Vignette"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("Color"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Vignette(Color.HotPink) - .Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyVignetteFilterRadius() - { - string path = CreateOutputDirectory("Vignette"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("Radius"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Vignette(image.Width / 4, image.Height / 4) - .Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyVignetteFilterInBox() - { - string path = CreateOutputDirectory("Vignette"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Vignette(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/AutoOrientTests.cs b/tests/ImageSharp.Tests46/Processors/Samplers/AutoOrientTests.cs deleted file mode 100644 index 97cd71718..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/AutoOrientTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - using Xunit; - - public class AutoOrientTests : FileTestBase - { - public static readonly TheoryData OrientationValues - = new TheoryData - { - { RotateType.None, FlipType.None, 0 }, - { RotateType.None, FlipType.None, 1 }, - { RotateType.None, FlipType.Horizontal, 2 }, - { RotateType.Rotate180, FlipType.None, 3 }, - { RotateType.Rotate180, FlipType.Horizontal, 4 }, - { RotateType.Rotate90, FlipType.Horizontal, 5 }, - { RotateType.Rotate270, FlipType.None, 6 }, - { RotateType.Rotate90, FlipType.Vertical, 7 }, - { RotateType.Rotate90, FlipType.None, 8 }, - }; - - [Theory] - [MemberData("OrientationValues")] - public void ImageShouldFlip(RotateType rotateType, FlipType flipType, ushort orientation) - { - string path = CreateOutputDirectory("AutoOrient"); - - string file = TestImages.Bmp.F; - - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-" + orientation + Path.GetExtension(file); - - Image image = new Image(stream); - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.Orientation, orientation); - - using (FileStream before = File.OpenWrite($"{path}/before-{filename}")) - { - using (FileStream after = File.OpenWrite($"{path}/after-{filename}")) - { - image.RotateFlip(rotateType, flipType) - .Save(before) - .AutoOrient() - .Save(after); - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/CropTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/CropTest.cs deleted file mode 100644 index 9e9dd34db..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/CropTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class CropTest : FileTestBase - { - [Fact] - public void ImageShouldApplyCropSampler() - { - string path = CreateOutputDirectory("Crop"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Crop(image.Width / 2, image.Height / 2) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/DetectEdgesTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/DetectEdgesTest.cs deleted file mode 100644 index ce088eac4..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/DetectEdgesTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class DetectEdgesTest : FileTestBase - { - public static readonly TheoryData DetectEdgesFilters - = new TheoryData - { - EdgeDetection.Kayyali, - EdgeDetection.Kirsch, - EdgeDetection.Lapacian3X3, - EdgeDetection.Lapacian5X5, - EdgeDetection.LaplacianOfGaussian, - EdgeDetection.Prewitt, - EdgeDetection.RobertsCross, - EdgeDetection.Robinson, - EdgeDetection.Scharr, - EdgeDetection.Sobel, - }; - - [Theory] - [MemberData(nameof(DetectEdgesFilters))] - public void ImageShouldApplyDetectEdgesFilter(EdgeDetection detector) - { - string path = CreateOutputDirectory("DetectEdges"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(detector); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.DetectEdges(detector) - .Save(output); - } - } - } - - [Theory] - [MemberData("DetectEdgesFilters")] - public void ImageShouldApplyDetectEdgesFilterInBox(EdgeDetection detector) - { - string path = CreateOutputDirectory("DetectEdges"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(detector + "-InBox"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.DetectEdges(detector, new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/EntropyCropTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/EntropyCropTest.cs deleted file mode 100644 index fdbbc5cde..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/EntropyCropTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class EntropyCropTest : FileTestBase - { - public static readonly TheoryData EntropyCropValues - = new TheoryData - { - .25f , - .75f , - }; - - [Theory] - [MemberData("EntropyCropValues")] - public void ImageShouldApplyEntropyCropSampler(float value) - { - string path = CreateOutputDirectory("EntropyCrop"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.EntropyCrop(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/FlipTests.cs b/tests/ImageSharp.Tests46/Processors/Samplers/FlipTests.cs deleted file mode 100644 index c07071e70..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/FlipTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class FlipTests : FileTestBase - { - public static readonly TheoryData FlipValues - = new TheoryData - { - { FlipType.None }, - { FlipType.Vertical }, - { FlipType.Horizontal }, - }; - - [Theory] - [MemberData("FlipValues")] - public void ImageShouldFlip(FlipType flipType) - { - string path = CreateOutputDirectory("Flip"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(flipType); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Flip(flipType) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/GuassianBlurTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/GuassianBlurTest.cs deleted file mode 100644 index cd1cc5638..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/GuassianBlurTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class GuassianBlurTest : FileTestBase - { - public static readonly TheoryData GuassianBlurValues - = new TheoryData - { - 3 , - 5 , - }; - - [Theory] - [MemberData("GuassianBlurValues")] - public void ImageShouldApplyGuassianBlurFilter(int value) - { - string path = CreateOutputDirectory("GuassianBlur"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.GuassianBlur(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/GuassianSharpenTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/GuassianSharpenTest.cs deleted file mode 100644 index 5672b5bae..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/GuassianSharpenTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class GuassianSharpenTest : FileTestBase - { - public static readonly TheoryData GuassianSharpenValues - = new TheoryData - { - 3 , - 5 , - }; - - [Theory] - [MemberData("GuassianSharpenValues")] - public void ImageShouldApplyGuassianSharpenFilter(int value) - { - string path = CreateOutputDirectory("GuassianSharpen"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.GuassianSharpen(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/OilPaintTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/OilPaintTest.cs deleted file mode 100644 index 8b5ff3f51..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/OilPaintTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System; - using System.IO; - - using Xunit; - - public class OilPaintTest : FileTestBase - { - public static readonly TheoryData> OilPaintValues - = new TheoryData> - { - new Tuple(15,10), - new Tuple(6,5) - }; - - [Theory] - [MemberData(nameof(OilPaintValues))] - public void ImageShouldApplyOilPaintFilter(Tuple value) - { - string path = CreateOutputDirectory("OilPaint"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.OilPaint(value.Item1, value.Item2) - .Save(output); - } - } - } - - [Theory] - [MemberData(nameof(OilPaintValues))] - public void ImageShouldApplyOilPaintFilterInBox(Tuple value) - { - string path = CreateOutputDirectory("OilPaint"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.OilPaint(value.Item1, value.Item2, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/PadTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/PadTest.cs deleted file mode 100644 index 5a33aac2b..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/PadTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class PadTest : FileTestBase - { - [Fact] - public void ImageShouldApplyPadSampler() - { - string path = CreateOutputDirectory("Pad"); - - foreach (TestFile file in Files) - { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Pad(image.Width + 50, image.Height + 50) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/PixelateTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/PixelateTest.cs deleted file mode 100644 index 397bdef06..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/PixelateTest.cs +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class PixelateTest : FileTestBase - { - public static readonly TheoryData PixelateValues - = new TheoryData - { - 4 , - 8 - }; - - [Theory] - [MemberData(nameof(PixelateValues))] - public void ImageShouldApplyPixelateFilter(int value) - { - string path = CreateOutputDirectory("Pixelate"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Pixelate(value) - .Save(output); - } - } - } - - [Theory] - [MemberData(nameof(PixelateValues))] - public void ImageShouldApplyPixelateFilterInBox(int value) - { - string path = CreateOutputDirectory("Pixelate"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Pixelate(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/ResizeTests.cs b/tests/ImageSharp.Tests46/Processors/Samplers/ResizeTests.cs deleted file mode 100644 index 876ba7ebc..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/ResizeTests.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class ResizeTests : FileTestBase - { - public static readonly TheoryData ReSamplers = - new TheoryData - { - { "Bicubic", new BicubicResampler() }, - { "Triangle", new TriangleResampler() }, - { "NearestNeighbor", new NearestNeighborResampler() }, - // Perf: Enable for local testing only - //{ "Box", new BoxResampler() }, - //{ "Lanczos3", new Lanczos3Resampler() }, - //{ "Lanczos5", new Lanczos5Resampler() }, - //{ "Lanczos8", new Lanczos8Resampler() }, - { "MitchellNetravali", new MitchellNetravaliResampler() }, - //{ "Hermite", new HermiteResampler() }, - //{ "Spline", new SplineResampler() }, - //{ "Robidoux", new RobidouxResampler() }, - //{ "RobidouxSharp", new RobidouxSharpResampler() }, - //{ "RobidouxSoft", new RobidouxSoftResampler() }, - //{ "Welch", new WelchResampler() } - }; - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResize(string name, IResampler sampler) - { - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Resize(image.Width / 2, image.Height / 2, sampler, true) - //image.Resize(555, 275, sampler, false) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeWidthAndKeepAspect(string name, IResampler sampler) - { - name = name + "-FixedWidth"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Resize(image.Width / 3, 0, sampler, false) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeHeightAndKeepAspect(string name, IResampler sampler) - { - name = name + "-FixedHeight"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Resize(0, image.Height / 3, sampler, false) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeWithCropWidthMode(string name, IResampler sampler) - { - name = name + "-CropWidth"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Sampler = sampler, - Size = new Size(image.Width / 2, image.Height) - }; - - image.Resize(options) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeWithCropHeightMode(string name, IResampler sampler) - { - name = name + "-CropHeight"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Sampler = sampler, - Size = new Size(image.Width, image.Height / 2) - }; - - image.Resize(options) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeWithPadMode(string name, IResampler sampler) - { - name = name + "-Pad"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad - }; - - image.Resize(options) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeWithBoxPadMode(string name, IResampler sampler) - { - name = name + "-BoxPad"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Sampler = sampler, - Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad - }; - - image.Resize(options) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeWithMaxMode(string name, IResampler sampler) - { - name = name + "Max"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Sampler = sampler, - Size = new Size(300, 300), - Mode = ResizeMode.Max - }; - - image.Resize(options) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeWithMinMode(string name, IResampler sampler) - { - name = name + "-Min"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Sampler = sampler, - Size = new Size(image.Width - 50, image.Height - 25), - Mode = ResizeMode.Min - }; - - image.Resize(options) - .Save(output); - } - } - } - - [Theory] - [MemberData("ReSamplers")] - public void ImageShouldResizeWithStretchMode(string name, IResampler sampler) - { - name = name + "Stretch"; - - string path = CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Sampler = sampler, - Size = new Size(image.Width - 200, image.Height), - Mode = ResizeMode.Stretch - }; - - image.Resize(options) - .Save(output); - } - } - } - - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - [InlineData(2, 0)] - public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) - { - Lanczos3Resampler sampler = new Lanczos3Resampler(); - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/RotateFlipTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/RotateFlipTest.cs deleted file mode 100644 index 04704b672..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/RotateFlipTest.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class RotateFlipTest : FileTestBase - { - public static readonly TheoryData RotateFlipValues - = new TheoryData - { - { RotateType.None, FlipType.Vertical }, - { RotateType.None, FlipType.Horizontal }, - { RotateType.Rotate90, FlipType.None }, - { RotateType.Rotate180, FlipType.None }, - { RotateType.Rotate270, FlipType.None }, - }; - - [Theory] - [MemberData("RotateFlipValues")] - public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) - { - string path = CreateOutputDirectory("RotateFlip"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(rotateType + "-" + flipType); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.RotateFlip(rotateType, flipType) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/RotateTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/RotateTest.cs deleted file mode 100644 index 96fb39e07..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/RotateTest.cs +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class RotateTest : FileTestBase - { - public static readonly TheoryData RotateFloatValues - = new TheoryData - { - 170 , - -170 , - }; - - public static readonly TheoryData RotateEnumValues - = new TheoryData - { - RotateType.None, - RotateType.Rotate90, - RotateType.Rotate180, - RotateType.Rotate270 - }; - - [Theory] - [MemberData("RotateFloatValues")] - public void ImageShouldApplyRotateSampler(float value) - { - string path = CreateOutputDirectory("Rotate"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Rotate(value) - .Save(output); - } - } - } - - [Theory] - [MemberData("RotateEnumValues")] - public void ImageShouldApplyRotateSampler(RotateType value) - { - string path = CreateOutputDirectory("Rotate"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Rotate(value) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Processors/Samplers/SkewTest.cs b/tests/ImageSharp.Tests46/Processors/Samplers/SkewTest.cs deleted file mode 100644 index 11db7e691..000000000 --- a/tests/ImageSharp.Tests46/Processors/Samplers/SkewTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using Xunit; - - public class SkewTest : FileTestBase - { - public static readonly TheoryData SkewValues - = new TheoryData - { - { 20, 10 }, - { -20, -10 } - }; - - [Theory] - [MemberData("SkewValues")] - public void ImageShouldApplySkewSampler(float x, float y) - { - string path = CreateOutputDirectory("Skew"); - - // Matches live example - // http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew - foreach (TestFile file in Files) - { - string filename = file.GetFileName(x + "-" + y); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Skew(x, y) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests46/Profiles/Exif/ExifProfileTests.cs deleted file mode 100644 index fa7347a97..000000000 --- a/tests/ImageSharp.Tests46/Profiles/Exif/ExifProfileTests.cs +++ /dev/null @@ -1,325 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System; - using System.Collections; - using System.IO; - using System.Linq; - using System.Text; - using Xunit; - - public class ExifProfileTests - { - [Fact] - public void Constructor() - { - using (FileStream stream = File.OpenRead(TestImages.Jpeg.Calliphora)) - { - Image image = new Image(stream); - - Assert.Null(image.ExifProfile); - - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); - - image = WriteAndRead(image); - - Assert.NotNull(image.ExifProfile); - Assert.Equal(1, image.ExifProfile.Values.Count()); - - ExifValue value = image.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); - TestValue(value, "Dirk Lemstra"); - } - } - - [Fact] - public void ConstructorEmpty() - { - new ExifProfile((byte[])null); - new ExifProfile(new byte[] { }); - } - - [Fact] - public void ConstructorCopy() - { - Assert.Throws(() => { new ExifProfile((ExifProfile)null); }); - - ExifProfile profile = GetExifProfile(); - - ExifProfile clone = new ExifProfile(profile); - TestProfile(clone); - - profile.SetValue(ExifTag.ColorSpace, (ushort)2); - - clone = new ExifProfile(profile); - TestProfile(clone); - } - - [Fact] - public void WriteFraction() - { - using (MemoryStream memStream = new MemoryStream()) - { - double exposureTime = 1.0 / 1600; - - ExifProfile profile = GetExifProfile(); - - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); - - Image image = new Image(1, 1); - image.ExifProfile = profile; - - image.SaveAsJpeg(memStream); - - memStream.Position = 0; - image = new Image(memStream); - - profile = image.ExifProfile; - Assert.NotNull(profile); - - ExifValue value = profile.GetValue(ExifTag.ExposureTime); - Assert.NotNull(value); - Assert.NotEqual(exposureTime, ((Rational)value.Value).ToDouble()); - - memStream.Position = 0; - profile = GetExifProfile(); - - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); - image.ExifProfile = profile; - - image.SaveAsJpeg(memStream); - - memStream.Position = 0; - image = new Image(memStream); - - profile = image.ExifProfile; - Assert.NotNull(profile); - - value = profile.GetValue(ExifTag.ExposureTime); - Assert.Equal(exposureTime, ((Rational)value.Value).ToDouble()); - } - } - - [Fact] - public void ReadWriteInfinity() - { - using (FileStream stream = File.OpenRead(TestImages.Jpeg.Floorplan)) - { - Image image = new Image(stream); - image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); - - image = WriteAndRead(image); - ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); - Assert.NotNull(value); - Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); - - image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); - - image = WriteAndRead(image); - value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); - Assert.NotNull(value); - Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); - - image.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); - - image = WriteAndRead(image); - value = image.ExifProfile.GetValue(ExifTag.FlashEnergy); - Assert.NotNull(value); - Assert.Equal(new Rational(double.PositiveInfinity), value.Value); - } - } - - [Fact] - public void SetValue() - { - Rational[] latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; - - using (FileStream stream = File.OpenRead(TestImages.Jpeg.Floorplan)) - { - Image image = new Image(stream); - image.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); - - ExifValue value = image.ExifProfile.GetValue(ExifTag.Software); - TestValue(value, "ImageSharp"); - - Assert.Throws(() => { value.Value = 15; }); - - image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); - - value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); - - TestValue(value, new SignedRational(7555, 100)); - - Assert.Throws(() => { value.Value = 75; }); - - image.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); - - value = image.ExifProfile.GetValue(ExifTag.XResolution); - TestValue(value, new Rational(150, 1)); - - Assert.Throws(() => { value.Value = "ImageSharp"; }); - - image.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); - - value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); - TestValue(value, (string)null); - - image.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); - - value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); - TestValue(value, latitude); - - image = WriteAndRead(image); - - Assert.NotNull(image.ExifProfile); - Assert.Equal(17, image.ExifProfile.Values.Count()); - - value = image.ExifProfile.GetValue(ExifTag.Software); - TestValue(value, "ImageSharp"); - - value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); - TestValue(value, new SignedRational(75.55)); - - value = image.ExifProfile.GetValue(ExifTag.XResolution); - TestValue(value, new Rational(150.0)); - - value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); - Assert.Null(value); - - value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); - TestValue(value, latitude); - - image.ExifProfile.Parts = ExifParts.ExifTags; - - image = WriteAndRead(image); - - Assert.NotNull(image.ExifProfile); - Assert.Equal(8, image.ExifProfile.Values.Count()); - - Assert.NotNull(image.ExifProfile.GetValue(ExifTag.ColorSpace)); - Assert.True(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.False(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.Null(image.ExifProfile.GetValue(ExifTag.ColorSpace)); - - Assert.Equal(7, image.ExifProfile.Values.Count()); - } - } - - [Fact] - public void Values() - { - ExifProfile profile = GetExifProfile(); - - TestProfile(profile); - - var thumbnail = profile.CreateThumbnail(); - Assert.NotNull(thumbnail); - Assert.Equal(256, thumbnail.Width); - Assert.Equal(170, thumbnail.Height); - } - - [Fact] - public void WriteTooLargeProfile() - { - StringBuilder junk = new StringBuilder(); - for (int i = 0; i < 65500; i++) - junk.Append("I"); - - Image image = new Image(100, 100); - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); - - using (MemoryStream memStream = new MemoryStream()) - { - Assert.Throws(() => image.SaveAsJpeg(memStream)); - } - } - - private static ExifProfile GetExifProfile() - { - using (FileStream stream = File.OpenRead(TestImages.Jpeg.Floorplan)) - { - Image image = new Image(stream); - - ExifProfile profile = image.ExifProfile; - Assert.NotNull(profile); - - return profile; - } - } - - private static Image WriteAndRead(Image image) - { - using (MemoryStream memStream = new MemoryStream()) - { - image.SaveAsJpeg(memStream); - - memStream.Position = 0; - return new Image(memStream); - } - } - - private static void TestProfile(ExifProfile profile) - { - Assert.NotNull(profile); - - Assert.Equal(16, profile.Values.Count()); - - foreach (ExifValue value in profile.Values) - { - Assert.NotNull(value.Value); - - if (value.Tag == ExifTag.Software) - Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); - - if (value.Tag == ExifTag.XResolution) - Assert.Equal(new Rational(300.0), value.Value); - - if (value.Tag == ExifTag.PixelXDimension) - Assert.Equal(2338U, value.Value); - } - } - - private static void TestValue(ExifValue value, string expected) - { - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - } - - private static void TestValue(ExifValue value, Rational expected) - { - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - } - - private static void TestValue(ExifValue value, SignedRational expected) - { - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - } - - private static void TestValue(ExifValue value, Rational[] expected) - { - Assert.NotNull(value); - - Assert.Equal(expected, (ICollection)value.Value); - } - - private static void TestValue(ExifValue value, double expected) - { - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - } - - private static void TestValue(ExifValue value, double[] expected) - { - Assert.NotNull(value); - - Assert.Equal(expected, (ICollection)value.Value); - } - } -} diff --git a/tests/ImageSharp.Tests46/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests46/Profiles/Exif/ExifTagDescriptionAttributeTests.cs deleted file mode 100644 index 4f77dc11a..000000000 --- a/tests/ImageSharp.Tests46/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Xunit; - - public class ExifDescriptionAttributeTests - { - [Fact] - public void Test_ExifTag() - { - var exifProfile = new ExifProfile(); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); - ExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("None", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)2); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("Inches", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)3); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("Centimeter", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)4); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("4", value.ToString()); - - exifProfile.SetValue(ExifTag.ImageWidth, 123); - value = exifProfile.GetValue(ExifTag.ImageWidth); - Assert.Equal("123", value.ToString()); - } - } -} diff --git a/tests/ImageSharp.Tests46/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests46/Profiles/Exif/ExifValueTests.cs deleted file mode 100644 index e79cb7186..000000000 --- a/tests/ImageSharp.Tests46/Profiles/Exif/ExifValueTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - using System.Linq; - using Xunit; - - public class ExifValueTests - { - private static ExifValue GetExifValue() - { - using (FileStream stream = File.OpenRead(TestImages.Jpeg.Floorplan)) - { - Image image = new Image(stream); - - ExifProfile profile = image.ExifProfile; - Assert.NotNull(profile); - - return profile.Values.First(); - } - } - - [Fact] - public void IEquatable() - { - ExifValue first = GetExifValue(); - ExifValue second = GetExifValue(); - - Assert.True(first == second); - Assert.True(first.Equals(second)); - Assert.True(first.Equals((object)second)); - } - - [Fact] - public void Properties() - { - ExifValue value = GetExifValue(); - - Assert.Equal(ExifDataType.Ascii, value.DataType); - Assert.Equal(ExifTag.GPSDOP, value.Tag); - Assert.Equal(false, value.IsArray); - Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); - Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.Value); - } - } -} diff --git a/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs b/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs deleted file mode 100644 index 3d57ceff5..000000000 --- a/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ImageSharp.Tests")] -[assembly: AssemblyDescription("A cross-platform library for processing of image files written in C#")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ImageSharp.Tests")] -[assembly: AssemblyCopyright("Copyright © James Jackson-South and contributors.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f836e8e6-b4d9-4208-8346-140c74678b91")] diff --git a/tests/ImageSharp.Tests46/TestFile.cs b/tests/ImageSharp.Tests46/TestFile.cs deleted file mode 100644 index f7e7f8517..000000000 --- a/tests/ImageSharp.Tests46/TestFile.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using System.IO; - -namespace ImageSharp.Tests -{ - public class TestFile - { - private readonly Image image; - private readonly string file; - - public TestFile(string file) - { - this.file = file; - - using (FileStream stream = File.OpenRead(file)) - { - this.image = new Image(stream); - } - } - - public string FileName - { - get - { - return Path.GetFileName(this.file); - } - } - - public string FileNameWithoutExtension - { - get - { - return Path.GetFileNameWithoutExtension(this.file); - } - } - - public string GetFileName(object value) - { - return this.FileNameWithoutExtension + "-" + value + Path.GetExtension(this.file); - } - - public string GetFileNameWithoutExtension(object value) - { - return this.FileNameWithoutExtension + "-" + value; - } - - public Image CreateImage() - { - return new Image(this.image); - } - } -} diff --git a/tests/ImageSharp.Tests46/TestImages.cs b/tests/ImageSharp.Tests46/TestImages.cs deleted file mode 100644 index edb2449e8..000000000 --- a/tests/ImageSharp.Tests46/TestImages.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using System.Diagnostics.PerformanceData; - -namespace ImageSharp.Tests -{ - /// - /// Class that contains all the test images. - /// - public static class TestImages - { - public static class Png - { - private static readonly string folder = "../../TestImages/Formats/Png/"; - - public static string P1 => folder + "pl.png"; - public static string Pd => folder + "pd.png"; - public static string Blur => folder + "blur.png"; - public static string Indexed => folder + "indexed.png"; - public static string Splash => folder + "splash.png"; - } - - public static class Jpeg - { - private const string Folder = "../../TestImages/Formats/Jpg/"; - public const string Cmyk = Folder + "cmyk.jpg"; - public const string Exif = Folder + "exif.jpg"; - public const string Floorplan = Folder + "Floorplan.jpeg"; - public const string Calliphora = Folder + "Calliphora.jpg"; - public const string Turtle = Folder + "turtle.jpg"; - public const string Fb = Folder + "fb.jpg"; - public const string Progress = Folder + "progress.jpg"; - public const string GammaDalaiLamaGray = Folder + "gamma_dalai_lama_gray.jpg"; - - public const string Geneserath = Folder + "geneserath.jpg"; - - public static readonly string[] All = new[] - { - Cmyk, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray, Geneserath - }; - } - - public static class Bmp - { - private static readonly string folder = "../../TestImages/Formats/Bmp/"; - - public static string Car => folder + "Car.bmp"; - public static string F => folder + "F.bmp"; - public static string NegHeight => folder + "neg_height.bmp"; - - public static string[] All => new[] {Car, F, NegHeight}; - } - - public static class Gif - { - private static readonly string folder = "../../TestImages/Formats/Gif/"; - - public static string Rings => folder + "rings.gif"; - public static string Giphy => folder + "giphy.gif"; - } - } -} diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/Car.bmp b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/Car.bmp deleted file mode 100644 index edaf3a8e4..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/Car.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d3a4a30cd67db6ded1e57126c7ba275404703e64b3dfb1c9c711128c15b0124 -size 810054 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/F.bmp b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/F.bmp deleted file mode 100644 index d95598bef..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/F.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6da008d2b285b2db946e6d4ebf8569b0ddd4a05ef273b38304cb65afccac87b3 -size 65502 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/neg_height.bmp b/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/neg_height.bmp deleted file mode 100644 index d0b99a902..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Bmp/neg_height.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81437b88a5d92fcb545fae4991643a0c73d95d0277dac0b79074971780008c8c -size 6220854 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Gif/giphy.gif b/tests/ImageSharp.Tests46/TestImages/Formats/Gif/giphy.gif deleted file mode 100644 index 029afdec6..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Gif/giphy.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3f42abd9f3e493a0acd5303ab0d37a6179835c5a14364a1f001abd9d9e906f96 -size 53655 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Gif/rings.gif b/tests/ImageSharp.Tests46/TestImages/Formats/Gif/rings.gif deleted file mode 100644 index acd5d6339..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Gif/rings.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:716448da88152225767c024aac498f5b7562b6e8391907cefc9d03dba40050fd -size 53435 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Calliphora.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Calliphora.jpg deleted file mode 100644 index aa3fdef01..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Calliphora.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:67172fcab405f914587b88cd1106328e6b24ab59d622ba509dcc99509951ff5c -size 254766 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Floorplan.jpeg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Floorplan.jpeg deleted file mode 100644 index 6f439d220..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/Floorplan.jpeg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de00b34b78dfa0886c93d8dd5cede27b4940d5c620e44631e77e6dc8838befc3 -size 161577 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/cmyk.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/cmyk.jpg deleted file mode 100644 index 2fe8f0a61..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/cmyk.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0 -size 611572 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/exif.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/exif.jpg deleted file mode 100644 index cba862660..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/exif.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a9d04b92d0de5836c59ede8ae421235488e4031e893e07b1fe7e4b78f6a9901 -size 32764 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/fb.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/fb.jpg deleted file mode 100644 index 7241890e2..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/fb.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c -size 15787 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg deleted file mode 100644 index c305caef4..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3d92a88b04518e266b98d9d2f5b4eb88f3f91c332d3397ea859bab8cabc41185 -size 84887 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/geneserath.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/geneserath.jpg deleted file mode 100644 index af0c04a1c..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/geneserath.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c71e5b1ba181a5b17ba9814c2025650de592efabf4062bd77baa3c8e774df007 -size 223841 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/progress.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/progress.jpg deleted file mode 100644 index 30b214c22..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/progress.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a9a1f0da3c5b3a3e7e7f35abe9f5b458163b48ca56226227b3d3cffe06af1971 -size 44884 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/turtle.jpg b/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/turtle.jpg deleted file mode 100644 index 07d96543b..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Jpg/turtle.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e5c576b0c5e743cfd498b110305268ecbae63c62061ba6c7eb8e060728191f1 -size 55126 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/blur.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/blur.png deleted file mode 100644 index 2ac488b7c..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Png/blur.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:10df946d3d6a9832bacd9ac2587b890c348d17731412c8fd17c34f66f35d9c94 -size 183768 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/indexed.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/indexed.png deleted file mode 100644 index f06e1cbd6..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Png/indexed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:65566cde707c02757a26fb5fb7702be9c53f55c17a1748d81c384559de2d1173 -size 33529 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/pd.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/pd.png deleted file mode 100644 index 12fde3229..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Png/pd.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:91dd938fb916d368738826741551aea694b340ba3362f56200c5d3c5e5510267 -size 1406 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/pl.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/pl.png deleted file mode 100644 index 15e991284..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Png/pl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa550e61fe276d2ebee8666d0cbb811449433edb76e01bcd38cb00c1aafc417e -size 91 diff --git a/tests/ImageSharp.Tests46/TestImages/Formats/Png/splash.png b/tests/ImageSharp.Tests46/TestImages/Formats/Png/splash.png deleted file mode 100644 index ca4f86bce..000000000 --- a/tests/ImageSharp.Tests46/TestImages/Formats/Png/splash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4c13422913f1c1910f8dd607236e79b4a5c7053deb8ce1c8be8372eca7465fb -size 245033 diff --git a/tests/ImageSharp.Tests46/app.config b/tests/ImageSharp.Tests46/app.config deleted file mode 100644 index 5e95024db..000000000 --- a/tests/ImageSharp.Tests46/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/packages.config b/tests/ImageSharp.Tests46/packages.config deleted file mode 100644 index e96e29cd6..000000000 --- a/tests/ImageSharp.Tests46/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file