How to use Serverless to add watermark to website pictures gracefully

Many forums and blogs will add watermarks to their images after uploading images, which can prove that this image "belongs to me" or "comes from my blog / website". So what's better to use Serverless technology to add watermark than traditional methods? This paper will share this simple.

The traditional method of adding watermark is usually in the process. Namely:

Although this approach is feasible, it will undoubtedly increase the pressure of single request and server. If it is highly concurrent, or when multiple people upload multiple large images, it may cause the server resource effect to be too large.

If it fails in the process of adding watermark, it may lead to image storage failure and data loss, which is not rational. So later, some people made the following improvements:

The advantage of doing so is—— We can quickly store the pictures. After storage, we can process the task list through a separate processing thread. On the one hand, we can ensure that the pictures uploaded by users can be stored immediately, displayed, and watermarked in the background. After processing, we can cover or store the pictures separately. If users need to read the pictures, It can automatically change to a watermarked image.

This method may be a little more complicated than the former, but it is actually an operation that is more stable in data, less pressure on the server and more controllable. However, the whole process still needs to be completed on their own servers. Now, many people have stored pictures and other resources in Tencent cloud Object store (COS) Can we combine some COS triggers with cloud function SCF to realize a process without watermark on our own server?

This article will take the function template of Tencent cloud function SCF (Python language) as an example to share.

Experiment

New function

In the serverless cloud function, select the template function:

By searching for the "image" keyword, select image compression and confirm to create it. After saving, click function code to write the code.

COS trigger

Some people may not know COS trigger very well. At this time, click Configure to get familiar with COS trigger style:

You can see the following:

{
   "Records":[
      {
        "event": {
          "eventVersion":"1.0",
          "eventSource":"qcs::cos",
          "eventName":"cos: ObjectCreated: *",
          "eventTime":1501054710,
          "eventQueue":"qcs:0:cos:gz:1251111111:cos",
          "requestParameters":{
            "requestSourceIP": "111.111.111.111",
            "requestHeaders":{
              "Authorization": "Authentication information uploaded"
            }
          }
         },
         "cos":{
            "cosSchemaVersion":"1.0",
            "cosNotificationId":"Set or returned ID",
            "cosBucket":{
               "name":"bucketname",
               "appid":"appId",
               "region":"gz"
            },
            "cosObject":{
               "key":"/appid/bucketname/DSC_0002.JPG",
               "size":2598526,
               "meta":{
                 "Content-Type": "text/plain",
                 "x-cos-meta-test": "Self defined meta",
                 "x-image-test": "Self defined meta"
               },
               "url": "Source site to access files url"
            }
         }
      }
   ]
}

You can see the whole data structure here. Note that Records is an array format, followed by:

"cosBucket":{"name":"bucketname","appid":"appId","region":"gz"}

This is triggered by the bucket

"Cosobject": {"key": "/ appid / bucketname / dsc_. JPG", "size": 2598526, "meta": {"content type": "text / plain", "x-cos-meta-test": "custom meta","x-image-test": "custom meta"},"url": "source url to access files"}

The key here is the name of the new file created in the above bucket.

Therefore, we can simply modify the above content to our format according to our actual situation, so as to test (in production, this is an automatically generated trigger format, which does not need to be modified by us, but only for testing)

{
   "Records":[
      {
        "event": {
          "eventVersion":"1.0",
          "eventSource":"qcs::cos",
          "eventName":"cos: ObjectCreated: *",
          "eventTime":1501054710,
          "eventQueue":"qcs:0:cos:gz:1251111111:cos",
          "requestParameters":{
            "requestSourceIP": "111.111.111.111",
            "requestHeaders":{
              "Authorization": "Authentication information uploaded"
            }
          }
         },
         "cos":{
            "cosSchemaVersion":"1.0",
            "cosNotificationId":"Set or returned ID",
            "cosBucket":{
               "name":"mytestcos",
               "appid":"appId",
               "region":"gz"
            },
            "cosObject":{
               "key":"test.png",
               "size":2598526,
               "meta":{
                 "Content-Type": "text/plain",
                 "x-cos-meta-test": "Self defined meta",
                 "x-image-test": "Self defined meta"
               },
               "url": "Source site to access files url"
            }
         }
      }
   ]
}

My cosbucket Name: mytestcos and key: test.png are modified here

Code modification

The reason why the existing template is used is that the template has relevant package s such as PIL and qcloud cos V5, which are exactly what we are going to need. In this way, we can save the process of our packing function and simply modify it.

Add watermark:

def add_word(pic_path, save_path):
    # Open the picture.
    im = Image.open(pic_path).convert('RGBA')
    # Create a new blank picture of the same size as the open picture
    txt = Image.new('RGBA', im.size, (0, 0, 0, 0))
    # Set font
    fnt = ImageFont.truetype("/tmp/font.ttf", 40)
    # Operate the new blank picture > > add the new picture to the palette
    d = ImageDraw.Draw(txt)
    # Add fonts to new pictures
    d.text((txt.size[0] - 220, txt.size[1] - 80), "By Dfounder", font=fnt,  fill=(255, 255, 255, 255))
    # Merge two pictures
    out = Image.alpha_composite(im, txt)
    # Save image
    out.save(save_path)

When adding watermark, we set text watermark, so we need to set font and font size:

fnt = ImageFont.truetype("/tmp/font.ttf",40)

At this point, we need to pass the font file into the /tmp/ folder before execution:

response = client.get_object(Bucket="mytestcos-12567****", Key="font.ttf", ) response['Body'].get_stream_to_file('/tmp/font.ttf')

Take my cos for example:

Then, the next step is to analyze the triggered event, including obtaining the new image name, pulling it from COS, putting it locally, then carrying out watermark, etc., and uploading it back to the new COS:

for record in event['Records']:
        try:
            bucket = record['cos']['cosBucket']['name'] + '-' + str(appid)
            key = record['cos']['cosObject']['key']
            key = key.replace('/' + str(appid) + '/' + record['cos']['cosBucket']['name'] + '/', '', 1)
            download_path = '/tmp/{}'.format(key)
            upload_path = '/tmp/new_pic-{}'.format(key)

            # Download pictures
            try:
                response = client.get_object(Bucket=bucket, Key=key, )
                response['Body'].get_stream_to_file(download_path)
            except CosServiceError as e:
                print(e.get_error_code())
                print(e.get_error_msg())
                print(e.get_resource_location())

            # Add watermark to image
            add_word(download_path, upload_path)


            # Image uploading
            response = client.put_object_from_local_file(
                Bucket=to_bucket,
                LocalFilePath=upload_path.decode('utf-8'),
                Key=("upload-" + key).decode('utf-8')
            )

        except Exception as e:
            print(e)

Why are there two buckets here?

Because we're going to use a bucket as a trigger for SCF If we write the watermark result back to this bucket again without additional judgment and processing, then the watermark image will be watermarked again and again, causing a bad cycle. Therefore, two buckets are created here, which can reduce the difficulty, protect the performance and reduce the birth of BUG.

The complete code is as follows:

# -*- coding: utf-8 -*-

from PIL import Image, ImageFont, ImageDraw
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client
from qcloud_cos_v5 import CosServiceError
from qcloud_cos_v5 import CosClientError

appid = **  # Please replace with your APPID
secret_id = ***'  # Please replace with your secret ID
secret_key = **'  # Please replace with your secret key
region = u'ap-chengdu'  # Please replace with the region where your bucket is located
token = ''
to_bucket = 'tobucket-12567***'  # Please replace with the bucket you used to store the compressed image

config = CosConfig(Secret_id=secret_id, Secret_key=secret_key, Region=region, Token=token)
client = CosS3Client(config)

response = client.get_object(Bucket="mytestcos-12567***", Key="font.ttf", )
response['Body'].get_stream_to_file('/tmp/font.ttf')

def add_word(pic_path, save_path):
    # Open the picture.
    im = Image.open(pic_path).convert('RGBA')
    # Create a new blank picture of the same size as the open picture
    txt = Image.new('RGBA', im.size, (0, 0, 0, 0))
    # Set font
    fnt = ImageFont.truetype("/tmp/font.ttf", 40)
    # Operate the new blank picture > > add the new picture to the palette
    d = ImageDraw.Draw(txt)
    # Add fonts to new pictures
    d.text((txt.size[0] - 220, txt.size[1] - 80), "By Dfounder", font=fnt,  fill=(255, 255, 255, 255))
    # Merge two pictures
    out = Image.alpha_composite(im, txt)
    # Save image
    out.save(save_path)

def main_handler(event, context):
    for record in event['Records']:
        try:
            bucket = record['cos']['cosBucket']['name'] + '-' + str(appid)
            key = record['cos']['cosObject']['key']
            key = key.replace('/' + str(appid) + '/' + record['cos']['cosBucket']['name'] + '/', '', 1)
            download_path = '/tmp/{}'.format(key)
            upload_path = '/tmp/new_pic-{}'.format(key)

            # Download pictures
            try:
                response = client.get_object(Bucket=bucket, Key=key, )
                response['Body'].get_stream_to_file(download_path)
            except CosServiceError as e:
                print(e.get_error_code())
                print(e.get_error_msg())
                print(e.get_resource_location())

            # Add watermark to image
            add_word(download_path, upload_path)


            # Image uploading
            response = client.put_object_from_local_file(
                Bucket=to_bucket,
                LocalFilePath=upload_path.decode('utf-8'),
                Key=("upload-" + key).decode('utf-8')
            )

        except Exception as e:
            print(e)

Note these parameters: appid, secret_id, secret_key, to_bucket

The sources of these parameters are as follows:

The secret ID and secret key need to be obtained here:

test

I have uploaded a test image in this bucket. The name is: test.png

The picture is like this:

Then we test:

As you can see, the test has been successful. Next, we can go to our target bucket to see:

You can see that a picture has been generated successfully:

You can see the watermark added in our code in the lower right corner of the picture:

At this point, we have completed the basic process of adding watermark for your website pictures through the cloud function SCF.

▎ extra words

In fact, this article is also an introduction. You can not only compress the image, add watermarks, but also perform other operations on the image through this process. For example, some deep processing, such as image stylization processing, will not occupy its own server resources, but through the cloud function SCF.

Even in the face of high concurrency and a large number of pictures are uploaded, all we have to do is pass the pictures into Tencent cloud object storage COS through SDK.

Portal:

Welcome to: Serverless Chinese network , you can Best practices Experience more about Serverless application development!

Recommended reading: Serverless architecture: from principle, design to project implementation

Tags: github less Python SDK

Posted on Sun, 08 Mar 2020 06:33:10 -0700 by trial_end_error