From ff30ab089e7f12910eeac899fdc86971df61832c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 13:53:55 +1000 Subject: [PATCH] Contrast Former-commit-id: 9aae78349c3150f3dff653fe4b065d6c305d3a1a Former-commit-id: 23d52315bcd126733230bd6ed8cc4f9566b6bd9a Former-commit-id: 29dfaaec8e8759096af680fc188541574002d297 --- src/ImageProcessorCore/Filters/Contrast.cs | 60 +++++++++++++++ .../Filters/Processors/ContrastProcessor.cs | 75 +++++++++++++++++++ .../Processors/Filters/ContrastTest.cs | 47 ++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/Contrast.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/ContrastTest.cs diff --git a/src/ImageProcessorCore/Filters/Contrast.cs b/src/ImageProcessorCore/Filters/Contrast.cs new file mode 100644 index 000000000..5025daf64 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Contrast.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + 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. long, float. + /// The image this method extends. + /// The new contrast of the image. Must be between -100 and 100. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Contrast(this Image source, int amount, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Contrast(source, amount, source.Bounds, progressHandler); + } + + /// + /// Alters the contrast component of the image. + /// + /// The pixel format. + /// The packed format. 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. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Contrast(this Image source, int amount, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + ContrastProcessor processor = new ContrastProcessor(amount); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs new file mode 100644 index 000000000..01aa128d3 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + 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 : ImageProcessor + where T : IPackedVector + where TP : 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 target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + float contrast = (100f + this.Value) / 100f; + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); + Vector4 shiftVector = new Vector4(.5f, .5f, .5f, 1); + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + Vector4 vector = (sourcePixels[x, y]).ToVector4().Expand(); + vector -= shiftVector; + vector *= contrastVector; + vector += shiftVector; + T packed = default(T); + packed.PackVector(vector.Compress()); + targetPixels[x, y] = packed; + } + this.OnRowProcessed(); + } + }); + } + } + } +} diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/ContrastTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/ContrastTest.cs new file mode 100644 index 000000000..589fa8a6e --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/ContrastTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.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) + { + const string path = "TestOutput/Contrast"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Contrast(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file