写点什么

Serverless 实战:如何快速实现图片压缩与水印添加?

  • 2020-05-27
  • 本文字数:4200 字

    阅读完需:约 14 分钟

Serverless实战:如何快速实现图片压缩与水印添加?

实际生活中,我们常常会有上传图片的需求,例如在相册系统中添加相片、发布文章时添加配图等等。图片与 Web 服务是紧密关联在一起的,但每张图片的大小、占用的空间等都是参差不齐的,而且有些图片上传到网站之后,容易被其它平台或开发者采集盗用,所以很多人都习惯于在图片上传之后进行图片压缩、标准化以及添加水印。


当图片数量很多、尺寸很大的时候,压缩、标准化和水印添加就会占用很多的资源。那么,我们是否能够利用 Serverless 架构实现图片压缩与水印的一条龙服务,同时用户量的激增也不会影响整体体验?

Serverless 与图像处理

一般来说,传统的图像处理方法会比较占用资源,导致服务器压力较大,甚至影响用户体验:



那么我们是否可以通过 Serverless 架构实现一个异步处理流程?



什么是异步处理流程?简单来说,就是用户直接上传图片到对象存储,将图片等资源进行持久化,然后通过对象存储相关的触发器,触发指定函数,函数进行图像压缩以及图像水印等相关操作,再次进行持久化。


以相册系统为例,用户上传图片之后,系统进行压缩以及水印并生成缩略图,存储到对象存储中。当用户浏览图片列表时,就展示带有水印的缩略图,这样可以大大提升加载速度,而水印可以当作图像的一种版权保护,当用户点击图片查看原图时,可以为用户展示原始图片。

图像压缩

图像压缩,在这里我们只把图像大小作为压缩依据,除此之外,还可以对图像的质量进行处理。


如果单以尺寸进行压缩处理,可以看作是将一个image对象以宽度传入,通过resize方法进行大小的调整,实现压缩功能。


def compressImage(image, width):    height = image.size[1] / (image.size[0] / width)    return image.resize((int(width), int(height)))
复制代码

图像水印

图像水印大多采用的是文字水印,当然我们还可以使用图片水印等。


此处为了将水印放在图像的右下角,并且恰好不超出图像范围,我们对每个字符大小进行了获取:


height = []width = []for eveStr in watermarkStr:    thisWidth, thisHeight = drawImage.textsize(eveStr, font)    height.append(thisHeight)    width.append(thisWidth)
复制代码


这样处理之后,我们得到的height列表就是所有即将水印文字的高度,width列表是所有即将水印文字的宽度。如果要将水印放在右下角,我们只需要在图片整体高度上减去height列表最大值,在图片整体宽度基础上减去width列表的总和即可:


def watermarImage(image, watermarkStr):    txtImage = Image.new('RGBA', image.size, (0, 0, 0, 0))    font = ImageFont.truetype("Brimborion.TTF", 40)    drawImage = ImageDraw.Draw(txtImage)    height = []    width = []    for eveStr in watermarkStr:        thisWidth, thisHeight = drawImage.textsize(eveStr, font)        height.append(thisHeight)        width.append(thisWidth)    drawImage.text((txtImage.size[0] - sum(width) - 10, txtImage.size[1] - max(height) - 10),                   watermarkStr, font=font,                   fill=(255, 255, 255, 255))    return Image.alpha_composite(image, txtImage)
复制代码

部署到云函数

通过函数的事件描述,可以确定腾讯云函数的对象存储触发器事件结果为:


{  "Records": [  {      "cos": {          "cosSchemaVersion": "1.0",          "cosObject": {              "url": "http://testpic-1253970026.cos.ap-chengdu.myqcloud.com/testfile",              "meta": {                  "x-cos-request-id": "NWMxOWY4MGFfMjViMjU4NjRfMTUyMV8yNzhhZjM=",                  "Content-Type": ""              },              "vid": "",              "key": "/1253970026/testpic/testfile",              "size": 1029          },          "cosBucket": {              "region": "cd",              "name": "testpic",              "appid": "1253970026"          },          "cosNotificationId": "unkown"      },      "event": {          "eventName": "cos: ObjectCreated:Post",          "eventVersion": "1.0",          "eventTime": 1545205770,          "eventSource": "qcs::cos",          "requestParameters": {              "requestSourceIP": "192.168.15.101",              "requestHeaders": {                  "Authorization": "q-sign-algorithm=sha1&q-ak=AKIDQm6iUh2NJ6jL41tVUis9KpY5Rgv49zyC&q-sign-time=1545205709;1545215769&q-key-time=1545205709;1545215769&q-header-list=host;x-cos-storage-class&q-url-param-list=&q-signature=098ac7dfe9cf21116f946c4b4c29001c2b449b14"              }          },          "eventQueue": "qcs:0:lambda:cd:appid/1253970026:default.printevent.$LATEST",          "reservedInfo": "",          "reqid": 179398952      }  }]}
复制代码


根据这个结构,我们可以确定相关详细信息,例如存储桶 /APPID 以及图片的 Key 等。然后我们将上面的代码按照函数计算的格式进行改写:


# -*- coding: utf8 -*-import osfrom PIL import Image, ImageFont, ImageDrawfrom qcloud_cos_v5 import CosConfigfrom qcloud_cos_v5 import CosS3Client
secret_id = os.environ.get('secret_id')secret_key = os.environ.get('secret_key')region = os.environ.get('region')cosClient = CosS3Client(CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key))

def compressImage(image, width): height = image.size[1] / (image.size[0] / width) return image.resize((int(width), int(height)))

def watermarImage(image, watermarkStr): txtImage = Image.new('RGBA', image.size, (0, 0, 0, 0)) font = ImageFont.truetype("Brimborion.TTF", 40) drawImage = ImageDraw.Draw(txtImage) height = [] width = [] for eveStr in watermarkStr: thisWidth, thisHeight = drawImage.textsize(eveStr, font) height.append(thisHeight) width.append(thisWidth) drawImage.text((txtImage.size[0] - sum(width) - 10, txtImage.size[1] - max(height) - 10), watermarkStr, font=font, fill=(255, 255, 255, 255)) return Image.alpha_composite(image, txtImage)

def main_handler(event, context): for record in event['Records']: bucket = record['cos']['cosBucket']['name'] + '-' + record['cos']['cosBucket']['appid'] key = record['cos']['cosObject']['key'] download_path = '/tmp/{}'.format(key) upload_path = '/tmp/new_pic-{}'.format(key)
# 下载图片 response = cosClient.get_object(Bucket=bucket, Key=key) response['Body'].get_stream_to_file(download_path)
# 图片处理 image = Image.open(download_path) image = compressImage(image, width=500) image = watermarImage(image, "Hello Serverless") image.save(upload_path)
# 上传图片 cosClient.put_object_from_local_file( Bucket=bucket, LocalFilePath=upload_path, Key="/compress-watermark/" + key )
复制代码


此时,新建serverless.yaml文件:


MyPicture:  component: "@serverless/tencent-scf"  inputs:    name: MyPicture    codeUri: ./    handler: index.main_handler    runtime: Python3.6    region: ap-guangzhou    description: My Picture Compress And Watermark    memorySize: 128    timeout: 20    environment:      variables:        secret_id: 用户密钥 id        secret_key: 用户密钥 key        region: ap-guangzhou    events:      - cos:          name: picture-1256773370.cos.ap-guangzhou.myqcloud.com          parameters:            bucket: picture-1256773370.cos.ap-guangzhou.myqcloud.com            filter:              prefix: source/            events: cos:ObjectCreated:*            enable: true
复制代码


从中我们可以看到,函数有一个cos触发器,这个触发器是针对存储桶picture-1256773370下面source/目录下的资源创建进行触发。

简单测试

现在,我们通过serverless部署项目:



部署完成之后,我们在存储桶picture-1256773370中,新建source/目录与compress-watermark/目录。


其中,前者用来上传文件,后者用来生成新的文件。随机搜索一张图片:



可以看到这张图片 4.5M,还是蛮大的,将这个图片上传到source/目录下:



稍等片刻,我们就可以在compress-watermark/目录下发现一个新的文件生成:



将文件下载下来,查看详情:



可以看到,图片尺寸明显变小,从 4.5M 压缩到了 340K。与此同时,图像右下角出现了预设的水印标志。


至此,我们完成了通过 COS 触发器实现图片压缩与水印的功能。

总结

通过本文展示的操作,我们成功实现了用户上传图像,并通过 Serverless 架构对其进行压缩与增加水印的功能。事实上, Serverless 架构可以解决很多传统生产中遇到的问题,并且可以更节约资源和成本,以本文为例,当我们的服务面临高并发的时候,传统情况下,很可能会由于图像压缩,水印的操作导致服务挂掉,但是通过这样一个策略,就算是出现了高并发,也仅仅是将图片传入对象存储,至于转换的逻辑、压缩的逻辑以及水印的逻辑等都由 Serverless 架构帮我们实现,既安全稳定,又节约成本和资源。


当然,除了压缩和水印,我们还可以利用 Serverless 架构来实现图像标准化、不同尺寸图像制作、视频压缩、不同分辨率的视频制作,甚至可以通过深度学习对图像进行打标签等。


作者介绍:


刘宇,腾讯 Serverless 团队后台研发工程师。毕业于浙江大学,硕士研究生学历,曾在滴滴出行、腾讯科技做产品经理,本科开始有自主创业经历,是 Anycodes 在线编程的负责人(该软件累计下载量超 100 万次)。目前投身于 Serverless 架构研发,著书《Serverless 架构:从原理、设计到项目实战》,参与开发和维护多个 Serverless 组件,是活跃的 Serverless Framework 的贡献者,也曾多次公开演讲和分享 Serverless 相关技术与经验,致力于 Serverless 的落地与项目上云。


2020-05-27 15:586203

评论 4 条评论

发布
用户头像
serverlesss是否可以支持并发处理?高并发的情况下,如果要达到近实时处理,对硬件配置和分布式的支持?
2020-06-08 16:05
回复
用户头像
想要上传成功后就能在前台展示压缩过的图怎么办?等异步压缩完要很久
2020-05-30 20:42
回复
异步压缩确实可能会有一定的延时,但是这个延时理论不会太大,虽然他是一个队列处理,但是实际上确是来了一个请求,就会启动一个实例,也就是说你可以认为“异步压缩过程”可以同时处理几百张,几千张图片,所以这个速度理论不会慢太多的。如果单纯的就是想要压缩,可以直接前台压缩。
2020-06-01 14:39
回复
用户头像
Serverless,未来可期!
2020-05-29 18:39
回复
没有更多了
发现更多内容

华为云数据灾备,如何让企业数据无忧

路过的憨憨

华为

华为云桌面,安全可靠的云上办公首选

路过的憨憨

华为

重磅丨九科被评为“2022年中国流程挖掘行业典型实践厂商” 实力再获“RPA中国”认可

九科Ninetech

RPA 流程挖掘 数智化转型

低代码无代码区别在哪?通过这5点来区别准没错

优秀

低代码 无代码

分布式事务-事务补偿(TCC)

zarmnosaj

10月月更

华为云数据灾备方案,撑起一把企业的保护伞

路过的憨憨

华为

4000字深度总结!Pipeline五大性能实践,招招制敌

极狐GitLab

DevOps CI/CD 持续交付 pipeline 极狐GitLab

开发者原来都是健身猛男?

InfoQ写作社区官方

热门活动

HTTP 常用的状态码及使用场景

孙铭

HTTP 10月月更 200

云端软件运行,小程序安全沙箱技术为端侧安全保驾护航

Geek_99967b

小程序

华为云CDN联手OBS桶,帮助企业更好降本增效!

路过的憨憨

华为

华为云数据灾备方案助力企业安全,守住企业底线

路过的憨憨

华为

View层、Controller层、Service层、Dao层的区别以及对应的功能

孙铭

service DAO 10月月更

Spring Boot 应用使用 application.yml 和 application.properties 的区别

汪子熙

Java mvc spring springboot 10月月更

Vuex在uniapp中的使用

孙铭

Vue vuex 10月月更

鸿蒙开发实例|分布式文件服务

TiAmo

华为 鸿蒙 10月月更

HTML基本知识学习笔记

虾仁疙瘩汤

html 前端 10月月更

华为云大数据BI解决方案,如何帮助企业精准营销

路过的憨憨

华为

华为云对象存储服务OBS教你一招轻松解决存储难题

路过的憨憨

华为

华为云灾备,保护企业信息数据势在必行!

路过的憨憨

华为

华为云对象存储OBS,助力企业高效解决存储问题

路过的憨憨

华为

Vue3, setup语法糖、Composition API全方位解读

yyds2026

Vue

Vue.nextTick核心原理

yyds2026

Vue

HTML学习笔记

虾仁疙瘩汤

html 前端 10月月更

英特尔“四维发力”系统级代工:晶圆制造、封装、芯粒、软件

科技之家

CSS学习笔记1

虾仁疙瘩汤

CSS html 10月月更

华为云数据灾备方案如何成为企业的坚实后盾

路过的憨憨

华为

rdd pair reduce

小东

助力企业资源的合理利用,华为云数据库RDS for MySQL使用经济更省心!

路过的憨憨

“程”风破浪的开发者|我是如何快速学 Go 的?GoFrame 只用了 3 天时间就从小白变大佬?

王中阳Go

Go golang 学习方法 10月月更 “程”风破浪的开发者

Zookeeper的服务器的log4j升级为log4j2的升级方案(忽略配置化兼容问题)

码界西柚

zookeeper

Serverless实战:如何快速实现图片压缩与水印添加?_云原生_刘宇_InfoQ精选文章