diff --git a/AWSFileUploaderWithImageCompression.Test/ImageServiceTest.cs b/AWSFileUploaderWithImageCompression.Test/ImageServiceTest.cs deleted file mode 100644 index b223c7d..0000000 --- a/AWSFileUploaderWithImageCompression.Test/ImageServiceTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -using NUnit.Framework; -using System; -using System.IO; -using System.Threading.Tasks; -using System.Text.Json; -using Moq; -using Amazon.S3.Model; - -namespace AWSFileUploaderWithImageCompression.Test -{ - public class ImageServiceTest - { -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - private IImageService imgService; -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - - - [OneTimeSetUp] - public void Setup() - { - //Mocking s3Uploader - var s3Uploader = new Mock(); - s3Uploader.Setup(s => s.UploadAsync(It.IsAny(), It.IsAny(), It.IsAny(), null)).Returns(Task.FromResult(new PutObjectResponse() { HttpStatusCode = System.Net.HttpStatusCode.OK })); - - imgService = new ImageService(s3Uploader.Object); - } - - - /// - /// This will test the resize and compression of large image files. - /// - [Test, TestCaseSource(typeof(TestSourceProvider), nameof(TestSourceProvider.GetLargeImages))] - public async Task CompressLargeImagesTestAsync(FileInfo originalFile) - { - //Arrange - var outputDirectory = new DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "Assets", "Compressed")).FullName; - if (!Directory.Exists(outputDirectory)) - Directory.CreateDirectory(outputDirectory); - var outputFileName = Path.Combine(outputDirectory, Guid.NewGuid().ToString() + originalFile.Extension); - - - //Act - var result = await imgService.ImageCompressor.CompressImage(originalFile.FullName, outputFileName); - var serializedResult = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); - - //Assert - if (result.ImageCompressionSucccess || (result.AfterCompressionSizeInBytes < result.OriginalSizeInBytes)) - Assert.Pass($"Successfully compressed the image\n-------------------------------------\nFileName: {originalFile.Name}\n{serializedResult}"); - else - Assert.Fail($"Compression Failed!\n-------------------------------------\nFileName: {originalFile.Name}\n{serializedResult}"); - - } - - /// - /// This will test that no compression should be performed if the images are already smaller in size. - /// - [Test, TestCaseSource(typeof(TestSourceProvider), nameof(TestSourceProvider.GetSmallImages))] - public async Task DontCompressSmallImageAsync(FileInfo originalFile) - { - //Arrange - var outputDirectory = new DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "Assets", "Compressed")).FullName; - if (!Directory.Exists(outputDirectory)) - Directory.CreateDirectory(outputDirectory); - var outputFileName = Path.Combine(outputDirectory, Guid.NewGuid().ToString() + originalFile.Extension); - - - //Act - var result = await imgService.ImageCompressor.CompressImage(originalFile.FullName, outputFileName); - _ = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); - - //Assert - Assert.IsTrue(result.ImageCompressionSucccess == false); - } - - /// - /// This will test the if the watermark to the image has been applied successfully. - /// - [Test] - public async Task WaterMarkTest() - { - //Arrange - var watermarkImage = File.OpenRead(Path.Combine(Environment.CurrentDirectory, "Assets", "Original", "SmallImages", "spiderman.png")); - var outputDirectory = new DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "Assets", "Watermark")).FullName; - if (!Directory.Exists(outputDirectory)) - Directory.CreateDirectory(outputDirectory); - var sourceFile = File.OpenRead(Path.Combine(Environment.CurrentDirectory, "Assets", "Original", "LargeImages", "2.jpg")); - var outputFile = $"{Path.Combine(outputDirectory)}{Guid.NewGuid()}.jpg"; - - - var s3Uploader = new Mock(); - s3Uploader.Setup(s => s.UploadAsync(It.IsAny(), It.IsAny(), It.IsAny(), null)).Returns(Task.FromResult(new PutObjectResponse() { HttpStatusCode = System.Net.HttpStatusCode.OK })); - - - imgService.ImageCompressor.UpdateImageServiceConfiguration(options => - { - options.WaterMarkTransperency = 5; //50% transparency - }); - - //Act - var success = await imgService.ImageCompressor.AddWaterMark(sourceFile, watermarkImage, outputFile); - - //Assert - Assert.IsTrue(success); - } - - - - /// - /// This will test the Compress and Upload - /// - [TestCase("2.jpg")] - [TestCase("3.jpg")] - [TestCase("4.jpg")] - public async Task CompressAndUploadTest(string fileName) - { - var sourceFile = File.OpenRead(Path.Combine(Environment.CurrentDirectory, "Assets", "Original", "LargeImages", fileName)); - var resp = await imgService.CompressAndUploadImageAsync(sourceFile); - - Assert.IsNotNull(resp); - Assert.IsTrue(resp.PutObjectResponse?.HttpStatusCode == System.Net.HttpStatusCode.OK); - } - - - [OneTimeTearDown] - public void ClearImages() - { - if (Directory.Exists(Path.Combine(Environment.CurrentDirectory, "Assets", "Compressed"))) - Directory.Delete(Path.Combine(Environment.CurrentDirectory, "Assets", "Compressed"), true); - if (Directory.Exists(Path.Combine(Environment.CurrentDirectory, "Assets", "Watermark"))) - Directory.Delete(Path.Combine(Environment.CurrentDirectory, "Assets", "Watermark"), true); - } - } - - -} \ No newline at end of file diff --git a/AWSFileUploaderWithImageCompression.Test/TestSourceProvider.cs b/AWSFileUploaderWithImageCompression.Test/TestSourceProvider.cs deleted file mode 100644 index 7ffb37c..0000000 --- a/AWSFileUploaderWithImageCompression.Test/TestSourceProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AWSFileUploaderWithImageCompression.Test -{ - public static class TestSourceProvider - { - public static FileInfo[] GetLargeImages() - { - return new DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "Assets", "Original", "LargeImages")).GetFiles(); - } - - public static FileInfo[] GetSmallImages() - { - return new DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "Assets", "Original", "SmallImages")).GetFiles(); - } - } -} diff --git a/AWSFileUploaderWithImageCompression.sln b/AWSFileUploaderWithImageCompression.sln index 89c55fe..7ce851b 100644 --- a/AWSFileUploaderWithImageCompression.sln +++ b/AWSFileUploaderWithImageCompression.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32421.90 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AWSFileUploaderWithImageCompression", "AWSFileUploaderWithImageCompression\AWSFileUploaderWithImageCompression.csproj", "{84E4CA04-3628-44E6-9F00-2696E89E4D27}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AWSFileUploaderWithImageCompression", "src\AWSFileUploaderWithImageCompression\AWSFileUploaderWithImageCompression.csproj", "{84E4CA04-3628-44E6-9F00-2696E89E4D27}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSFileUploaderWithImageCompression.Test", "AWSFileUploaderWithImageCompression.Test\AWSFileUploaderWithImageCompression.Test.csproj", "{B827FA59-AC7E-46C3-8180-472A4A9D32D3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSFileUploaderWithImageCompression.Test", "tests\AWSFileUploaderWithImageCompression.Test\AWSFileUploaderWithImageCompression.Test.csproj", "{B827FA59-AC7E-46C3-8180-472A4A9D32D3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/AWSFileUploaderWithImageCompression/AWSFileUploaderWithImageCompression.csproj b/AWSFileUploaderWithImageCompression/AWSFileUploaderWithImageCompression.csproj deleted file mode 100644 index 89d7c95..0000000 --- a/AWSFileUploaderWithImageCompression/AWSFileUploaderWithImageCompression.csproj +++ /dev/null @@ -1,46 +0,0 @@ - - - - net6.0 - enable - enable - uploaderIcon.ico - 1.1.2 - Muthukumar Thevar - A library for compressing, adding watermark and uploading the file into aws s3. - uploaderIcon.png - README.md - https://github.com/mak-thevar/AWSFileUploaderWithImageCompression - https://github.com/mak-thevar/AWSFileUploaderWithImageCompression - git - False - LICENSE - AWS, Image Uploader, Image Compressor, Image Watermark, S3, S3 Uploader, Amazon Web Services, Compress - - - - - - - True - \ - - - True - \ - - - - - - - - - - - True - \ - - - - diff --git a/AWSFileUploaderWithImageCompression/Classes/ImageCompressor.cs b/AWSFileUploaderWithImageCompression/Classes/ImageCompressor.cs deleted file mode 100644 index 41363c2..0000000 --- a/AWSFileUploaderWithImageCompression/Classes/ImageCompressor.cs +++ /dev/null @@ -1,137 +0,0 @@ -using AWSFileUploaderWithImageCompression.Models; -using ImageMagick; - -namespace AWSFileUploaderWithImageCompression -{ - public class ImageCompressor : IImageCompressor - { - private ImgCompressorConfiguration serviceConfiguration; - - public ImageCompressor(ImgCompressorConfiguration? serviceConfiguration = null) - { - if (serviceConfiguration == null) - serviceConfiguration = new ImgCompressorConfiguration(); - - this.serviceConfiguration = serviceConfiguration; - } - - public ImageCompressor(Action? serviceConfiguration = null) - { - if (serviceConfiguration == null) - serviceConfiguration = (x) => - { - _ = new ImgCompressorConfiguration(); - }; - - var imgCompressorConfig = new ImgCompressorConfiguration(); - serviceConfiguration.Invoke(imgCompressorConfig); - this.serviceConfiguration = imgCompressorConfig; - } - - public void UpdateImageServiceConfiguration(ImgCompressorConfiguration serviceConfiguration) - { - this.serviceConfiguration = serviceConfiguration; - } - - public void UpdateImageServiceConfiguration(Action serviceConfiguration) - { - var imgCompressorConfig = new ImgCompressorConfiguration(); - serviceConfiguration.Invoke(imgCompressorConfig); - this.serviceConfiguration = imgCompressorConfig; - } - - - public async Task AddWaterMark(Stream sourceImageStream, Stream waterMarkImageStream, string outputFilePath) - { - try - { - using var watermark = new MagickImage(waterMarkImageStream); - using var image = new MagickImage(sourceImageStream); - - watermark.Resize(image.Width - 10, image.Height * 25 / 100); - watermark.Evaluate(Channels.Alpha, EvaluateOperator.Divide,serviceConfiguration.WaterMarkTransperency); - - image.Composite(watermark, serviceConfiguration.WaterMarkPosition, CompositeOperator.Over); - - await image.WriteAsync(outputFilePath); - return true; - - } - catch (Exception) - { - - throw; - } - } - - public async Task CompressImage(Stream imageStream, string outputFilePath) - { - try - { - using var img = new MagickImage(imageStream); - var originalHeight = img.Height; - var originalWidth = img.Width; - if (img.Height > serviceConfiguration.MaxHeight || img.Width > serviceConfiguration.MaxWidth) - { - var imgGeo = new MagickGeometry - { - IgnoreAspectRatio = !serviceConfiguration.MaintainAspectRatio - }; - if (img.Height > serviceConfiguration.MaxHeight) - imgGeo.Height = serviceConfiguration.MaxHeight; - else if (img.Width > serviceConfiguration.MaxWidth) - imgGeo.Width = serviceConfiguration.MaxWidth; - img.Resize(imgGeo); - } - else - { - return new ImageCompressorResponse - { - ImageCompressionSucccess = false, - OriginalImageHeight = originalHeight, - OriginalImageWidth = originalWidth, - OriginalSizeInBytes = imageStream.Length, - }; - } - - using var memStream = new MemoryStream(); - await img.WriteAsync(memStream); - - var optimizer = new ImageOptimizer(); - memStream.Position = 0; - var comp = (img.Height >= serviceConfiguration.MaxHeight || img.Width >= serviceConfiguration.MaxWidth) && optimizer.LosslessCompress(memStream); - await File.WriteAllBytesAsync(outputFilePath, memStream.ToArray()); - using var outputImage = new MagickImage(memStream); - return new ImageCompressorResponse - { - ImageCompressionSucccess = comp, - OriginalSizeInBytes = imageStream.Length, - AfterCompressionSizeInBytes = memStream.Length, - OutputFilePath = outputFilePath, - OutPutFileStream = memStream, - OriginalImageWidth = originalWidth, - OriginalImageHeight = originalHeight, - ResizedImageHeight = outputImage.Height, - ResizedImageWidth = outputImage.Width, - }; - } - catch (Exception) - { - - throw; - } - - } - - public async Task AddWaterMark(string sourceImagePath, string watermarkImagePath, string outputFilePath) - { - return await AddWaterMark(File.OpenRead(sourceImagePath), File.OpenRead(watermarkImagePath), outputFilePath); - } - - public async Task CompressImage(string sourceImagePath, string outputFilePath) - { - return await CompressImage(File.OpenRead(sourceImagePath), outputFilePath); - } - - } -} diff --git a/AWSFileUploaderWithImageCompression/Classes/ImageService.cs b/AWSFileUploaderWithImageCompression/Classes/ImageService.cs deleted file mode 100644 index 21bc609..0000000 --- a/AWSFileUploaderWithImageCompression/Classes/ImageService.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Amazon.S3.Model; -using AWSFileUploaderWithImageCompression.Models; - -namespace AWSFileUploaderWithImageCompression -{ - public class ImageService : IImageService - { - private IImageCompressor imageComp; - private IS3ImageUploader s3Uploader; - - public ImageService(IS3ImageUploader s3ImageUploader, ImgCompressorConfiguration? imgCompressorConfiguration = null) - { - this.imageComp = new ImageCompressor(imgCompressorConfiguration); - this.s3Uploader = s3ImageUploader; - } - - public ImageService(IS3ImageUploader s3ImageUploader, Action? imgCompressorConfiguration) - { - this.imageComp = new ImageCompressor(imgCompressorConfiguration); - this.s3Uploader = s3ImageUploader; - } - - public IImageCompressor ImageCompressor { get => imageComp; set => imageComp = value; } - public IS3ImageUploader S3ImageUploader { get => s3Uploader; set => s3Uploader = value; } - - public async Task CompressAndUploadImageAsync(Stream sourceImageStream, string fileKeyName = "") - { - var tmpFile = Path.GetTempFileName(); - var comp = await imageComp.CompressImage(sourceImageStream, tmpFile); - if (!comp.ImageCompressionSucccess) - throw new Exception("Image compression failed."); - var uploaderResp = await s3Uploader.UploadAsync(File.OpenRead(tmpFile), key: fileKeyName); - return SendResponse(uploaderResp, comp); - } - - public async Task CompressAndUploadImageAsync(string sourceFilePath, string fileKeyName = "") - { - return await CompressAndUploadImageAsync(File.OpenRead(sourceFilePath), fileKeyName); - } - - public async Task CompressWaterMarkAndUploadAsync(Stream sourceImageStream, Stream watermarkImage, string fileKeyName = "") - { - var tmpFile = Path.GetTempFileName(); - _ = await this.imageComp.AddWaterMark(sourceImageStream, watermarkImage, tmpFile); - return await CompressAndUploadImageAsync(File.OpenRead(tmpFile), fileKeyName); - } - - public async Task CompressWaterMarkAndUploadAsync(string sourceFilePath, Stream watermarkImage, string fileKeyName = "") - { - return await CompressWaterMarkAndUploadAsync(File.OpenRead(sourceFilePath), watermarkImage, fileKeyName); - } - - public async Task CompressWaterMarkAndUploadAsync(string sourceFilePath, string watermarkImagePath, string fileKeyName = "") - { - return await CompressWaterMarkAndUploadAsync(File.OpenRead(sourceFilePath), File.OpenRead(watermarkImagePath), fileKeyName); - } - - private static ImageServiceResponse SendResponse(PutObjectResponse putObjectResponse, ImageCompressorResponse imageCompressorResponse) - { - return new ImageServiceResponse - { - ImageCompressorResponse = imageCompressorResponse, - PutObjectResponse = putObjectResponse, - }; - } - } -} diff --git a/AWSFileUploaderWithImageCompression/Classes/Models/ImageCompressorResponse.cs b/AWSFileUploaderWithImageCompression/Classes/Models/ImageCompressorResponse.cs deleted file mode 100644 index 8b35cfa..0000000 --- a/AWSFileUploaderWithImageCompression/Classes/Models/ImageCompressorResponse.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; - -namespace AWSFileUploaderWithImageCompression.Models -{ - public class ImageCompressorResponse - { - /// - /// Whether the lossless compression was successfull if false then the image has been just resized (If the image had exceeded the maxWidth | maxHeight). - /// - public bool ImageCompressionSucccess { get; set; } - public long OriginalSizeInBytes { get; set; } - public int OriginalImageWidth { get; set; } - public int OriginalImageHeight { get; set; } - public long AfterCompressionSizeInBytes { get; set; } - - public int ResizedImageHeight { get; set; } - public int ResizedImageWidth { get; set; } - - public string OutputFilePath { get; set; } = string.Empty; - - [JsonIgnore] - public Stream? OutPutFileStream { get; set; } = null; - } -} diff --git a/AWSFileUploaderWithImageCompression/Classes/Models/ImageServiceResponse.cs b/AWSFileUploaderWithImageCompression/Classes/Models/ImageServiceResponse.cs deleted file mode 100644 index f9c140a..0000000 --- a/AWSFileUploaderWithImageCompression/Classes/Models/ImageServiceResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Amazon.S3.Model; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AWSFileUploaderWithImageCompression.Models -{ - public class ImageServiceResponse - { - public PutObjectResponse? PutObjectResponse { get; set; } - public ImageCompressorResponse? ImageCompressorResponse { get; set; } - } -} diff --git a/AWSFileUploaderWithImageCompression/Classes/Models/ImgCompressorConfiguration.cs b/AWSFileUploaderWithImageCompression/Classes/Models/ImgCompressorConfiguration.cs deleted file mode 100644 index 444f072..0000000 --- a/AWSFileUploaderWithImageCompression/Classes/Models/ImgCompressorConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using ImageMagick; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AWSFileUploaderWithImageCompression.Models -{ - public class ImgCompressorConfiguration - { - public int MaxWidth { get; set; } = 1600; - public int MaxHeight { get; set; } = 1200; - public bool MaintainAspectRatio { get; set; } = true; - - /// - /// The direction to set the watermark - /// - public Gravity WaterMarkPosition { get; set; } = Gravity.Southeast; - /// - /// From 0 to 10, 10 = 100% transperency and 0 = 0% transperency - /// - public double WaterMarkTransperency { get; set; } = 3.5; - } -} diff --git a/AWSFileUploaderWithImageCompression/Classes/S3ImageUploader.cs b/AWSFileUploaderWithImageCompression/Classes/S3ImageUploader.cs deleted file mode 100644 index 5fea978..0000000 --- a/AWSFileUploaderWithImageCompression/Classes/S3ImageUploader.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Amazon; -using Amazon.Runtime; -using Amazon.S3; -using Amazon.S3.Model; - -namespace AWSFileUploaderWithImageCompression -{ - public class S3ImageUploader : IS3ImageUploader - { - private readonly string accessKey; - private readonly string secretKey; - private readonly RegionEndpoint regionEndpoint; - private readonly string _bucketName; - private readonly BasicAWSCredentials awsCred; - - public S3ImageUploader(string accessKey, string secretKey, RegionEndpoint regionEndpoint, string bucketName) - { - this.accessKey = accessKey; - this.secretKey = secretKey; - this.regionEndpoint = regionEndpoint; - this._bucketName = bucketName; - this.awsCred = new BasicAWSCredentials(this.accessKey, this.secretKey); - } - - public async Task UploadAsync(Stream sourceImageStream, string bucketName = "", string key = "", S3StorageClass? storageClass = null) - { - var s3Client = new AmazonS3Client(awsCred, regionEndpoint); - var putReq = new PutObjectRequest - { - BucketName = string.IsNullOrEmpty(bucketName) ? _bucketName : bucketName, - Key = string.IsNullOrEmpty(key) ? Guid.NewGuid().ToString() : key, - StorageClass = storageClass ?? S3StorageClass.Standard, - AutoResetStreamPosition = true, - InputStream = sourceImageStream, - }; - var putResp = await s3Client.PutObjectAsync(putReq); - return putResp; - } - - - public async Task UploadAsync(string sourceImagePath, string bucketName = "", string key = "", S3StorageClass? storageClass = null) - { - return await UploadAsync(File.OpenRead(sourceImagePath), bucketName, key, storageClass); - } - - - } -} diff --git a/AWSFileUploaderWithImageCompression/Interfaces/IImageCompressor.cs b/AWSFileUploaderWithImageCompression/Interfaces/IImageCompressor.cs deleted file mode 100644 index 7394835..0000000 --- a/AWSFileUploaderWithImageCompression/Interfaces/IImageCompressor.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AWSFileUploaderWithImageCompression.Models; - -namespace AWSFileUploaderWithImageCompression -{ - public interface IImageCompressor - { - Task AddWaterMark(Stream sourceImageStream, Stream waterMarkImageStream, string outputFilePath); - Task AddWaterMark(string sourceImagePath, string watermarkImagePath, string outputFilePath); - Task CompressImage(Stream imageStream, string outputFilePath); - Task CompressImage(string sourceImagePath, string outputFilePath); - void UpdateImageServiceConfiguration(ImgCompressorConfiguration serviceConfiguration); - void UpdateImageServiceConfiguration(Action serviceConfiguration); - } -} \ No newline at end of file diff --git a/AWSFileUploaderWithImageCompression/Interfaces/IImageService.cs b/AWSFileUploaderWithImageCompression/Interfaces/IImageService.cs deleted file mode 100644 index d6ce7c1..0000000 --- a/AWSFileUploaderWithImageCompression/Interfaces/IImageService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AWSFileUploaderWithImageCompression.Models; - -namespace AWSFileUploaderWithImageCompression -{ - public interface IImageService - { - IImageCompressor ImageCompressor { get; set; } - IS3ImageUploader S3ImageUploader { get; set; } - - Task CompressAndUploadImageAsync(Stream sourceImageStream, string fileKeyName = ""); - Task CompressWaterMarkAndUploadAsync(Stream sourceImageStream, Stream watermarkImage, string fileKeyName = ""); - - Task CompressAndUploadImageAsync(string sourceFilePath, string fileKeyName = ""); - Task CompressWaterMarkAndUploadAsync(string sourceFilePath, Stream watermarkImage, string fileKeyName = ""); - Task CompressWaterMarkAndUploadAsync(string sourceFilePath, string watermarkImagePath, string fileKeyName = ""); - } -} \ No newline at end of file diff --git a/AWSFileUploaderWithImageCompression/Interfaces/IS3ImageUploader.cs b/AWSFileUploaderWithImageCompression/Interfaces/IS3ImageUploader.cs deleted file mode 100644 index 7655331..0000000 --- a/AWSFileUploaderWithImageCompression/Interfaces/IS3ImageUploader.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Amazon.S3; -using Amazon.S3.Model; - -namespace AWSFileUploaderWithImageCompression -{ - public interface IS3ImageUploader - { - Task UploadAsync(Stream sourceImageStream, string bucketName = "", string key = "", S3StorageClass? storageClass = null); - Task UploadAsync(string sourceImagePath, string bucketName = "", string key = "", S3StorageClass? storageClass = null); - } -} \ No newline at end of file diff --git a/README.md b/README.md index c035b42..8cd8f0a 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,14 @@ [![LinkedIn](https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555)](https://www.linkedin.com/in/mak11/) # AWSFileUploaderWithImageCompression -A library for compressing, adding watermark and uploading the file into aws s3. +A library for compressing, adding watermark and uploading the file into aws s3. The library targets **.NET 9.0**. This library uses [Magic.NET](https://github.com/dlemstra/Magick.NET) for compressing and adding watermark to the image. +## Repository structure +- `src/` – library implementation. +- `tests/` – unit tests and sample assets. + ## Features - Compress the image in a simplied way. - Seperation of logic for uploader, compression and adding watermark. diff --git a/global.json b/global.json new file mode 100644 index 0000000..2bc13e8 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.100", + "rollForward": "latestMinor" + } +} diff --git a/src/AWSFileUploaderWithImageCompression/AWSFileUploaderWithImageCompression.csproj b/src/AWSFileUploaderWithImageCompression/AWSFileUploaderWithImageCompression.csproj new file mode 100644 index 0000000..a753c71 --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/AWSFileUploaderWithImageCompression.csproj @@ -0,0 +1,44 @@ + + + + net9.0 + enable + enable + uploaderIcon.ico + 1.1.2 + Muthukumar Thevar + A library for compressing, adding watermark and uploading the file into aws s3. + uploaderIcon.png + README.md + https://github.com/mak-thevar/AWSFileUploaderWithImageCompression + https://github.com/mak-thevar/AWSFileUploaderWithImageCompression + git + False + LICENSE + AWS, Image Uploader, Image Compressor, Image Watermark, S3, S3 Uploader, Amazon Web Services, Compress + + + + + True + \ + + + True + \ + + + + + + + + + + + True + \ + + + + diff --git a/src/AWSFileUploaderWithImageCompression/Classes/ImageCompressor.cs b/src/AWSFileUploaderWithImageCompression/Classes/ImageCompressor.cs new file mode 100644 index 0000000..b610aac --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Classes/ImageCompressor.cs @@ -0,0 +1,136 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using AWSFileUploaderWithImageCompression.Models; +using ImageMagick; + +namespace AWSFileUploaderWithImageCompression; + +public class ImageCompressor : IImageCompressor +{ + private ImgCompressorConfiguration serviceConfiguration; + + public ImageCompressor(ImgCompressorConfiguration? serviceConfiguration = null) + { + this.serviceConfiguration = serviceConfiguration ?? new ImgCompressorConfiguration(); + } + + public ImageCompressor(Action? serviceConfiguration = null) + { + var imgCompressorConfig = new ImgCompressorConfiguration(); + serviceConfiguration?.Invoke(imgCompressorConfig); + this.serviceConfiguration = imgCompressorConfig; + } + + public void UpdateImageServiceConfiguration(ImgCompressorConfiguration serviceConfiguration) + { + this.serviceConfiguration = serviceConfiguration ?? throw new ArgumentNullException(nameof(serviceConfiguration)); + } + + public void UpdateImageServiceConfiguration(Action serviceConfiguration) + { + ArgumentNullException.ThrowIfNull(serviceConfiguration); + var imgCompressorConfig = new ImgCompressorConfiguration(); + serviceConfiguration.Invoke(imgCompressorConfig); + this.serviceConfiguration = imgCompressorConfig; + } + + public async Task AddWaterMark(Stream sourceImageStream, Stream waterMarkImageStream, string outputFilePath) + { + ArgumentNullException.ThrowIfNull(sourceImageStream); + ArgumentNullException.ThrowIfNull(waterMarkImageStream); + ArgumentException.ThrowIfNullOrWhiteSpace(outputFilePath); + + using var watermark = new MagickImage(waterMarkImageStream); + using var image = new MagickImage(sourceImageStream); + + watermark.Resize(Math.Max(1, image.Width - 10), image.Height * 25 / 100); + watermark.Evaluate(Channels.Alpha, EvaluateOperator.Divide, serviceConfiguration.WaterMarkTransperency); + + image.Composite(watermark, serviceConfiguration.WaterMarkPosition, CompositeOperator.Over); + + await image.WriteAsync(outputFilePath).ConfigureAwait(false); + return true; + } + + public async Task CompressImage(Stream imageStream, string outputFilePath) + { + ArgumentNullException.ThrowIfNull(imageStream); + ArgumentException.ThrowIfNullOrWhiteSpace(outputFilePath); + + using var img = new MagickImage(imageStream); + var originalHeight = img.Height; + var originalWidth = img.Width; + + if (img.Height <= serviceConfiguration.MaxHeight && img.Width <= serviceConfiguration.MaxWidth) + { + return new ImageCompressorResponse + { + ImageCompressionSucccess = false, + OriginalImageHeight = originalHeight, + OriginalImageWidth = originalWidth, + OriginalSizeInBytes = imageStream.Length, + }; + } + + var imgGeo = new MagickGeometry + { + IgnoreAspectRatio = !serviceConfiguration.MaintainAspectRatio, + }; + + if (img.Height > serviceConfiguration.MaxHeight) + { + imgGeo.Height = serviceConfiguration.MaxHeight; + } + else if (img.Width > serviceConfiguration.MaxWidth) + { + imgGeo.Width = serviceConfiguration.MaxWidth; + } + + img.Resize(imgGeo); + + using var memStream = new MemoryStream(); + await img.WriteAsync(memStream).ConfigureAwait(false); + + memStream.Position = 0; + var optimizer = new ImageOptimizer(); + var compressionEligible = img.Height >= serviceConfiguration.MaxHeight || img.Width >= serviceConfiguration.MaxWidth; + var compressionSucceeded = compressionEligible && optimizer.LosslessCompress(memStream); + + memStream.Position = 0; + var compressedBytes = memStream.ToArray(); + await File.WriteAllBytesAsync(outputFilePath, compressedBytes).ConfigureAwait(false); + + using var outputImage = new MagickImage(compressedBytes); + return new ImageCompressorResponse + { + ImageCompressionSucccess = compressionSucceeded, + OriginalSizeInBytes = imageStream.Length, + AfterCompressionSizeInBytes = compressedBytes.LongLength, + OutputFilePath = outputFilePath, + OutPutFileStream = new MemoryStream(compressedBytes, writable: false), + OriginalImageWidth = originalWidth, + OriginalImageHeight = originalHeight, + ResizedImageHeight = outputImage.Height, + ResizedImageWidth = outputImage.Width, + }; + } + + public async Task AddWaterMark(string sourceImagePath, string watermarkImagePath, string outputFilePath) + { + ArgumentException.ThrowIfNullOrWhiteSpace(sourceImagePath); + ArgumentException.ThrowIfNullOrWhiteSpace(watermarkImagePath); + + await using var sourceStream = File.OpenRead(sourceImagePath); + await using var watermarkStream = File.OpenRead(watermarkImagePath); + return await AddWaterMark(sourceStream, watermarkStream, outputFilePath).ConfigureAwait(false); + } + + public async Task CompressImage(string sourceImagePath, string outputFilePath) + { + ArgumentException.ThrowIfNullOrWhiteSpace(sourceImagePath); + + await using var sourceStream = File.OpenRead(sourceImagePath); + return await CompressImage(sourceStream, outputFilePath).ConfigureAwait(false); + } +} diff --git a/src/AWSFileUploaderWithImageCompression/Classes/ImageService.cs b/src/AWSFileUploaderWithImageCompression/Classes/ImageService.cs new file mode 100644 index 0000000..f770ead --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Classes/ImageService.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Amazon.S3.Model; +using AWSFileUploaderWithImageCompression.Models; + +namespace AWSFileUploaderWithImageCompression; + +public class ImageService : IImageService, IAsyncDisposable +{ + private IImageCompressor imageComp; + private IS3ImageUploader s3Uploader; + + public ImageService(IS3ImageUploader s3ImageUploader, ImgCompressorConfiguration? imgCompressorConfiguration = null) + { + ArgumentNullException.ThrowIfNull(s3ImageUploader); + + imageComp = new ImageCompressor(imgCompressorConfiguration); + s3Uploader = s3ImageUploader; + } + + public ImageService(IS3ImageUploader s3ImageUploader, Action? imgCompressorConfiguration) + { + ArgumentNullException.ThrowIfNull(s3ImageUploader); + + imageComp = new ImageCompressor(imgCompressorConfiguration); + s3Uploader = s3ImageUploader; + } + + public IImageCompressor ImageCompressor + { + get => imageComp; + set => imageComp = value ?? throw new ArgumentNullException(nameof(value)); + } + + public IS3ImageUploader S3ImageUploader + { + get => s3Uploader; + set => s3Uploader = value ?? throw new ArgumentNullException(nameof(value)); + } + + public async Task CompressAndUploadImageAsync(Stream sourceImageStream, string fileKeyName = "") + { + ArgumentNullException.ThrowIfNull(sourceImageStream); + + var tmpFile = Path.GetTempFileName(); + try + { + var comp = await imageComp.CompressImage(sourceImageStream, tmpFile).ConfigureAwait(false); + if (!comp.ImageCompressionSucccess) + { + throw new InvalidOperationException("Image compression failed."); + } + + await using var tempStream = File.Open(tmpFile, FileMode.Open, FileAccess.Read, FileShare.Read); + var uploaderResp = await s3Uploader.UploadAsync(tempStream, key: fileKeyName).ConfigureAwait(false); + return SendResponse(uploaderResp, comp); + } + finally + { + if (File.Exists(tmpFile)) + { + File.Delete(tmpFile); + } + } + } + + public async Task CompressAndUploadImageAsync(string sourceFilePath, string fileKeyName = "") + { + ArgumentException.ThrowIfNullOrWhiteSpace(sourceFilePath); + + await using var sourceStream = File.OpenRead(sourceFilePath); + return await CompressAndUploadImageAsync(sourceStream, fileKeyName).ConfigureAwait(false); + } + + public async Task CompressWaterMarkAndUploadAsync(Stream sourceImageStream, Stream watermarkImage, string fileKeyName = "") + { + ArgumentNullException.ThrowIfNull(sourceImageStream); + ArgumentNullException.ThrowIfNull(watermarkImage); + + var tmpFile = Path.GetTempFileName(); + try + { + var watermarkApplied = await imageComp.AddWaterMark(sourceImageStream, watermarkImage, tmpFile).ConfigureAwait(false); + if (!watermarkApplied) + { + throw new InvalidOperationException("Applying watermark failed."); + } + + await using var tempStream = File.Open(tmpFile, FileMode.Open, FileAccess.Read, FileShare.Read); + return await CompressAndUploadImageAsync(tempStream, fileKeyName).ConfigureAwait(false); + } + finally + { + if (File.Exists(tmpFile)) + { + File.Delete(tmpFile); + } + } + } + + public async Task CompressWaterMarkAndUploadAsync(string sourceFilePath, Stream watermarkImage, string fileKeyName = "") + { + ArgumentException.ThrowIfNullOrWhiteSpace(sourceFilePath); + ArgumentNullException.ThrowIfNull(watermarkImage); + + await using var sourceStream = File.OpenRead(sourceFilePath); + return await CompressWaterMarkAndUploadAsync(sourceStream, watermarkImage, fileKeyName).ConfigureAwait(false); + } + + public async Task CompressWaterMarkAndUploadAsync(string sourceFilePath, string watermarkImagePath, string fileKeyName = "") + { + ArgumentException.ThrowIfNullOrWhiteSpace(sourceFilePath); + ArgumentException.ThrowIfNullOrWhiteSpace(watermarkImagePath); + + await using var sourceStream = File.OpenRead(sourceFilePath); + await using var watermarkStream = File.OpenRead(watermarkImagePath); + return await CompressWaterMarkAndUploadAsync(sourceStream, watermarkStream, fileKeyName).ConfigureAwait(false); + } + + private static ImageServiceResponse SendResponse(PutObjectResponse putObjectResponse, ImageCompressorResponse imageCompressorResponse) + { + return new ImageServiceResponse + { + ImageCompressorResponse = imageCompressorResponse, + PutObjectResponse = putObjectResponse, + }; + } + + public ValueTask DisposeAsync() + { + return s3Uploader.DisposeAsync(); + } +} diff --git a/src/AWSFileUploaderWithImageCompression/Classes/Models/ImageCompressorResponse.cs b/src/AWSFileUploaderWithImageCompression/Classes/Models/ImageCompressorResponse.cs new file mode 100644 index 0000000..14fbca1 --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Classes/Models/ImageCompressorResponse.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Text.Json.Serialization; + +namespace AWSFileUploaderWithImageCompression.Models; + +public class ImageCompressorResponse +{ + /// + /// Whether the lossless compression was successful; if false then the image has been just resized (if the image had exceeded the maxWidth | maxHeight). + /// + public bool ImageCompressionSucccess { get; set; } + public long OriginalSizeInBytes { get; set; } + public int OriginalImageWidth { get; set; } + public int OriginalImageHeight { get; set; } + public long AfterCompressionSizeInBytes { get; set; } + + public int ResizedImageHeight { get; set; } + public int ResizedImageWidth { get; set; } + + public string OutputFilePath { get; set; } = string.Empty; + + [JsonIgnore] + public Stream? OutPutFileStream { get; set; } +} diff --git a/src/AWSFileUploaderWithImageCompression/Classes/Models/ImageServiceResponse.cs b/src/AWSFileUploaderWithImageCompression/Classes/Models/ImageServiceResponse.cs new file mode 100644 index 0000000..4ef093a --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Classes/Models/ImageServiceResponse.cs @@ -0,0 +1,9 @@ +using Amazon.S3.Model; + +namespace AWSFileUploaderWithImageCompression.Models; + +public class ImageServiceResponse +{ + public PutObjectResponse? PutObjectResponse { get; set; } + public ImageCompressorResponse? ImageCompressorResponse { get; set; } +} diff --git a/src/AWSFileUploaderWithImageCompression/Classes/Models/ImgCompressorConfiguration.cs b/src/AWSFileUploaderWithImageCompression/Classes/Models/ImgCompressorConfiguration.cs new file mode 100644 index 0000000..e550d0e --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Classes/Models/ImgCompressorConfiguration.cs @@ -0,0 +1,20 @@ +using ImageMagick; + +namespace AWSFileUploaderWithImageCompression.Models; + +public class ImgCompressorConfiguration +{ + public int MaxWidth { get; set; } = 1600; + public int MaxHeight { get; set; } = 1200; + public bool MaintainAspectRatio { get; set; } = true; + + /// + /// The direction to set the watermark + /// + public Gravity WaterMarkPosition { get; set; } = Gravity.Southeast; + + /// + /// From 0 to 10, 10 = 100% transparency and 0 = 0% transparency + /// + public double WaterMarkTransperency { get; set; } = 3.5; +} diff --git a/src/AWSFileUploaderWithImageCompression/Classes/S3ImageUploader.cs b/src/AWSFileUploaderWithImageCompression/Classes/S3ImageUploader.cs new file mode 100644 index 0000000..d2ac934 --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Classes/S3ImageUploader.cs @@ -0,0 +1,90 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Amazon.S3; +using Amazon.S3.Model; + +namespace AWSFileUploaderWithImageCompression; + +public sealed class S3ImageUploader : IS3ImageUploader +{ + private readonly IAmazonS3 s3Client; + private readonly string bucketName; + private readonly bool ownsClient; + + public S3ImageUploader(string accessKey, string secretKey, RegionEndpoint regionEndpoint, string bucketName) + : this(CreateClient(accessKey, secretKey, regionEndpoint), bucketName, true) + { + } + + public S3ImageUploader(IAmazonS3 amazonS3Client, string bucketName) + : this(amazonS3Client, bucketName, false) + { + } + + private S3ImageUploader(IAmazonS3 amazonS3Client, string bucketName, bool ownsClient) + { + s3Client = amazonS3Client ?? throw new ArgumentNullException(nameof(amazonS3Client)); + if (string.IsNullOrWhiteSpace(bucketName)) + { + throw new ArgumentException("Bucket name must be provided.", nameof(bucketName)); + } + + this.bucketName = bucketName; + this.ownsClient = ownsClient; + } + + public async Task UploadAsync(Stream sourceImageStream, string bucketName = "", string key = "", S3StorageClass? storageClass = null) + { + ArgumentNullException.ThrowIfNull(sourceImageStream); + + var putReq = new PutObjectRequest + { + BucketName = string.IsNullOrWhiteSpace(bucketName) ? this.bucketName : bucketName, + Key = string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString() : key, + StorageClass = storageClass ?? S3StorageClass.Standard, + AutoResetStreamPosition = true, + InputStream = sourceImageStream, + }; + + return await s3Client.PutObjectAsync(putReq).ConfigureAwait(false); + } + + public async Task UploadAsync(string sourceImagePath, string bucketName = "", string key = "", S3StorageClass? storageClass = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(sourceImagePath); + + await using var sourceStream = File.OpenRead(sourceImagePath); + return await UploadAsync(sourceStream, bucketName, key, storageClass).ConfigureAwait(false); + } + + public async ValueTask DisposeAsync() + { + if (!ownsClient) + { + return; + } + + switch (s3Client) + { + case IAsyncDisposable asyncDisposable: + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + break; + case IDisposable disposable: + disposable.Dispose(); + break; + } + } + + private static IAmazonS3 CreateClient(string accessKey, string secretKey, RegionEndpoint regionEndpoint) + { + ArgumentException.ThrowIfNullOrWhiteSpace(accessKey); + ArgumentException.ThrowIfNullOrWhiteSpace(secretKey); + ArgumentNullException.ThrowIfNull(regionEndpoint); + + var awsCred = new BasicAWSCredentials(accessKey, secretKey); + return new AmazonS3Client(awsCred, regionEndpoint); + } +} diff --git a/src/AWSFileUploaderWithImageCompression/Interfaces/IImageCompressor.cs b/src/AWSFileUploaderWithImageCompression/Interfaces/IImageCompressor.cs new file mode 100644 index 0000000..48d1c33 --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Interfaces/IImageCompressor.cs @@ -0,0 +1,15 @@ +using System.IO; +using System.Threading.Tasks; +using AWSFileUploaderWithImageCompression.Models; + +namespace AWSFileUploaderWithImageCompression; + +public interface IImageCompressor +{ + Task AddWaterMark(Stream sourceImageStream, Stream waterMarkImageStream, string outputFilePath); + Task AddWaterMark(string sourceImagePath, string watermarkImagePath, string outputFilePath); + Task CompressImage(Stream imageStream, string outputFilePath); + Task CompressImage(string sourceImagePath, string outputFilePath); + void UpdateImageServiceConfiguration(ImgCompressorConfiguration serviceConfiguration); + void UpdateImageServiceConfiguration(Action serviceConfiguration); +} diff --git a/src/AWSFileUploaderWithImageCompression/Interfaces/IImageService.cs b/src/AWSFileUploaderWithImageCompression/Interfaces/IImageService.cs new file mode 100644 index 0000000..f83245a --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Interfaces/IImageService.cs @@ -0,0 +1,18 @@ +using System.IO; +using System.Threading.Tasks; +using AWSFileUploaderWithImageCompression.Models; + +namespace AWSFileUploaderWithImageCompression; + +public interface IImageService +{ + IImageCompressor ImageCompressor { get; set; } + IS3ImageUploader S3ImageUploader { get; set; } + + Task CompressAndUploadImageAsync(Stream sourceImageStream, string fileKeyName = ""); + Task CompressWaterMarkAndUploadAsync(Stream sourceImageStream, Stream watermarkImage, string fileKeyName = ""); + + Task CompressAndUploadImageAsync(string sourceFilePath, string fileKeyName = ""); + Task CompressWaterMarkAndUploadAsync(string sourceFilePath, Stream watermarkImage, string fileKeyName = ""); + Task CompressWaterMarkAndUploadAsync(string sourceFilePath, string watermarkImagePath, string fileKeyName = ""); +} diff --git a/src/AWSFileUploaderWithImageCompression/Interfaces/IS3ImageUploader.cs b/src/AWSFileUploaderWithImageCompression/Interfaces/IS3ImageUploader.cs new file mode 100644 index 0000000..bfa5918 --- /dev/null +++ b/src/AWSFileUploaderWithImageCompression/Interfaces/IS3ImageUploader.cs @@ -0,0 +1,12 @@ +using System.IO; +using System.Threading.Tasks; +using Amazon.S3; +using Amazon.S3.Model; + +namespace AWSFileUploaderWithImageCompression; + +public interface IS3ImageUploader : IAsyncDisposable +{ + Task UploadAsync(Stream sourceImageStream, string bucketName = "", string key = "", S3StorageClass? storageClass = null); + Task UploadAsync(string sourceImagePath, string bucketName = "", string key = "", S3StorageClass? storageClass = null); +} diff --git a/AWSFileUploaderWithImageCompression/uploaderIcon.ico b/src/AWSFileUploaderWithImageCompression/uploaderIcon.ico similarity index 100% rename from AWSFileUploaderWithImageCompression/uploaderIcon.ico rename to src/AWSFileUploaderWithImageCompression/uploaderIcon.ico diff --git a/AWSFileUploaderWithImageCompression/uploaderIcon.png b/src/AWSFileUploaderWithImageCompression/uploaderIcon.png similarity index 100% rename from AWSFileUploaderWithImageCompression/uploaderIcon.png rename to src/AWSFileUploaderWithImageCompression/uploaderIcon.png diff --git a/AWSFileUploaderWithImageCompression.Test/AWSFileUploaderWithImageCompression.Test.csproj b/tests/AWSFileUploaderWithImageCompression.Test/AWSFileUploaderWithImageCompression.Test.csproj similarity index 83% rename from AWSFileUploaderWithImageCompression.Test/AWSFileUploaderWithImageCompression.Test.csproj rename to tests/AWSFileUploaderWithImageCompression.Test/AWSFileUploaderWithImageCompression.Test.csproj index a24a75d..da97723 100644 --- a/AWSFileUploaderWithImageCompression.Test/AWSFileUploaderWithImageCompression.Test.csproj +++ b/tests/AWSFileUploaderWithImageCompression.Test/AWSFileUploaderWithImageCompression.Test.csproj @@ -1,9 +1,8 @@ - net6.0 + net9.0 enable - false @@ -50,15 +49,15 @@ - - - - - + + + + + - + diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/2.jpg b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/2.jpg similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/2.jpg rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/2.jpg diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/3.jpg b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/3.jpg similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/3.jpg rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/3.jpg diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/4.jpg b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/4.jpg similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/4.jpg rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/4.jpg diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/5.jpg b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/5.jpg similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/5.jpg rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/5.jpg diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/spiderman_original.jpg b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/spiderman_original.jpg similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/spiderman_original.jpg rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/LargeImages/spiderman_original.jpg diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/batman.png b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/batman.png similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/batman.png rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/batman.png diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/compressed.jpg b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/compressed.jpg similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/compressed.jpg rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/compressed.jpg diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/shield.png b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/shield.png similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/shield.png rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/shield.png diff --git a/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/spiderman.png b/tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/spiderman.png similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/spiderman.png rename to tests/AWSFileUploaderWithImageCompression.Test/Assets/Original/SmallImages/spiderman.png diff --git a/tests/AWSFileUploaderWithImageCompression.Test/ImageServiceTest.cs b/tests/AWSFileUploaderWithImageCompression.Test/ImageServiceTest.cs new file mode 100644 index 0000000..44ffd29 --- /dev/null +++ b/tests/AWSFileUploaderWithImageCompression.Test/ImageServiceTest.cs @@ -0,0 +1,117 @@ +using System; +using System.IO; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.S3.Model; +using Moq; +using NUnit.Framework; + +namespace AWSFileUploaderWithImageCompression.Test; + +[TestFixture] +public class ImageServiceTest +{ + private IImageService imgService = null!; + + [OneTimeSetUp] + public void Setup() + { + var s3Uploader = new Mock(); + s3Uploader.Setup(s => s.UploadAsync(It.IsAny(), It.IsAny(), It.IsAny(), null)) + .ReturnsAsync(new PutObjectResponse { HttpStatusCode = HttpStatusCode.OK }); + s3Uploader.Setup(s => s.DisposeAsync()).Returns(ValueTask.CompletedTask); + + imgService = new ImageService(s3Uploader.Object); + } + + /// + /// This will test the resize and compression of large image files. + /// + [Test, TestCaseSource(typeof(TestSourceProvider), nameof(TestSourceProvider.GetLargeImages))] + public async Task CompressLargeImagesTestAsync(FileInfo originalFile) + { + var outputDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Compressed"); + Directory.CreateDirectory(outputDirectory); + var outputFileName = Path.Combine(outputDirectory, Guid.NewGuid() + originalFile.Extension); + + var result = await imgService.ImageCompressor.CompressImage(originalFile.FullName, outputFileName); + var serializedResult = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); + + if (result.ImageCompressionSucccess || result.AfterCompressionSizeInBytes < result.OriginalSizeInBytes) + { + Assert.Pass($"Successfully compressed the image\n-------------------------------------\nFileName: {originalFile.Name}\n{serializedResult}"); + } + + Assert.Fail($"Compression Failed!\n-------------------------------------\nFileName: {originalFile.Name}\n{serializedResult}"); + } + + /// + /// This will test that no compression should be performed if the images are already smaller in size. + /// + [Test, TestCaseSource(typeof(TestSourceProvider), nameof(TestSourceProvider.GetSmallImages))] + public async Task DontCompressSmallImageAsync(FileInfo originalFile) + { + var outputDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Compressed"); + Directory.CreateDirectory(outputDirectory); + var outputFileName = Path.Combine(outputDirectory, Guid.NewGuid() + originalFile.Extension); + + var result = await imgService.ImageCompressor.CompressImage(originalFile.FullName, outputFileName); + _ = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); + + Assert.IsFalse(result.ImageCompressionSucccess); + } + + /// + /// This will test the if the watermark to the image has been applied successfully. + /// + [Test] + public async Task WaterMarkTest() + { + await using var watermarkImage = File.OpenRead(Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Original", "SmallImages", "spiderman.png")); + var outputDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Watermark"); + Directory.CreateDirectory(outputDirectory); + await using var sourceFile = File.OpenRead(Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Original", "LargeImages", "2.jpg")); + var outputFile = Path.Combine(outputDirectory, $"{Guid.NewGuid()}.jpg"); + + imgService.ImageCompressor.UpdateImageServiceConfiguration(options => + { + options.WaterMarkTransperency = 5; + }); + + var success = await imgService.ImageCompressor.AddWaterMark(sourceFile, watermarkImage, outputFile); + + Assert.IsTrue(success); + } + + /// + /// This will test the Compress and Upload + /// + [TestCase("2.jpg")] + [TestCase("3.jpg")] + [TestCase("4.jpg")] + public async Task CompressAndUploadTest(string fileName) + { + await using var sourceFile = File.OpenRead(Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Original", "LargeImages", fileName)); + var resp = await imgService.CompressAndUploadImageAsync(sourceFile); + + Assert.IsNotNull(resp); + Assert.That(resp.PutObjectResponse?.HttpStatusCode, Is.EqualTo(HttpStatusCode.OK)); + } + + [OneTimeTearDown] + public void ClearImages() + { + var compressedPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Compressed"); + if (Directory.Exists(compressedPath)) + { + Directory.Delete(compressedPath, true); + } + + var watermarkPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Watermark"); + if (Directory.Exists(watermarkPath)) + { + Directory.Delete(watermarkPath, true); + } + } +} diff --git a/AWSFileUploaderWithImageCompression.Test/Properties/launchSettings.json b/tests/AWSFileUploaderWithImageCompression.Test/Properties/launchSettings.json similarity index 100% rename from AWSFileUploaderWithImageCompression.Test/Properties/launchSettings.json rename to tests/AWSFileUploaderWithImageCompression.Test/Properties/launchSettings.json diff --git a/tests/AWSFileUploaderWithImageCompression.Test/TestSourceProvider.cs b/tests/AWSFileUploaderWithImageCompression.Test/TestSourceProvider.cs new file mode 100644 index 0000000..53fc33b --- /dev/null +++ b/tests/AWSFileUploaderWithImageCompression.Test/TestSourceProvider.cs @@ -0,0 +1,19 @@ +using System.IO; +using NUnit.Framework; + +namespace AWSFileUploaderWithImageCompression.Test; + +public static class TestSourceProvider +{ + public static FileInfo[] GetLargeImages() + { + var directory = new DirectoryInfo(Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Original", "LargeImages")); + return directory.GetFiles(); + } + + public static FileInfo[] GetSmallImages() + { + var directory = new DirectoryInfo(Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Original", "SmallImages")); + return directory.GetFiles(); + } +}