-
Notifications
You must be signed in to change notification settings - Fork 3.8k
feat(dart): add resize-image quickstart sample #1272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3b25605
cbb439b
11a928c
98b244b
46beb93
03d5c40
b539744
8e2da6f
1d18d7e
37f4842
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .dart_tool/ | ||
| .packages | ||
| build/ | ||
| *.dart_tool | ||
| pubspec.lock |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,119 @@ | ||||||||||||||||||||||||||||||||||||
| import 'dart:typed_data'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import 'package:google_cloud_storage/google_cloud_storage.dart' | ||||||||||||||||||||||||||||||||||||
| show ObjectMetadata, NotFoundException; | ||||||||||||||||||||||||||||||||||||
| import 'package:firebase_functions/firebase_functions.dart'; | ||||||||||||||||||||||||||||||||||||
| import 'package:image/image.dart'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| final defaultWidth = defineInt( | ||||||||||||||||||||||||||||||||||||
| 'DEFAULT_WIDTH', | ||||||||||||||||||||||||||||||||||||
| ParamOptions<int>(defaultValue: 300), | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| void main(List<String> args) async { | ||||||||||||||||||||||||||||||||||||
| await fireUp(args, (firebase) { | ||||||||||||||||||||||||||||||||||||
| /// An https function that resizes images in Cloud Storage. | ||||||||||||||||||||||||||||||||||||
| /// It creates a separate Storage folder to cache stored images | ||||||||||||||||||||||||||||||||||||
| /// so that it does not need to resize an image twice. | ||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||
| /// It returns an HTTP redirect to the public Storage download URL. | ||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||
| /// The query params it accepts are: | ||||||||||||||||||||||||||||||||||||
| /// - image: the image file path in Cloud Storage | ||||||||||||||||||||||||||||||||||||
| /// - width (optional): the width in pixels to resize to | ||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||
| /// Example call: https://<function-url>?image=myFile.png&width=400 | ||||||||||||||||||||||||||||||||||||
| firebase.https.onRequest(name: 'imageOptimizer', (request) async { | ||||||||||||||||||||||||||||||||||||
| // Parse arguments from query params in the URL | ||||||||||||||||||||||||||||||||||||
| final queryParams = request.url.queryParameters; | ||||||||||||||||||||||||||||||||||||
| final imageFileName = queryParams['image']; | ||||||||||||||||||||||||||||||||||||
| if (imageFileName == null) { | ||||||||||||||||||||||||||||||||||||
| throw InvalidArgumentError( | ||||||||||||||||||||||||||||||||||||
| 'No image provided. Include the image file name as a query param.', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| var width = int.tryParse(queryParams['width'] ?? ""); | ||||||||||||||||||||||||||||||||||||
| if (width == null) { | ||||||||||||||||||||||||||||||||||||
| logger.info( | ||||||||||||||||||||||||||||||||||||
| 'Cloud not parse width from query params. Using default width.', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| width = defaultWidth.value(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Get the storage bucket from the built-in parameter | ||||||||||||||||||||||||||||||||||||
| // https://firebase.google.com/docs/functions/config-env#built-in-parameters | ||||||||||||||||||||||||||||||||||||
| final bucketName = storageBucket.value(); | ||||||||||||||||||||||||||||||||||||
| final bucket = firebase.adminApp.storage().bucket(bucketName); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Return early if the image has been resized before | ||||||||||||||||||||||||||||||||||||
| final cachedFileName = 'image-optimizer-cache/${width}w-${imageFileName}'; | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| await bucket.storage.objectMetadata(bucketName, cachedFileName); | ||||||||||||||||||||||||||||||||||||
| final downloadUrl = await firebase.adminApp.storage().getDownloadURL( | ||||||||||||||||||||||||||||||||||||
| bucket, | ||||||||||||||||||||||||||||||||||||
| cachedFileName, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| logger.log('Cache hit. Using existing resized image.'); | ||||||||||||||||||||||||||||||||||||
| return Response.movedPermanently(downloadUrl); | ||||||||||||||||||||||||||||||||||||
| } on NotFoundException { | ||||||||||||||||||||||||||||||||||||
| logger.log('Cache miss. Resizing image to width ${width}'); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Download original image | ||||||||||||||||||||||||||||||||||||
| List<int> originalBytes; | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| originalBytes = await bucket.storage.downloadObject( | ||||||||||||||||||||||||||||||||||||
| bucket.name, | ||||||||||||||||||||||||||||||||||||
| imageFileName, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } on NotFoundException { | ||||||||||||||||||||||||||||||||||||
| throw InvalidArgumentError( | ||||||||||||||||||||||||||||||||||||
| 'Image ${imageFileName} does not exist in bucket ${bucketName}.', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Decode image | ||||||||||||||||||||||||||||||||||||
| final originalImage = decodeImage(Uint8List.fromList(originalBytes)); | ||||||||||||||||||||||||||||||||||||
| if (originalImage == null) { | ||||||||||||||||||||||||||||||||||||
| throw InvalidArgumentError( | ||||||||||||||||||||||||||||||||||||
| 'Failed to decode image. Are you sure it was an image file?', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Resize if needed | ||||||||||||||||||||||||||||||||||||
| var encodedBytes; | ||||||||||||||||||||||||||||||||||||
| if (originalImage.width >= width) { | ||||||||||||||||||||||||||||||||||||
| final resizedImage = copyResize( | ||||||||||||||||||||||||||||||||||||
| originalImage, | ||||||||||||||||||||||||||||||||||||
| width: width, | ||||||||||||||||||||||||||||||||||||
| maintainAspect: true, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| encodedBytes = encodeNamedImage(imageFileName, resizedImage); | ||||||||||||||||||||||||||||||||||||
| if (encodedBytes == null) { | ||||||||||||||||||||||||||||||||||||
| throw InternalError('Failed to encode resized image.'); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| logger.info( | ||||||||||||||||||||||||||||||||||||
| 'Image is already smaller than the requested width. No need to resize.', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| encodedBytes = originalBytes; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Upload resized image to cache directory | ||||||||||||||||||||||||||||||||||||
| await bucket.storage.uploadObject( | ||||||||||||||||||||||||||||||||||||
| bucket.name, | ||||||||||||||||||||||||||||||||||||
| cachedFileName, | ||||||||||||||||||||||||||||||||||||
| encodedBytes, | ||||||||||||||||||||||||||||||||||||
| // Tell clients to cache the resized image to reduce repeat requests | ||||||||||||||||||||||||||||||||||||
| metadata: ObjectMetadata(cacheControl: 'public, max-age=86400'), | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+103
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Return download URL | ||||||||||||||||||||||||||||||||||||
| final downloadUrl = await firebase.adminApp.storage().getDownloadURL( | ||||||||||||||||||||||||||||||||||||
| bucket, | ||||||||||||||||||||||||||||||||||||
| cachedFileName, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| return Response.movedPermanently(downloadUrl); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "functions": { | ||
| "source": ".", | ||
| "codebase": "dart-quickstarts-resize-image" | ||
| }, | ||
| "emulators": { | ||
| "functions": { | ||
| "port": 5001 | ||
| }, | ||
| "storage": { | ||
| "port": 9199 | ||
| }, | ||
| "ui": { | ||
| "enabled": true | ||
| }, | ||
| "singleProjectMode": true | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| name: resize_image | ||
| description: Image resizer example for Firebase Functions for Dart | ||
| publish_to: none | ||
|
|
||
| environment: | ||
| sdk: ^3.11.0 | ||
|
|
||
| dependencies: | ||
| image: ^4.8.0 | ||
| path: ^1.9.1 | ||
| firebase_functions: | ||
| git: | ||
| url: https://github.com/firebase/firebase-functions-dart | ||
| ref: main | ||
| intl: ^0.20.2 | ||
|
|
||
| dev_dependencies: | ||
| build_runner: ^2.10.5 | ||
| lints: ^6.0.0 | ||
|
Comment on lines
+6
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Several version constraints in the environment:
sdk: ^3.5.0
dependencies:
dart_firebase_admin: ^0.4.0
firebase_functions:
git:
url: https://github.com/firebase/firebase-functions-dart
ref: main
google_cloud_storage: ^0.6.0
image: ^4.3.0
path: ^1.9.0
dev_dependencies:
build_runner: ^2.4.0
lints: ^4.0.0 |
||
|
|
||
| dependency_overrides: | ||
| firebase_admin_sdk: | ||
| git: | ||
| url: https://github.com/firebase/firebase-admin-dart | ||
| path: packages/firebase_admin_sdk | ||
| ref: main | ||
| google_cloud_firestore: | ||
| git: | ||
| url: https://github.com/firebase/firebase-admin-dart | ||
| path: packages/google_cloud_firestore | ||
| ref: main | ||
|
Comment on lines
+27
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling
objectMetadatabeforegetDownloadURLis redundant. ThegetDownloadURLmethod internally fetches the object's metadata to retrieve the download token. If the object does not exist, it will throw a 404 error which you are already catching. Removing the explicit metadata check reduces unnecessary API calls.