-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[image_picker_for_web] Added image resize functionality #3439
Changes from all commits
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 |
---|---|---|
@@ -1,9 +1,10 @@ | ||
import 'dart:async'; | ||
import 'dart:html' as html; | ||
import 'dart:math' as math; | ||
|
||
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; | ||
import 'package:meta/meta.dart'; | ||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | ||
import 'package:meta/meta.dart'; | ||
|
||
final String _kImagePickerInputsDomId = '__image_picker_web-file-input'; | ||
final String _kAcceptImageMimeType = 'image/*'; | ||
|
@@ -37,9 +38,85 @@ class ImagePickerPlugin extends ImagePickerPlatform { | |
double maxHeight, | ||
int imageQuality, | ||
CameraDevice preferredCameraDevice = CameraDevice.rear, | ||
}) { | ||
}) async { | ||
String capture = computeCaptureAttribute(source, preferredCameraDevice); | ||
return pickFile(accept: _kAcceptImageMimeType, capture: capture); | ||
|
||
final pickedFile = await pickFile( | ||
accept: _kAcceptImageMimeType, | ||
capture: capture, | ||
); | ||
|
||
if (maxWidth != null && maxHeight != null) { | ||
return _resizeImage(pickedFile.path, maxWidth, maxHeight, imageQuality); | ||
} else { | ||
return pickedFile; | ||
} | ||
} | ||
|
||
static Future<PickedFile> _resizeImage( | ||
String src, | ||
double maxWidth, | ||
double maxHeight, | ||
int imageQuality, | ||
) { | ||
final completer = Completer<PickedFile>(); | ||
final img = html.ImageElement(); | ||
|
||
img.onError.listen((event) { | ||
completer.complete(PickedFile('')); | ||
}); | ||
|
||
img.onLoad.listen((event) { | ||
if (img.width > 1 && img.height > 1) { | ||
final canvas = html.CanvasElement(); | ||
final ctx = canvas.context2D; | ||
|
||
var width = math.min(img.width, maxWidth); | ||
var height = math.min(img.height, maxHeight); | ||
|
||
if (!_isImageQualityValid(imageQuality)) { | ||
imageQuality = 100; | ||
} | ||
|
||
final shouldDownscale = maxWidth < img.width || maxHeight < img.height; | ||
if (shouldDownscale) { | ||
final downscaledWidth = (height / img.height) * img.width; | ||
final downscaledHeight = (width / img.width) * img.height; | ||
|
||
if (width < height) { | ||
height = downscaledHeight; | ||
} else if (height < width) { | ||
width = downscaledWidth; | ||
} else { | ||
if (img.width < img.height) { | ||
width = downscaledWidth; | ||
} else if (img.height < img.width) { | ||
height = downscaledHeight; | ||
} | ||
} | ||
} | ||
|
||
canvas.height = height.floor(); | ||
canvas.width = width.floor(); | ||
|
||
// Draw the image to canvas and resize | ||
ctx.drawImageScaled(img, 0, 0, canvas.width, canvas.height); | ||
final base64 = canvas.toDataUrl('image/png', imageQuality / 100); | ||
completer.complete(PickedFile(base64)); | ||
} | ||
}); | ||
|
||
img.src = src; | ||
// make sure the load event fires for cached images too | ||
if (img.complete) { | ||
// Flush cache | ||
img.src = | ||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; | ||
Comment on lines
+112
to
+114
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. Will this trigger the onLoad event above? If so, you might want to check for this special image to do nothing with it; otherwise you'll attempt to complete the 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. Yes it's a bit weird here and I don't really understand what's going on. 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. @ditman, you are right, if it does trigger the onload event several times, it would cause exceptions. The JavaScript code I used was not using a Promise (or Completer) but simply copying the result to the canvas so the hack to avoid cases where onload did not fire did not have any side effects but here it would. The base64 image is a 1x1 gif. Maybe the completer may be avoided if the loaded image size is 1x1. 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. Added a simple |
||
// Try again | ||
img.src = src; | ||
} | ||
|
||
return completer.future; | ||
} | ||
|
||
@override | ||
|
@@ -120,6 +197,10 @@ class ImagePickerPlugin extends ImagePickerPlatform { | |
return _completer.future; | ||
} | ||
|
||
static bool _isImageQualityValid(int imageQuality) { | ||
return imageQuality != null && imageQuality > 0 && imageQuality < 100; | ||
} | ||
|
||
/// Initializes a DOM container where we can host input elements. | ||
html.Element _ensureInitialized(String id) { | ||
var target = html.querySelector('#${id}'); | ||
|
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.
Don't you need to add this
img
to the DOM for the events to fire?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.
I'm haven't had many experience of developing flutter web plugins and I don't know how to proceed here.
Could you elaborate on this @ditman please?
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.
Hm, some times for some DOM elements to fire their load/error events they must be injected into the markup of the page (anywhere, but they need to be injected).
I'm not sure this is the case, but I'd test this code across all main browsers (Edge/Safari/Firefox/Chrome) to ensure that the on*** events of the ImageElement are firing as expected.
Same approach for the comment below where we're resetting the image to something blank, and then the desired source again :)
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.
@ditman , you are right, events are generally fired if the element is added to DOM tree but while converting my JavaScript code to Dart, I realized that it worked without adding it to DOM for Chrome. I haven't tested for other mentioned browsers.
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.
OK I understand what you're saying here, but I don't know how to exactly add this element to the DOM tree :/
I'd need some help here @postacik @ditman .