Skip to content
Merged
4 changes: 2 additions & 2 deletions functions/v2/imagemagick/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<img src="https://avatars2.githubusercontent.com/u/2810941?v=3&s=96" alt="Google Cloud Platform logo" title="Google Cloud Platform" align="right" height="96" width="96"/>

# Google Cloud Functions ImageMagick sample
# Google Cloud Functions imagemagick sample

This sample shows you how to blur an image using ImageMagick in a
This sample shows you how to blur an image using sharp in a
Storage-triggered Cloud Function.

View the [source code][code].
Expand Down
45 changes: 26 additions & 19 deletions functions/v2/imagemagick/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

// [START functions_imagemagick_setup]
const functions = require('@google-cloud/functions-framework');
const gm = require('gm').subClass({imageMagick: true});
const sharp = require('sharp');
const fs = require('fs').promises;
const path = require('path');
const vision = require('@google-cloud/vision');
Expand All @@ -34,6 +34,14 @@ functions.cloudEvent('blurOffensiveImages', async cloudEvent => {
// This event represents the triggering Cloud Storage object.
const bucket = cloudEvent.data.bucket;
const name = cloudEvent.data.name;

if (bucket === BLURRED_BUCKET_NAME) {
console.log(
'Event triggered by the blurred bucket; skip to avoid recursion'
);
return;
}

const file = storage.bucket(bucket).file(name);
const filePath = `gs://${bucket}/${name}`;

Expand Down Expand Up @@ -61,9 +69,10 @@ functions.cloudEvent('blurOffensiveImages', async cloudEvent => {
// [END functions_imagemagick_analyze]

// [START functions_imagemagick_blur]
// Blurs the given file using ImageMagick, and uploads it to another bucket.
// Blurs the given file using sharp, and uploads it to another bucket.
const blurImage = async (file, blurredBucketName) => {
const tempLocalPath = `/tmp/${path.parse(file.name).base}`;
const tempLocalBlurredPath = `/tmp/blurred-${path.parse(file.name).base}`;

// Download file from bucket.
try {
Expand All @@ -74,33 +83,31 @@ const blurImage = async (file, blurredBucketName) => {
throw new Error(`File download failed: ${err}`);
}

await new Promise((resolve, reject) => {
gm(tempLocalPath)
.blur(0, 16)
.write(tempLocalPath, (err, stdout) => {
if (err) {
console.error('Failed to blur image.', err);
reject(err);
} else {
console.log(`Blurred image: ${file.name}`);
resolve(stdout);
}
});
});
try {
await sharp(tempLocalPath).blur(16).toFile(tempLocalBlurredPath);

console.log(`Blurred image: ${file.name}`);
} catch (err) {
console.error('Failed to blur image.', err);
throw err;
}

// Upload result to a different bucket, to avoid re-triggering this function.
const blurredBucket = storage.bucket(blurredBucketName);

// Upload the Blurred image back into the bucket.
const gcsPath = `gs://${blurredBucketName}/${file.name}`;
try {
await blurredBucket.upload(tempLocalPath, {destination: file.name});
await blurredBucket.upload(tempLocalBlurredPath, {destination: file.name});
console.log(`Uploaded blurred image to: ${gcsPath}`);
} catch (err) {
throw new Error(`Unable to upload blurred image to ${gcsPath}: ${err}`);
} finally {
// Delete the temporary file.
await Promise.allSettled([
fs.unlink(tempLocalPath),
fs.unlink(tempLocalBlurredPath),
]);
}

// Delete the temporary file.
return fs.unlink(tempLocalPath);
};
// [END functions_imagemagick_blur]
6 changes: 3 additions & 3 deletions functions/v2/imagemagick/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
},
"engines": {
"node": ">=16.0.0"
"node": ">=18.17.0"
},
"scripts": {
"test": "c8 mocha -p -j 2 test/*.test.js --timeout=20000 --exit"
Expand All @@ -18,7 +18,7 @@
"@google-cloud/functions-framework": "^3.1.0",
"@google-cloud/storage": "^7.0.0",
"@google-cloud/vision": "^4.0.0",
"gm": "^1.23.1"
"sharp": "^0.34.5"
Comment thread
angelcaamal marked this conversation as resolved.
},
"devDependencies": {
"c8": "^10.0.0",
Expand All @@ -27,4 +27,4 @@
"sinon": "^18.0.0",
"supertest": "^7.0.0"
}
}
}
6 changes: 0 additions & 6 deletions functions/v2/imagemagick/test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
'use strict';

const assert = require('assert');
const {execSync} = require('child_process');
const {Storage} = require('@google-cloud/storage');
const sinon = require('sinon');
const supertest = require('supertest');
Expand All @@ -34,11 +33,6 @@ const testFiles = {

require('../index');

// ImageMagick is available by default in Cloud Run Functions environments
// https://cloud.google.com/functions/1stgendocs/tutorials/imagemagick-1st-gen.md#importing_dependencies
// Manually install it for testing only.
execSync('sudo apt-get install imagemagick -y');

describe('functions/imagemagick tests', () => {
before(async () => {
let exists;
Expand Down
18 changes: 8 additions & 10 deletions functions/v2/imagemagick/test/unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,29 @@ const loadSample = (adultResult, fileName) => {
return {
bucket: sinon.stub().returnsThis(),
file: sinon.stub().returnsThis(),
upload: sinon.stub().returnsThis(),
download: sinon.stub().returnsThis(),
upload: sinon.stub().resolves(),
download: sinon.stub().resolves(),
name: fileName,
};
},
};

const gm = () => {
return {
blur: sinon.stub().returnsThis(),
write: sinon.stub().yields(),
};
const sharpInstance = {
blur: sinon.stub().returnsThis(),
toFile: sinon.stub().resolves(),
};
gm.subClass = sinon.stub().returnsThis();
const sharpMock = sinon.stub().returns(sharpInstance);

const fs = {
promises: {
unlink: sinon.stub(),
unlink: sinon.stub().resolves(),
},
};

return proxyquire('..', {
'@google-cloud/vision': vision,
'@google-cloud/storage': storage,
gm: gm,
sharp: sharpMock,
fs: fs,
});
};
Expand Down
Loading