Skip to content

Commit

Permalink
Implement basic histogram
Browse files Browse the repository at this point in the history
Closes #66
  • Loading branch information
lmadhavan committed Dec 20, 2021
1 parent c612e5c commit 7379d6f
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Fotografix.Core.Tests/BitmapAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private static async Task SaveAsync(CanvasBitmap bitmap, string filename)

using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await bitmap.SaveAsync(stream, CanvasBitmapFileFormat.Jpeg);
await bitmap.SaveAsync(stream, filename.EndsWith(".png") ? CanvasBitmapFileFormat.Png : CanvasBitmapFileFormat.Jpeg);
}

await CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => await Launcher.LaunchFolderAsync(tempFolder));
Expand Down
1 change: 1 addition & 0 deletions Fotografix.Core.Tests/Fotografix.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
<Content Include="TestData\Barn_exposure.jpg" />
<Content Include="TestData\Barn_exposure_thumbnail.jpg" />
<Content Include="TestData\Barn_highlights.jpg" />
<Content Include="TestData\Barn_histogram.png" />
<Content Include="TestData\Barn_hue_yellow.jpg" />
<Content Include="TestData\Barn_luminance_yellow.jpg" />
<Content Include="TestData\Barn_saturation.jpg" />
Expand Down
17 changes: 16 additions & 1 deletion Fotografix.Core.Tests/PhotoEditorTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Graphics.Canvas;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
Expand Down Expand Up @@ -214,6 +215,20 @@ public void ScalesSizeToFitMaxDimension()
Assert.AreEqual(new Size(100, 50), PhotoEditor.ScaleDimensions(new Size(100, 50), 200), "originalSize < maxDimension: should not scale");
}

[TestMethod]
public async Task ComputesHistogram()
{
using (var editor = await PhotoEditor.CreateAsync(photo))
{
var histogram = editor.ComputeHistogram();

using (var output = histogram.ExportToCanvasBitmap(CanvasDevice.GetSharedDevice()))
{
await BitmapAssert.VerifyAsync(output, "Barn_histogram.png");
}
}
}

private async Task VerifyOutputAsync(PhotoEditor editor, string filename)
{
using (var output = editor.ExportToCanvasBitmap())
Expand Down
Binary file added Fotografix.Core.Tests/TestData/Barn_histogram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Fotografix.Core/Fotografix.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="CropRect.cs" />
<Compile Include="Histogram.cs" />
<Compile Include="IPhoto.cs" />
<Compile Include="IRenderScaleProvider.cs" />
<Compile Include="Xmp\BitmapPropertiesXmpExtensions.cs" />
Expand Down
65 changes: 65 additions & 0 deletions Fotografix.Core/Histogram.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Windows.Foundation;
using Windows.UI;

namespace Fotografix
{
public sealed class Histogram
{
private const int NumBins = 256;
private const int Height = 100;

private readonly float[] r;
private readonly float[] g;
private readonly float[] b;

public Histogram(float[] r, float[] g, float[] b)
{
this.r = r;
this.g = g;
this.b = b;
}

public static Histogram Compute(ICanvasImage image, Rect rect, ICanvasResourceCreator resourceCreator)
{
float[] r = CanvasImage.ComputeHistogram(image, rect, resourceCreator, EffectChannelSelect.Red, NumBins);
float[] g = CanvasImage.ComputeHistogram(image, rect, resourceCreator, EffectChannelSelect.Green, NumBins);
float[] b = CanvasImage.ComputeHistogram(image, rect, resourceCreator, EffectChannelSelect.Blue, NumBins);
return new Histogram(r, g, b);
}

public static Size RenderSize => new Size(NumBins, Height);

public void Draw(CanvasDrawingSession ds)
{
float s = Height / 0.025f;

void Draw(float[] x, Color color)
{
for (int i = 0; i < NumBins; i++)
{
ds.DrawLine(i, Height, i, Height - s * x[i], color);
}
}

ds.Blend = CanvasBlend.Add;
Draw(r, Color.FromArgb(255, 255, 0, 0));
Draw(g, Color.FromArgb(255, 0, 255, 0));
Draw(b, Color.FromArgb(255, 0, 0, 255));
}

public CanvasBitmap ExportToCanvasBitmap(ICanvasResourceCreator resourceCreator)
{
var rt = new CanvasRenderTarget(resourceCreator, NumBins, Height, 96);

using (var ds = rt.CreateDrawingSession())
{
ds.Clear(Colors.Transparent);
Draw(ds);
}

return rt;
}
}
}
23 changes: 20 additions & 3 deletions Fotografix.Core/PhotoEditor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using System;
using System.Numerics;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics.Imaging;
Expand All @@ -11,17 +13,20 @@ public sealed class PhotoEditor : IRenderScaleProvider, IDisposable
{
private readonly IPhoto photo;
private readonly CanvasBitmap bitmap;
private readonly Transform2DEffect histogramTransform;
private PhotoAdjustment adjustment;
private bool dirty;

private PhotoEditor(IPhoto photo, CanvasBitmap bitmap)
{
this.photo = photo;
this.bitmap = bitmap;
this.histogramTransform = new Transform2DEffect { CacheOutput = true };
}

public void Dispose()
{
histogramTransform.Dispose();
adjustment.Dispose();
bitmap.Dispose();
}
Expand Down Expand Up @@ -67,7 +72,7 @@ public void Draw(CanvasDrawingSession ds)
public event EventHandler Invalidated;

public Size OriginalSize => bitmap.Size;
public Size RenderSize => adjustment.GetOutputSize(resourceCreator: bitmap);
public Size RenderSize => adjustment.GetOutputSize(ResourceCreator);

public float RenderScale
{
Expand Down Expand Up @@ -145,9 +150,9 @@ public CanvasBitmap ExportToCanvasBitmap(int? maxDimension = null)
var originalRenderScale = adjustment.RenderScale;
adjustment.RenderScale = 1;

var bounds = adjustment.Output.GetBounds(resourceCreator: bitmap);
var bounds = adjustment.Output.GetBounds(ResourceCreator);
var scaledSize = ScaleDimensions(new Size(bounds.Width, bounds.Height), maxDimension);
var rt = new CanvasRenderTarget(resourceCreator: bitmap, size: scaledSize);
var rt = new CanvasRenderTarget(ResourceCreator, size: scaledSize);

using (var ds = rt.CreateDrawingSession())
{
Expand All @@ -166,6 +171,18 @@ public async Task<SoftwareBitmap> ExportToSoftwareBitmapAsync(int? maxDimension
}
}

public Histogram ComputeHistogram()
{
var bounds = adjustment.Output.GetBounds(ResourceCreator);
var scaledSize = ScaleDimensions(new Size(bounds.Width, bounds.Height), ThumbnailSize);

histogramTransform.TransformMatrix = Matrix3x2.CreateScale((float)(scaledSize.Width / bounds.Width));
histogramTransform.Source = adjustment.Output;
return Histogram.Compute(histogramTransform, new Rect(0, 0, scaledSize.Width, scaledSize.Height), ResourceCreator);
}

private ICanvasResourceCreatorWithDpi ResourceCreator => bitmap;

private void SetAdjustment(PhotoAdjustment value)
{
if (value == null)
Expand Down
12 changes: 12 additions & 0 deletions Fotografix/EditorTemplates.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@

<DataTemplate x:Key="PhotoAdjustmentTemplate" x:DataType="local:EditorViewModel" x:DefaultBindMode="TwoWay">
<StackPanel>
<!-- Histogram -->
<Border BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="8" Margin="-8,0,0,8">
<Image Source="{x:Bind HistogramSource, Mode=OneWay}" />
</Border>

<!-- Light -->
<winui:Expander IsExpanded="True" Header="Light">
<StackPanel>
<Slider Header="Exposure" Value="{x:Bind Adjustment.Exposure}" />
Expand All @@ -132,6 +139,7 @@
</StackPanel>
</winui:Expander>

<!-- Color -->
<winui:Expander Header="Color">
<StackPanel>
<ToggleSwitch Margin="0,0,0,8" Header="Black &amp; white" IsOn="{x:Bind Adjustment.BlackAndWhite}" />
Expand All @@ -142,6 +150,7 @@
</StackPanel>
</winui:Expander>

<!-- HSL -->
<winui:Expander x:DefaultBindMode="OneWay" Header="HSL" Visibility="{x:Bind Adjustment.BlackAndWhite, Converter={StaticResource InvertedBoolToVisibilityConverter}}">
<Pivot Margin="4,-16,4,0">
<Pivot.HeaderTemplate>
Expand All @@ -156,12 +165,14 @@
</Pivot>
</winui:Expander>

<!-- B&W Mix -->
<winui:Expander x:DefaultBindMode="OneWay" Header="B&amp;W Mix"
Visibility="{x:Bind Adjustment.BlackAndWhite}"
IsExpanded="{x:Bind Adjustment.BlackAndWhite}"
Content="{x:Bind Adjustment.ColorRanges.LuminanceView}"
ContentTemplate="{StaticResource ColorRangeViewTemplate}" />

<!-- Detail -->
<winui:Expander Header="Detail">
<StackPanel>
<!-- Preview accuracy warning -->
Expand Down Expand Up @@ -219,6 +230,7 @@
</StackPanel>
</winui:Expander>

<!-- Crop -->
<winui:Expander Header="Crop" IsExpanded="{x:Bind CropMode}">
<StackPanel>
<TextBlock Margin="2,0,0,8" FontWeight="SemiBold" Text="Aspect ratio" />
Expand Down
36 changes: 35 additions & 1 deletion Fotografix/EditorViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using Fotografix.Input;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
using Windows.System;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Xaml.Media;

namespace Fotografix
{
Expand All @@ -16,6 +18,7 @@ public sealed class EditorViewModel : NotifyPropertyChangedBase, IPointerInputHa
public const int CropHandleSize = 6;

private readonly PhotoEditor editor;
private readonly ICanvasResourceCreatorWithDpi resourceCreator;
private readonly ScalingHelper scalingHelper;
private readonly CropTracker cropTracker;
private readonly CropOverlay cropOverlay;
Expand All @@ -31,6 +34,7 @@ public EditorViewModel(PhotoEditor editor, ICanvasResourceCreatorWithDpi resourc
this.editor = editor;
editor.Invalidated += (s, e) => Invalidate();

this.resourceCreator = resourceCreator;
this.scalingHelper = new ScalingHelper(resourceCreator, editor);
this.cropTracker = cropTracker;
cropTracker.RectChanged += (s, e) => Invalidate();
Expand Down Expand Up @@ -345,6 +349,36 @@ public async Task ExportAsync(bool launchFolderAfterExport = true)

#endregion

#region Histogram

private CanvasImageSource histogramSource;

public ImageSource HistogramSource
{
get
{
if (histogramSource == null)
{
this.histogramSource = new CanvasImageSource(resourceCreator, Histogram.RenderSize);
editor.Invalidated += (s, e) => UpdateHistogram();
UpdateHistogram();
}

return histogramSource;
}
}

private void UpdateHistogram()
{
var histogram = editor.ComputeHistogram();
using (var ds = histogramSource.CreateDrawingSession(Color.FromArgb(192, 48, 48, 48)))
{
histogram.Draw(ds);
}
}

#endregion

private void Invalidate()
{
Invalidated?.Invoke(this, EventArgs.Empty);
Expand Down

0 comments on commit 7379d6f

Please sign in to comment.