Skip to content

Commit 19cbc3c

Browse files
committed
Updated to Version 4.2
1 parent a7ee268 commit 19cbc3c

File tree

14 files changed

+633
-149
lines changed

14 files changed

+633
-149
lines changed

CHANGELOG.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [4.2] - 2020-02-06
8+
### Added
9+
- Honor outputFormat Parameter from the pull request [#117](https://github.com/awslabs/serverless-image-handler/pull/117)
10+
- Support serving images under s3 subdirectories, Fix to make /fit-in/ work; Fix for VipsJpeg: Invalid SOS error plus several other critical fixes from the pull request [#130](https://github.com/awslabs/serverless-image-handler/pull/130)
11+
- Allow regex in SOURCE_BUCKETS for environment variable from the pull request [#138](https://github.com/awslabs/serverless-image-handler/pull/138)
12+
- Fix build script on other platforms from the pull request [#139](https://github.com/awslabs/serverless-image-handler/pull/139)
13+
- Add Cache-Control response header from the pull request [#151](https://github.com/awslabs/serverless-image-handler/pull/151)
14+
- Add AUTO_WEBP option to automatically serve WebP if the client supports it from the pull request [#152](https://github.com/awslabs/serverless-image-handler/pull/152)
15+
- Use HTTP 404 & forward Cache-Control, Content-Type, Expires, and Last-Modified headers from S3 from the pull request [#158](https://github.com/awslabs/serverless-image-handler/pull/158)
16+
- fix: DeprecationWarning: Buffer() is deprecated from the pull request [#174](https://github.com/awslabs/serverless-image-handler/pull/174)
17+
- Add hex color support for Thumbor ```filters:background_color``` and ```filters:fill``` [#154](https://github.com/awslabs/serverless-image-handler/issues/154)
18+
- Add format and watermark support for Thumbor [#109](https://github.com/awslabs/serverless-image-handler/issues/109), [#131](https://github.com/awslabs/serverless-image-handler/issues/131), [#109](https://github.com/awslabs/serverless-image-handler/issues/142)
19+
* __Note that__ duplicated features has been merged gracefully.
20+
21+
### Changed
22+
- sharp base version (from 0.23.3 to 0.23.4)
23+
- Image handler Amazon CloudFront distribution ```DefaultCacheBehavior.ForwaredValues.Header``` to ```["Origin", "Accept"]``` for webp
24+
- Image resize process change for ```filters:no_upscale()``` handling by ```withoutEnlargement``` edit key [#144](https://github.com/awslabs/serverless-image-handler/issues/144)
25+
26+
### Fixed
27+
- Add and fix Cache-control, Content-Type, Expires, and Last-Modified headers to response: [#103](https://github.com/awslabs/serverless-image-handler/issues/103), [#107](https://github.com/awslabs/serverless-image-handler/issues/107), [#120](https://github.com/awslabs/serverless-image-handler/issues/120)
28+
- Fix Amazon S3 bucket subfolder issue: [#106](https://github.com/awslabs/serverless-image-handler/issues/106), [#112](https://github.com/awslabs/serverless-image-handler/issues/112), [#119](https://github.com/awslabs/serverless-image-handler/issues/119), [#123](https://github.com/awslabs/serverless-image-handler/issues/123), [#167](https://github.com/awslabs/serverless-image-handler/issues/167), [#175](https://github.com/awslabs/serverless-image-handler/issues/175)
29+
- Fix HTTP status code for missing images from 500 to 404: [#159](https://github.com/awslabs/serverless-image-handler/issues/159)
30+
- Fix European character in filename issue: [#149](https://github.com/awslabs/serverless-image-handler/issues/149)
31+
- Fix image scaling issue for filename containing 'x' character: [#163](https://github.com/awslabs/serverless-image-handler/issues/163), [#176](https://github.com/awslabs/serverless-image-handler/issues/176)
32+
- Fix regular expression issue: [#114](https://github.com/awslabs/serverless-image-handler/issues/114), [#121](https://github.com/awslabs/serverless-image-handler/issues/121), [#125](https://github.com/awslabs/serverless-image-handler/issues/125)
33+
- Fix not working quality parameter: [#129](https://github.com/awslabs/serverless-image-handler/issues/129)
34+
735
## [4.1] - 2019-12-31
836
### Added
937
- CHANGELOG file
@@ -15,5 +43,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1543
- Image handler function to use Composite API (https://sharp.pixelplumbing.com/en/stable/api-composite/)
1644
- License to Apache-2.0
1745

18-
# Removed
46+
### Removed
1947
- Reference to deprecated sharp function (overlayWith)
48+
- Capability to resize images proportionally if width or height is set to 0 (sharp v0.23.1 and later check that the width and height - if present - are positive integers)

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
same "printed page" as the copyright notice for easier
188188
identification within third-party archives.
189189

190-
Copyright 2019 - 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
190+
Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
191191

192192
Licensed under the Apache License, Version 2.0 (the "License");
193193
you may not use this file except in compliance with the License.

NOTICE.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ sharp under the Apache License Version 2.0
2020
sinon under the BSD-3-Clause license
2121
sinon-chai under the BSD-2-Clause license
2222
uuid under the Massachusetts Institute of Technology (MIT) license
23+
color under the Massachusetts Institute of Technology (MIT) license
24+
color-name under the Massachusetts Institute of Technology (MIT) license

deployment/serverless-image-handler.template

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
"Default" : 1,
3131
"Type" : "Number",
3232
"AllowedValues" : [ 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653 ]
33+
},
34+
"AutoWebP" : {
35+
"Description" : "Would you like to enable automatic WebP based on accept headers? Select 'Yes' if so.",
36+
"Default" : "No",
37+
"Type" : "String",
38+
"AllowedValues" : [ "Yes", "No" ]
3339
}
3440
},
3541
"Metadata": {
@@ -74,6 +80,7 @@
7480
"Resources": {
7581
"Logs": {
7682
"DeletionPolicy": "Retain",
83+
"UpdateReplacePolicy": "Retain",
7784
"Type": "AWS::S3::Bucket",
7885
"Properties": {
7986
"AccessControl": "LogDeliveryWrite",
@@ -130,7 +137,7 @@
130137
"TargetOriginId": { "Fn::Sub": "${ImageHandlerApi}" },
131138
"ForwardedValues": {
132139
"QueryString": false,
133-
"Headers": [ "Origin" ],
140+
"Headers": [ "Origin", "Accept" ],
134141
"Cookies": { "Forward": "none" }
135142
},
136143
"ViewerProtocolPolicy": "https-only"
@@ -310,6 +317,7 @@
310317
},
311318
"ImageHandlerApiDeployment": {
312319
"Type": "AWS::ApiGateway::Deployment",
320+
"DependsOn": "ApiAccountConfig",
313321
"Properties": {
314322
"RestApiId": { "Ref": "ImageHandlerApi" },
315323
"StageName": "image",
@@ -371,6 +379,9 @@
371379
"Timeout": 30,
372380
"Environment" : {
373381
"Variables" : {
382+
"AUTO_WEBP" : {
383+
"Ref" : "AutoWebP"
384+
},
374385
"CORS_ENABLED" : {
375386
"Ref" : "CorsEnabled"
376387
},
@@ -514,6 +525,7 @@
514525
"Type": "AWS::S3::Bucket",
515526
"Condition": "DeployDemoUICondition",
516527
"DeletionPolicy": "Retain",
528+
"UpdateReplacePolicy": "Retain",
517529
"Properties": {
518530
"BucketEncryption": {
519531
"ServerSideEncryptionConfiguration": [

source/custom-resource/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ let sendResponse = function(event, callback, logStreamName, responseStatus, resp
218218
const responseBody = JSON.stringify({
219219
Status: responseStatus,
220220
Reason: reason,
221-
PhysicalResourceId: logStreamName,
221+
PhysicalResourceId: event.LogicalResourceId,
222222
StackId: event.StackId,
223223
RequestId: event.RequestId,
224224
LogicalResourceId: event.LogicalResourceId,

source/custom-resource/lib/s3-helper.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
'use strict';
1919

20-
let moment = require('moment');
2120
let AWS = require('aws-sdk');
2221
const fs = require('fs');
2322

@@ -75,7 +74,6 @@ class s3Helper {
7574
console.log(`Attempting to save content blob destination location: ${destS3Bucket}/${destS3key}`);
7675
console.log(JSON.stringify(content));
7776

78-
let _self = this;
7977
return new Promise((resolve, reject) => {
8078
let _content = `'use strict';\n\nconst appVariables = {\n`;
8179

source/image-handler/image-handler.js

Lines changed: 101 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ImageHandler {
2626
if (edits !== undefined) {
2727
const modifiedImage = await this.applyEdits(originalImage, edits);
2828
if (request.outputFormat !== undefined) {
29-
await modifiedImage.toFormat(request.outputFormat);
29+
modifiedImage.toFormat(request.outputFormat);
3030
}
3131
const bufferImage = await modifiedImage.toBuffer();
3232
return bufferImage.toString('base64');
@@ -42,26 +42,79 @@ class ImageHandler {
4242
* @param {Object} edits - The edits to be made to the original image.
4343
*/
4444
async applyEdits(originalImage, edits) {
45-
const image = sharp(originalImage);
45+
if (edits.resize === undefined) {
46+
edits.resize = {};
47+
edits.resize.fit = 'inside';
48+
}
49+
50+
const image = sharp(originalImage, { failOnError: false });
51+
const metadata = await image.metadata();
4652
const keys = Object.keys(edits);
4753
const values = Object.values(edits);
54+
4855
// Apply the image edits
4956
for (let i = 0; i < keys.length; i++) {
5057
const key = keys[i];
5158
const value = values[i];
5259
if (key === 'overlayWith') {
53-
const overlay = await this.getOverlayImage(value.bucket, value.key);
54-
const params = [{ ...value.options, input: overlay }];
60+
let imageMetadata = metadata;
61+
if (edits.resize) {
62+
let imageBuffer = await image.toBuffer();
63+
imageMetadata = await sharp(imageBuffer).resize({ edits: { resize: edits.resize }}).metadata();
64+
}
65+
66+
const { bucket, key, wRatio, hRatio, alpha } = value;
67+
const overlay = await this.getOverlayImage(bucket, key, wRatio, hRatio, alpha, imageMetadata);
68+
const overlayMetadata = await sharp(overlay).metadata();
69+
70+
let { options } = value;
71+
if (options) {
72+
if (options.left) {
73+
let left = options.left;
74+
if (left.endsWith('p')) {
75+
left = parseInt(left.replace('p', ''));
76+
if (left < 0) {
77+
left = imageMetadata.width + (imageMetadata.width * left / 100) - overlayMetadata.width;
78+
} else {
79+
left = imageMetadata.width * left / 100;
80+
}
81+
} else {
82+
left = parseInt(left);
83+
if (left < 0) {
84+
left = imageMetadata.width + left - overlayMetadata.width;
85+
}
86+
}
87+
options.left = parseInt(left);
88+
}
89+
if (options.top) {
90+
let top = options.top;
91+
if (top.endsWith('p')) {
92+
top = parseInt(top.replace('p', ''));
93+
if (top < 0) {
94+
top = imageMetadata.height + (imageMetadata.height * top / 100) - overlayMetadata.height;
95+
} else {
96+
top = imageMetadata.height * top / 100;
97+
}
98+
} else {
99+
top = parseInt(top);
100+
if (top < 0) {
101+
top = imageMetadata.height + top - overlayMetadata.height;
102+
}
103+
}
104+
options.top = parseInt(top);
105+
}
106+
}
107+
108+
const params = [{ ...options, input: overlay }];
55109
image.composite(params);
56110
} else if (key === 'smartCrop') {
57111
const options = value;
58112
const imageBuffer = await image.toBuffer();
59-
const metadata = await image.metadata();
60-
// ----
61113
const boundingBox = await this.getBoundingBox(imageBuffer, options.faceIndex);
62-
const cropArea = await this.getCropArea(boundingBox, options, metadata);
63-
try { image.extract(cropArea) }
64-
catch (err) {
114+
const cropArea = this.getCropArea(boundingBox, options, metadata);
115+
try {
116+
image.extract(cropArea)
117+
} catch (err) {
65118
throw ({
66119
status: 400,
67120
code: 'SmartCrop::PaddingOutOfBounds',
@@ -82,18 +135,48 @@ class ImageHandler {
82135
* @param {string} bucket - The name of the bucket containing the overlay.
83136
* @param {string} key - The keyname corresponding to the overlay.
84137
*/
85-
async getOverlayImage(bucket, key) {
138+
async getOverlayImage(bucket, key, wRatio, hRatio, alpha, sourceImageMetadata) {
86139
const s3 = new AWS.S3();
87140
const params = { Bucket: bucket, Key: key };
88-
// Request
89-
const request = s3.getObject(params).promise();
90-
// Response handling
91141
try {
92-
const overlayImage = await request;
93-
return Promise.resolve(overlayImage.Body);
142+
const { width, height } = sourceImageMetadata;
143+
const overlayImage = await s3.getObject(params).promise();
144+
let resize = {
145+
fit: 'inside'
146+
}
147+
148+
// Set width and height of the watermark image based on the ratio
149+
const zeroToHundred = /^(100|[1-9]?[0-9])$/;
150+
if (zeroToHundred.test(wRatio)) {
151+
resize['width'] = parseInt(width * wRatio / 100);
152+
}
153+
if (zeroToHundred.test(hRatio)) {
154+
resize['height'] = parseInt(height * hRatio / 100);
155+
}
156+
157+
// If alpha is not within 0-100, the default alpha is 0 (fully opaque).
158+
if (zeroToHundred.test(alpha)) {
159+
alpha = parseInt(alpha);
160+
} else {
161+
alpha = 0;
162+
}
163+
164+
const convertedImage = await sharp(overlayImage.Body)
165+
.resize(resize)
166+
.composite([{
167+
input: Buffer.from([255, 255, 255, 255 * (1 - alpha / 100)]),
168+
raw: {
169+
width: 1,
170+
height: 1,
171+
channels: 4
172+
},
173+
tile: true,
174+
blend: 'dest-in'
175+
}]).toBuffer();
176+
return Promise.resolve(convertedImage);
94177
} catch (err) {
95178
return Promise.reject({
96-
status: 500,
179+
status: err.statusCode ? err.statusCode : 500,
97180
code: err.code,
98181
message: err.message
99182
})
@@ -131,12 +214,9 @@ class ImageHandler {
131214
const rekognition = new AWS.Rekognition();
132215
const params = { Image: { Bytes: imageBuffer }};
133216
const faceIdx = (faceIndex !== undefined) ? faceIndex : 0;
134-
// Request
135-
const request = rekognition.detectFaces(params).promise();
136-
// Response handling
137217
try {
138-
const response = (await request).FaceDetails[faceIdx].BoundingBox;
139-
return Promise.resolve(await response);
218+
const response = await rekognition.detectFaces(params).promise();
219+
return Promise.resolve(response.FaceDetails[faceIdx].BoundingBox);
140220
} catch (err) {
141221
console.log(err);
142222
if (err.message === "Cannot read property 'BoundingBox' of undefined") {
@@ -158,4 +238,3 @@ class ImageHandler {
158238

159239
// Exports
160240
module.exports = ImageHandler;
161-

0 commit comments

Comments
 (0)