Skip to content

WIP: Process Uploaded Images #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions AllReady.Processing.UnitTest/AllReady.Processing.UnitTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="Moq" Version="4.8.1" />
<PackageReference Include="Shouldly" Version="3.0.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\AllReady.Processing\AllReady.Processing\AllReady.Processing.csproj" />
</ItemGroup>

</Project>
31 changes: 31 additions & 0 deletions AllReady.Processing.UnitTest/ImageHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.IO;
using Shouldly;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;

namespace AllReady.Processing.UnitTest
{
public static class ImageHelpers
{
public static Stream GetImageStream(int width, int height, IImageFormat format = null)
{
var stream = new MemoryStream();
using (var img = new Image<Rgba32>(width, height))
{
img.Save(stream, format ?? ImageFormats.Png);
}

stream.Seek(0, SeekOrigin.Begin);
return stream;
}

public static void ShouldBeImageWithDimensions(this Stream stream, int width, int height)
{
using (var img = Image.Load<Rgba32>(stream))
{
img.Width.ShouldBe(width);
img.Height.ShouldBe(height);
}
}
}
}
100 changes: 100 additions & 0 deletions AllReady.Processing.UnitTest/ImageResizeShould.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Microsoft.Azure.WebJobs.Host;
using Moq;
using Shouldly;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using Xunit;

namespace AllReady.Processing.UnitTest
{
public class ImageResizeShould
{
private const string BlobName = "/container/image.png";
private readonly TraceWriter _log = new Mock<TraceWriter>(TraceLevel.Verbose).Object;

public ImageResizeShould()
{
Environment.SetEnvironmentVariable("maxDimension", "400");
}

[Fact]
public void Not_resize_an_image_within_maxDimensions_in_both_directions()
{
using (var src = ImageHelpers.GetImageStream(400, 400))
using (var result = new MemoryStream())
{
ImageResize.Run(src, BlobName, result, _log);

result.Seek(0, SeekOrigin.Begin);
result.ReadByte().ShouldBe(-1);
}
}

[Fact]
public void Resize_an_image_with_width_larger_than_maxDimensions()
{
using (var src = ImageHelpers.GetImageStream(700, 400))
using (var result = new MemoryStream())
{
ImageResize.Run(src, BlobName, result, _log);

result.Seek(0, SeekOrigin.Begin);
result.ShouldBeImageWithDimensions(400, 228);
}
}

[Fact]
public void Resize_an_image_with_height_larger_than_maxDimensions()
{
using (var src = ImageHelpers.GetImageStream(250, 500))
using (var result = new MemoryStream())
{
ImageResize.Run(src, BlobName, result, _log);

result.Seek(0, SeekOrigin.Begin);
result.ShouldBeImageWithDimensions(200, 400);
}
}

[Fact]
public void Resize_an_image_with_both_dimensions_larger_than_maxDimensions()
{
using (var src = ImageHelpers.GetImageStream(500, 650))
using (var result = new MemoryStream())
{
ImageResize.Run(src, BlobName, result, _log);

result.Seek(0, SeekOrigin.Begin);
result.ShouldBeImageWithDimensions(307, 400);
}
}

public static readonly IEnumerable<object[]> TestImageFormats =
new[]
{
new object[] {ImageFormats.Png},
new object[] {ImageFormats.Gif},
new object[] {ImageFormats.Bmp},
new object[] {ImageFormats.Jpeg}
};

[Theory]
[MemberData(nameof(TestImageFormats))]
public void Maintain_the_same_image_format_when_resizing(IImageFormat expected)
{
using (var src = ImageHelpers.GetImageStream(500, 650, expected))
using (var result = new MemoryStream())
{
ImageResize.Run(src, BlobName, result, _log);

result.Seek(0, SeekOrigin.Begin);
var format = Image.DetectFormat(result);
format.ShouldBe(format);
}
}
}
}
21 changes: 21 additions & 0 deletions AllReady.Processing.UnitTest/QueueTestShould.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Diagnostics;
using Microsoft.Azure.WebJobs.Host;
using Moq;
using Xunit;

namespace AllReady.Processing.UnitTest
{
public class QueueTestShould
{
[Fact]
public void Log_that_the_queue_message_was_dequeued()
{
string item = "Test Item";
var mockTraceWriter = new Mock<TraceWriter>(TraceLevel.Verbose);

QueueTest.Run(item, mockTraceWriter.Object);

mockTraceWriter.Verify(x => x.Trace(It.Is<TraceEvent>(e => e.Level == TraceLevel.Info && e.Message.Contains(item))), Times.Once);
}
}
}
10 changes: 8 additions & 2 deletions AllReady.Processing/AllReady.Processing.sln
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
VisualStudioVersion = 15.0.27130.2026
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AllReady.Processing", "AllReady.Processing\AllReady.Processing.csproj", "{C619B518-08A3-4CFC-BC22-CB0B4A63CB31}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AllReady.Processing", "AllReady.Processing\AllReady.Processing.csproj", "{C619B518-08A3-4CFC-BC22-CB0B4A63CB31}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AllReady.Processing.UnitTest", "..\AllReady.Processing.UnitTest\AllReady.Processing.UnitTest.csproj", "{CBA25B72-7C92-4C78-ABF6-1E35079214B5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -15,6 +17,10 @@ Global
{C619B518-08A3-4CFC-BC22-CB0B4A63CB31}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C619B518-08A3-4CFC-BC22-CB0B4A63CB31}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C619B518-08A3-4CFC-BC22-CB0B4A63CB31}.Release|Any CPU.Build.0 = Release|Any CPU
{CBA25B72-7C92-4C78-ABF6-1E35079214B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBA25B72-7C92-4C78-ABF6-1E35079214B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBA25B72-7C92-4C78-ABF6-1E35079214B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBA25B72-7C92-4C78-ABF6-1E35079214B5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.6" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.8" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0002" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
Expand Down
13 changes: 13 additions & 0 deletions AllReady.Processing/AllReady.Processing/Configuration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace AllReady.Processing
{
public static class Configuration
{
public static string GetEnvironmentVariable(string key, string defaultValue = "") =>
Environment.GetEnvironmentVariable(key) ?? defaultValue;

public static int GetEnvironmentVariableAsInt(string key, int defaultValue = 0) =>
int.Parse(GetEnvironmentVariable(key, defaultValue.ToString()));
}
}
42 changes: 42 additions & 0 deletions AllReady.Processing/AllReady.Processing/ImageResize.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.IO;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;

namespace AllReady.Processing
{
public class ImageResize
{
[FunctionName("ImageResize")]
public static void Run(
[BlobTrigger("images/{name}")] Stream imageBlob,
string name,
[Blob("images/{name}", FileAccess.Write)] Stream outputBlob,
TraceWriter log)
{
int maxDimension = Configuration.GetEnvironmentVariableAsInt("maxDimension", 800);
using (var img = Image.Load<Rgba32>(imageBlob, out IImageFormat imageFormat))
{
int bigDimension = Math.Max(img.Width, img.Height);
if (bigDimension <= maxDimension)
{
log.Info($"Image {name} does not need resizing.");
return;
}

double ratio = bigDimension == img.Width
? (double) maxDimension / img.Width
: (double) maxDimension / img.Height;
int width = (int) (img.Width * ratio);
int height = (int) (img.Height * ratio);

log.Info($"Resizing image {name} to {width}x{height}");
img.Mutate(ctx => ctx.Resize(width, height));

img.Save(outputBlob, imageFormat);
}
}
}
}
2 changes: 1 addition & 1 deletion AllReady.Processing/AllReady.Processing/QueueTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static class QueueTest
// local storage emulator.

[FunctionName("QueueTest")]
public static void Run([QueueTrigger("queue-test", Connection = "")]string item, TraceWriter log)
public static void Run([QueueTrigger("queue-test")]string item, TraceWriter log)
{
log.Info($"A message was dequeued from the queue-test queue: {item}");
}
Expand Down