Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[image_picker_for_web] Added image resize functionality #3439

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/*';
Expand Down Expand Up @@ -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();
Copy link
Member

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?

Copy link
Author

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?

Copy link
Member

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 :)

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.

Copy link
Author

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 .


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
Copy link
Member

Choose a reason for hiding this comment

The 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 completer several times, and it'll throw an exception (you can only complete a completer once)

Copy link
Author

Choose a reason for hiding this comment

The 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.
Maybe @postacik could help us here (?).

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a simple img.width > 1 && img.height > 1 check before performing the resize logic.
Give me your feedback here!

// Try again
img.src = src;
}

return completer.future;
}

@override
Expand Down Expand Up @@ -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}');
Expand Down