Skip to main content Skip to footer

How to Create a Thumbnail Image on AWS S3 Using an Imaging API

Document Solutions for Imaging (DsImaging), previously GrapeCity Document for Imaging (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 Document Solutions 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 Document Solutions with Lambda functions and extend this to DsImaging for creating an imaging service over AWS S3 events.

Here are the steps for creating an imaging service over AWS S3 events with Document Solutions:

  1. Design the service with DsImaging and Image operations
  2. Setting up AWS services
  3. Create a LAMBDA function with Visual Studio
  4. Configure S3 to publish events

Designing the service

DsImaging and Image operations

DsImaging 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:

  1. Add Watermark on the image
  2. Convert Image to Grayscale
  3. Resize the image to a thumbnail

Original Image v Final Image

There are plenty more operations that you can perform with DsImaging.

The Workflow

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:

Workflow

The flow of this diagram can be described as:

  1. User uploads an image into a specific S3 bucket (say, Source bucket)
  2. An event (object created) is raised based on the uploaded image.
  3. This event is sent to an AWS Lambda function
    • This invoked function takes an S3 event and its object's key (image unique ID).
    • DsImaging library is used to manipulate this image
  4. S3 service then uploads the image to another bucket (say, Target bucket)

Pre-requisites:

  1. Visual Studio
  2. Download and Install AWS Toolkit for Visual Studio.

Setup AWS Services:

  1. Two S3 bucket ("Source Bucket" and "Target Bucket")
    • Open AWS Management Console
    • Select All Services > Storage > S3
    • Click on Create bucket
      • Name: gc-imaging-source-bucket
    • Click on Create bucket
      • Name: gc-imaging-target-bucket
  2. Add an Execution role which gives permission to access AWS resources
    • Open AWS Management Console
    • Open IAM Console
    • Click on 'Create Role' button
    • Select 'AWS Lambda' and press next to select permission
    • Select arn:aws:iam::aws:policy/AWSLambdaExecute policy and press 'Create Role'

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.

Creating a Lambda Function with Visual Studio

  1. Open Visual Studio and create new project 'GCImagingAWSLambdaS3' by selecting C# > AWS Lambda > AWS Lambda Project (.NET Core)
  2. Select 'Simple S3 Function' from 'Select Blueprint' dialog.

AWS Sample

  1. Open NuGet Package Manager, search GrapeCity.Documents.Imaging, and install the package.
  2. Create a new class GcImagingOperations.cs. GcImagingOperations class contains static functions to manipulate your images.
    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 
     } 
    }
    
  3. 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; 
     } 
    } 
    
  4. Now publish your function to AWS directly by right-clicking on your project and then selecting 'Publish to AWS Lambda.'

AWS Sample 2

Configure S3 to Publish Events

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:

  1. "Open Amazon S3 Console"
  2. Select your bucket gc-imaging-source-bucket.
  3. Select Properties > Advanced settings > Events

AWS Sample 3

  1. Add a notification with following settings
    • Name: GrayScaleImage
    • Events: All object create events
    • Send To: Lambda Function
    • Lambda: GCImagingAWSLambdaS3::GCImagingAWSLambdaS3.Function::FunctionHandler (Lambda Function ARN, see your project's configuration file)
  2. Publish the settings.

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

Happy coding! Be sure to leave any comments or questions below.

comments powered by Disqus