GrapeCity Document for Imaging (or GcImaging) is a high-speed, feature-rich API to process images in .NET Core targeted applications. You can create, load, modify, crop, resize, convert, and draw images.
Earlier we discussed how GrapeCity Documents can be used to generate documents (PDF and Excel) on top of AWS Lambda. In this article, we'll discuss how we can leverage this compatibility of GrapeCity Documents with Lambda functions and extend this to GcImaging for creating an imaging service over AWS S3 events.
Here are the steps for creating an imaging service over AWS S3 events with GrapeCity Documents:
GcImaging is distributed as standalone NuGet packages, available directly from NuGet.org.
NuGet Package Manager
Install-Package GrapeCity.Documents.Imaging
Dotnet CLI
dotnet add package GrapeCity.Documents.Imaging
In this blog, we'll design a service that reads an image uploaded to S3 and creates a grayscale thumbnail with a watermark. Here are the operations that we'll perform:
There are plenty more operations that you can perform with GcImaging.
Amazon S3 publishes events (for example, when an object is created in a bucket) to AWS Lambda and invokes your Lambda function by passing the event data as a parameter. This integration lets you write Lambda functions that process Amazon S3 events. In Amazon S3, you can add a bucket notification configuration identifying the type of event you want Amazon S3 to publish and the Lambda function you want to invoke. This notification system is used to manipulate the image which is uploaded to a separate bucket.
We can create a Lambda function that this bucket would invoke after an image is uploaded into it. Then this function reads the image and uploads a manipulated image into another bucket. A high-level design of our imaging service would look like the following:
The flow of this diagram can be described as:
The AWSLambdaExecute policy has the permissions that the function needs to manage objects in Amazon S3. Next, we will create a Lambda function containing the code to fetch, modify, and upload the image to an S3 bucket.
You can download the project from GitHub at this location "GC Imaging AWS Lambda S3."
using System;
using System.IO;
using System.Drawing;
using GrapeCity.Documents.Drawing;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Imaging;
namespace GCImagingAWSLambdaS3
{
public class GcImagingOperations
{
public static string GetConvertedImage(byte[] stream)
{
using (var bmp = new GcBitmap())
{
bmp.Load(stream);
// Add watermark
var newImg = new GcBitmap();
newImg.Load(stream);
using (var g = bmp.CreateGraphics(Color.White))
{
g.DrawImage(
newImg,
new RectangleF(0, 0, bmp.Width, bmp.Height),
null,
ImageAlign.Default
);
g.DrawString("DOCUMENT", new TextFormat
{
FontSize = 22,
ForeColor = Color.FromArgb(128, Color.Yellow),
Font = FontCollection.SystemFonts.DefaultFont
},
new RectangleF(0, 0, bmp.Width, bmp.Height),
TextAlignment.Center, ParagraphAlignment.Center, false);
}
// Convert to grayscale
bmp.ApplyEffect(GrayscaleEffect.Get(GrayscaleStandard.BT601));
// Resize to thumbnail
var resizedImage = bmp.Resize(100, 100, InterpolationMode.NearestNeighbor);
return GetBase64(resizedImage);
}
}
#region helper
private static string GetBase64(GcBitmap bmp)
{
using (MemoryStream m = new MemoryStream())
{
bmp.SaveAsPng(m);
return Convert.ToBase64String(m.ToArray());
}
}
#endregion
}
}
Open Function class. Add the following code in its 'FunctionHandler' method:
public async Task<string> FunctionHandler(S3Event evnt, ILambdaContext context)
{
var s3Event = evnt.Records?[0].S3;
if(s3Event == null)
{
return null;
}
try
{
var rs = await this.S3Client.GetObjectMetadataAsync(
s3Event.Bucket.Name,
s3Event.Object.Key);
if (rs.Headers.ContentType.StartsWith("image/"))
{
using (GetObjectResponse response = await S3Client.GetObjectAsync(
s3Event.Bucket.Name,
s3Event.Object.Key))
{
using (Stream responseStream = response.ResponseStream)
{
using (StreamReader reader = new StreamReader(responseStream))
{
using (var memstream = new MemoryStream())
{
var buffer = new byte[512];
var bytesRead = default(int);
while ((bytesRead = reader.BaseStream.Read(buffer, 0, buffer.Length)) > 0)
memstream.Write(buffer, 0, bytesRead);
// Perform image manipulation
var transformedImage = GcImagingOperations.GetConvertedImage(memstream.ToArray());
PutObjectRequest putRequest = new PutObjectRequest()
{
BucketName = DestBucket,
Key = $"grayscale-{s3Event.Object.Key}",
ContentType = rs.Headers.ContentType,
ContentBody = transformedImage
};
await S3Client.PutObjectAsync(putRequest);
}
}
}
}
}
return rs.Headers.ContentType;
}
catch (Exception e)
{
throw;
}
}
The event your Lambda function receives is for a single object and it provides information, such as the bucket name and object key name. There are two types of permissions policies that you work with when you set up the end-to-end experience:
Permissions for your Lambda function Regardless of what invokes a Lambda function, AWS Lambda executes the function by assuming the IAM role (execution role) that you specify at the time you create the Lambda function. Using the permissions policy associated with this role, you grant your Lambda function the permissions that it needs. For example, if your Lambda function needs to read an object, you grant permissions for the relevant Amazon S3 actions in the permissions policy. For more information, see Manage Permissions: Using an IAM Role (Execution Role).
Permissions for Amazon S3 to invoke your Lambda function Amazon S3 cannot invoke your Lambda function without your permission. You grant this permission via the permissions policy associated with the Lambda function.
The remaining configuration needs to setup S3 to publish events to the function we have written. Follow these steps:
Now, when images are uploaded to the gc-imaging-source-bucket the grayscale version is automatically created and loaded into the gc-imaging-target-bucket. You can download the code in this project from this GitHub repo.
Happy coding! Be sure to leave any comments or questions below.