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

[image_picker_for_web] Added support for maxWidth, maxHeight and imageQuality #4389

Merged
merged 37 commits into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
dd87538
scaling and image quality done
balvinderz Sep 25, 2021
c3c1344
image scaling and quality done
balvinderz Sep 25, 2021
cf0ee4d
implement max width max height and imageQuality for web
balvinderz Sep 27, 2021
130f78a
added maxWidth max height and imageQuality for web
balvinderz Sep 27, 2021
b8d3d65
dartfmt
balvinderz Sep 27, 2021
be720c2
revert pubspec.yaml of image_picker
balvinderz Sep 27, 2021
754cec5
run format
balvinderz Sep 27, 2021
942ac46
added image resizer class
balvinderz Sep 30, 2021
982d40a
Merge branch 'master' of https://github.com/flutter/plugins into comp…
balvinderz Sep 30, 2021
6145cf5
Merge branch 'compress_file_web' of https://github.com/balvinderz/plu…
balvinderz Sep 30, 2021
91f92e6
fix calculate size logic
balvinderz Sep 30, 2021
b10408f
fix if condition in resizeImage
balvinderz Sep 30, 2021
42d8191
image resizer done
balvinderz Sep 30, 2021
019a738
added utils test'
balvinderz Sep 30, 2021
2b1842d
update readme
balvinderz Sep 30, 2021
5f7a0dd
add image resizer tests
balvinderz Sep 30, 2021
f61b965
dartfmt
balvinderz Sep 30, 2021
5b477e6
add licenses
balvinderz Sep 30, 2021
60e0b9f
remove path dependancy
balvinderz Sep 30, 2021
7a63323
fix typo and remove extra space
balvinderz Sep 30, 2021
c75b036
fix doc comment
balvinderz Sep 30, 2021
f5e6d45
remove double spaces
balvinderz Sep 30, 2021
d38de67
review changes
balvinderz Oct 2, 2021
5c352f6
fix typo
balvinderz Oct 4, 2021
08b8240
use completeError
balvinderz Oct 4, 2021
7151d71
remove unused imports
balvinderz Oct 4, 2021
a3bbc58
dartfmt
balvinderz Oct 4, 2021
d81990b
fix typo
balvinderz Oct 4, 2021
c90eff3
remove 1 doc comment
balvinderz Oct 4, 2021
811b3fb
update tests and resizing algorithm
balvinderz Oct 5, 2021
3e4d7c1
Review changes
balvinderz Oct 5, 2021
554e996
move image resizer files in src
balvinderz Oct 6, 2021
d15b8b1
dartfmt
balvinderz Oct 6, 2021
b87f0cf
dartfmt again
balvinderz Oct 6, 2021
d99a702
Fix some review comments
ditman Oct 16, 2021
5e825ab
Slight update in the CHANGELOG
ditman Oct 16, 2021
c4d057e
Merge branch 'master' into compress_file_web
ditman Oct 16, 2021
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
5 changes: 5 additions & 0 deletions packages/image_picker/image_picker_for_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.1.4

* Implemented `maxWidth`, `maxHeight` and `imageQuality` when selecting images
(except for gifs).

## 2.1.3

* Add `implements` to pubspec.
Expand Down
5 changes: 3 additions & 2 deletions packages/image_picker/image_picker_for_web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ Each browser may implement `capture` any way they please, so it may (or may not)
difference in your users' experience.

### pickImage()
The arguments `maxWidth`, `maxHeight` and `imageQuality` are not supported on the web.
The arguments `maxWidth`, `maxHeight` and `imageQuality` are not supported for gif images.
The argument `imageQuality` only works for jpeg and webp images.

### pickVideo()
The argument `maxDuration` is not supported on the web.
Expand All @@ -63,7 +64,7 @@ You should be able to use `package:image_picker` _almost_ as normal.
Once the user has picked a file, the returned `PickedFile` instance will contain a
`network`-accessible URL (pointing to a location within the browser).

The instace will also let you retrieve the bytes of the selected file across all platforms.
The instance will also let you retrieve the bytes of the selected file across all platforms.

If you want to use the path directly, your code would need look like this:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:html' as html;
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter_test/flutter_test.dart';
import 'package:image_picker_for_web/src/image_resizer.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:integration_test/integration_test.dart';

//This is a sample 10x10 png image
final String pngFileBase64Contents =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAQMAAAC3/F3+AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABlBMVEXqQzX+/v6lfubTAAAAAWJLR0QB/wIt3gAAAAlwSFlzAAAHEwAABxMBziAPCAAAAAd0SU1FB+UJHgsdDM0ErZoAAAALSURBVAjXY2DABwAAHgABboVHMgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wOS0zMFQxMToyOToxMi0wNDowMHCDC24AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDktMzBUMTE6Mjk6MTItMDQ6MDAB3rPSAAAAAElFTkSuQmCC";

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

// Under test...
late ImageResizer imageResizer;
late XFile pngFile;
setUp(() {
imageResizer = ImageResizer();
final pngHtmlFile = _base64ToFile(pngFileBase64Contents, "pngImage.png");
pngFile = XFile(html.Url.createObjectUrl(pngHtmlFile),
name: pngHtmlFile.name, mimeType: pngHtmlFile.type);
});

testWidgets("image is loaded correctly ", (WidgetTester tester) async {
final imageElement = await imageResizer.loadImage(pngFile.path);
expect(imageElement.width!, 10);
expect(imageElement.height!, 10);
});

testWidgets(
"canvas is loaded with image's width and height when max width and max height are null",
(widgetTester) async {
final imageElement = await imageResizer.loadImage(pngFile.path);
final canvas = imageResizer.resizeImageElement(imageElement, null, null);
expect(canvas.width, imageElement.width);
expect(canvas.height, imageElement.height);
});

testWidgets(
"canvas size is scaled when max width and max height are not null",
(widgetTester) async {
final imageElement = await imageResizer.loadImage(pngFile.path);
final canvas = imageResizer.resizeImageElement(imageElement, 8, 8);
expect(canvas.width, 8);
expect(canvas.height, 8);
});

testWidgets("resized image is returned after converting canvas to file",
(widgetTester) async {
final imageElement = await imageResizer.loadImage(pngFile.path);
final canvas = imageResizer.resizeImageElement(imageElement, null, null);
final resizedImage =
await imageResizer.writeCanvasToFile(pngFile, canvas, null);
expect(resizedImage.name, "scaled_${pngFile.name}");
});

testWidgets("image is scaled when maxWidth is set",
(WidgetTester tester) async {
final scaledImage =
await imageResizer.resizeImageIfNeeded(pngFile, 5, null, null);
expect(scaledImage.name, "scaled_${pngFile.name}");
final scaledImageSize = await _getImageSize(scaledImage);
expect(scaledImageSize, Size(5, 5));
});

testWidgets("image is scaled when maxHeight is set",
(WidgetTester tester) async {
final scaledImage =
await imageResizer.resizeImageIfNeeded(pngFile, null, 6, null);
expect(scaledImage.name, "scaled_${pngFile.name}");
final scaledImageSize = await _getImageSize(scaledImage);
expect(scaledImageSize, Size(6, 6));
});

testWidgets("image is scaled when imageQuality is set",
(WidgetTester tester) async {
final scaledImage =
await imageResizer.resizeImageIfNeeded(pngFile, null, null, 89);
expect(scaledImage.name, "scaled_${pngFile.name}");
});

testWidgets("image is scaled when maxWidth,maxHeight,imageQuality are set",
(WidgetTester tester) async {
final scaledImage =
await imageResizer.resizeImageIfNeeded(pngFile, 3, 4, 89);
expect(scaledImage.name, "scaled_${pngFile.name}");
});

testWidgets("image is not scaled when maxWidth,maxHeight, is set",
(WidgetTester tester) async {
final scaledImage =
await imageResizer.resizeImageIfNeeded(pngFile, null, null, null);
expect(scaledImage.name, pngFile.name);
});
}

Future<Size> _getImageSize(XFile file) async {
final completer = Completer<Size>();
final image = html.ImageElement(src: file.path);
image.onLoad.listen((event) {
completer.complete(Size(image.width!.toDouble(), image.height!.toDouble()));
});
image.onError.listen((event) {
completer.complete(Size(0, 0));
});
return completer.future;
}

html.File _base64ToFile(String data, String fileName) {
var arr = data.split(',');
var bstr = html.window.atob(arr[1]);
var n = bstr.length, u8arr = Uint8List(n);

while (n >= 1) {
u8arr[n - 1] = bstr.codeUnitAt(n - 1);
n--;
}

return html.File([u8arr], fileName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:html' as html;

import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:image_picker_for_web/src/image_resizer.dart';
import 'package:meta/meta.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';

Expand All @@ -23,10 +24,14 @@ class ImagePickerPlugin extends ImagePickerPlatform {

late html.Element _target;

late ImageResizer _imageResizer;

/// A constructor that allows tests to override the function that creates file inputs.
ImagePickerPlugin({
@visibleForTesting ImagePickerPluginTestOverrides? overrides,
@visibleForTesting ImageResizer? imageResizer,
}) : _overrides = overrides {
_imageResizer = imageResizer ?? ImageResizer();
_target = _ensureInitialized(_kImagePickerInputsDomId);
}

Expand Down Expand Up @@ -122,7 +127,12 @@ class ImagePickerPlugin extends ImagePickerPlatform {
accept: _kAcceptImageMimeType,
capture: capture,
);
return files.first;
return _imageResizer.resizeImageIfNeeded(
files.first,
maxWidth,
maxHeight,
imageQuality,
);
}

/// Returns an [XFile] containing the video that was picked.
Expand Down Expand Up @@ -157,8 +167,21 @@ class ImagePickerPlugin extends ImagePickerPlatform {
double? maxWidth,
double? maxHeight,
int? imageQuality,
}) {
return getFiles(accept: _kAcceptImageMimeType, multiple: true);
balvinderz marked this conversation as resolved.
Show resolved Hide resolved
}) async {
final List<XFile> images = await getFiles(
accept: _kAcceptImageMimeType,
multiple: true,
);
final Iterable<Future<XFile>> resized = images.map(
(image) => _imageResizer.resizeImageIfNeeded(
image,
maxWidth,
maxHeight,
imageQuality,
),
);

return Future.wait<XFile>(resized);
}

/// Injects a file input with the specified accept+capture attributes, and
Expand Down Expand Up @@ -244,17 +267,17 @@ class ImagePickerPlugin extends ImagePickerPlatform {
input.onChange.first.then((event) {
final files = _handleOnChangeEvent(event);
if (!_completer.isCompleted && files != null) {
_completer.complete(files
.map((file) => XFile(
html.Url.createObjectUrl(file),
name: file.name,
length: file.size,
lastModified: DateTime.fromMillisecondsSinceEpoch(
file.lastModified ?? DateTime.now().millisecondsSinceEpoch,
),
mimeType: file.type,
))
.toList());
_completer.complete(files.map((file) {
return XFile(
html.Url.createObjectUrl(file),
name: file.name,
length: file.size,
lastModified: DateTime.fromMillisecondsSinceEpoch(
file.lastModified ?? DateTime.now().millisecondsSinceEpoch,
),
mimeType: file.type,
);
}).toList());
}
});
input.onError.first.then((event) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:image_picker_for_web/src/image_resizer_utils.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'dart:html' as html;

/// Helper class that resizes images.
class ImageResizer {
/// Resizes the image if needed.
/// (Does not support gif images)
Future<XFile> resizeImageIfNeeded(XFile file, double? maxWidth,
double? maxHeight, int? imageQuality) async {
if (!imageResizeNeeded(maxWidth, maxHeight, imageQuality) ||
file.mimeType == "image/gif") {
// Implement maxWidth and maxHeight for image/gif
return file;
}
try {
final imageElement = await loadImage(file.path);
final canvas = resizeImageElement(imageElement, maxWidth, maxHeight);
final resizedImage = await writeCanvasToFile(file, canvas, imageQuality);
html.Url.revokeObjectUrl(file.path);
return resizedImage;
} catch (e) {
return file;
}
}

/// function that loads the blobUrl into an imageElement
Future<html.ImageElement> loadImage(String blobUrl) {
final imageLoadCompleter = Completer<html.ImageElement>();
final imageElement = html.ImageElement();
imageElement.src = blobUrl;

imageElement.onLoad.listen((event) {
imageLoadCompleter.complete(imageElement);
});
imageElement.onError.listen((event) {
final exception = ("Error while loading image.");
imageElement.remove();
imageLoadCompleter.completeError(exception);
});
return imageLoadCompleter.future;
}

/// Draws image to a canvas while resizing the image to fit the [maxWidth],[maxHeight] constraints
html.CanvasElement resizeImageElement(
html.ImageElement source, double? maxWidth, double? maxHeight) {
final newImageSize = calculateSizeOfDownScaledImage(
Size(source.width!.toDouble(), source.height!.toDouble()),
maxWidth,
maxHeight);
final canvas = html.CanvasElement();
canvas.width = newImageSize.width.toInt();
canvas.height = newImageSize.height.toInt();
final context = canvas.context2D;
if (maxHeight == null && maxWidth == null) {
context.drawImage(source, 0, 0);
} else {
context.drawImageScaled(source, 0, 0, canvas.width!, canvas.height!);
}
return canvas;
}

/// function that converts a canvas element to Xfile
/// [imageQuality] is only supported for jpeg and webp images.
Future<XFile> writeCanvasToFile(
XFile originalFile, html.CanvasElement canvas, int? imageQuality) async {
final calculatedImageQuality = ((min(imageQuality ?? 100, 100)) / 100.0);
final blob =
await canvas.toBlob(originalFile.mimeType, calculatedImageQuality);
return XFile(html.Url.createObjectUrlFromBlob(blob),
mimeType: originalFile.mimeType,
name: "scaled_" + originalFile.name,
lastModified: DateTime.now(),
length: blob.size);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:math';
import 'dart:ui';

import 'package:flutter/material.dart';

///a function that checks if an image needs to be resized or not
bool imageResizeNeeded(double? maxWidth, double? maxHeight, int? imageQuality) {
return imageQuality != null
? isImageQualityValid(imageQuality)
: (maxWidth != null || maxHeight != null);
}

/// a function that checks if image quality is between 0 to 100
bool isImageQualityValid(int imageQuality) {
return (imageQuality >= 0 && imageQuality <= 100);
}

/// a function that calculates the size of the downScaled image.
/// imageWidth is the width of the image
/// imageHeight is the height of the image
/// maxWidth is the maximum width of the scaled image
/// maxHeight is the maximum height of the scaled image
Size calculateSizeOfDownScaledImage(
Size imageSize, double? maxWidth, double? maxHeight) {
double widthFactor = maxWidth != null ? imageSize.width / maxWidth : 1;
double heightFactor = maxHeight != null ? imageSize.height / maxHeight : 1;
double resizeFactor = max(widthFactor, heightFactor);
return (resizeFactor > 1 ? imageSize ~/ resizeFactor : imageSize);
}
2 changes: 1 addition & 1 deletion packages/image_picker/image_picker_for_web/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: image_picker_for_web
description: Web platform implementation of image_picker
repository: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_for_web
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
version: 2.1.3
version: 2.1.4

environment:
sdk: ">=2.12.0 <3.0.0"
Expand Down
Loading