Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion packages/image_picker/image_picker/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ dependencies:
flutter:
sdk: flutter
flutter_plugin_android_lifecycle: ^2.0.1
image_picker_for_web: ^2.1.0
image_picker_for_web:
path: ../image_picker_for_web
image_picker_platform_interface: ^2.3.0

dev_dependencies:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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:convert';
import 'dart:html' as html;
import 'dart:typed_data';
Expand All @@ -13,14 +14,19 @@ import 'package:integration_test/integration_test.dart';

final String expectedStringContents = 'Hello, world!';
final String otherStringContents = 'Hello again, world!';
final String pngFileBase64Contents = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAcTAAAHEwHOIA8IAAAB0mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj4KICAgICAgICAgPHBob3Rvc2hvcDpDcmVkaXQ+wqkgR29vZ2xlPC9waG90b3Nob3A6Q3JlZGl0PgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4K43gerQAAAA1JREFUCB1jeOVs+h8ABd8CYkMBAJAAAAAASUVORK5CYII=";

final Uint8List bytes = utf8.encode(expectedStringContents) as Uint8List;
final Uint8List otherBytes = utf8.encode(otherStringContents) as Uint8List;
final Uint8List pngFileBytes = utf8.encode(pngFileBase64Contents) as Uint8List;

final Map<String, dynamic> options = {
'type': 'text/plain',
'lastModified': DateTime.utc(2017, 12, 13).millisecondsSinceEpoch,
};
final html.File textFile = html.File([bytes], 'hello.txt', options);
final html.File secondTextFile = html.File([otherBytes], 'secondFile.txt');
final html.File pngImageFile = html.File([pngFileBytes],'testimage.png');

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
Expand Down Expand Up @@ -112,6 +118,39 @@ void main() {
expect(secondFile.length(), completion(secondTextFile.size));
});

testWidgets('image is not scaled if maxWidth and maxHeight is not set',(WidgetTester tester) async {
final mockInput = html.FileUploadInputElement();
final overrides = ImagePickerPluginTestOverrides()
..createInputElement = ((_, __) => mockInput)
..getMultipleFilesFromInput = ((_) => [pngImageFile]);

final plugin = ImagePickerPlugin(overrides: overrides);

// Init the pick file dialog...
final image = plugin.getImage(source: ImageSource.gallery,);
final imageElement = html.ImageElement(src: pngFileBase64Contents);
final imageloadCompleter = Completer<void>();
imageElement.onLoad.listen((event) {
print(event);
imageloadCompleter.complete();
});
mockInput.dispatchEvent(html.Event('change'));

await imageloadCompleter.future;
expect(imageElement.width,1);
expect(imageElement.height,1);
final XFile xFile = await image;
final pickedImageElement = html.ImageElement(src: html.Url.createObjectUrl(html.Blob(await xFile.readAsBytes())));
final newCompleter = Completer<void>();
pickedImageElement.onLoad.listen((event) {
newCompleter.complete();
}
);
await newCompleter.future;
expect(pickedImageElement.width,99);

});

// There's no good way of detecting when the user has "aborted" the selection.

testWidgets('computeCaptureAttribute', (WidgetTester tester) async {
Expand Down Expand Up @@ -170,4 +209,6 @@ void main() {
expect(input.attributes, contains('multiple'));
});
});


}
157 changes: 41 additions & 116 deletions packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
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/image_resizer.dart';
import 'package:meta/meta.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';

Expand All @@ -23,6 +24,8 @@ class ImagePickerPlugin extends ImagePickerPlatform {

late html.Element _target;

final _imageResizer = ImageResizer();

/// A constructor that allows tests to override the function that creates file inputs.
ImagePickerPlugin({
@visibleForTesting ImagePickerPluginTestOverrides? overrides,
Expand Down Expand Up @@ -92,7 +95,7 @@ class ImagePickerPlugin extends ImagePickerPlatform {
String? capture,
}) {
html.FileUploadInputElement input =
createInputElement(accept, capture) as html.FileUploadInputElement;
createInputElement(accept, capture) as html.FileUploadInputElement;
_injectAndActivate(input);
return _getSelectedFile(input);
}
Expand Down Expand Up @@ -121,11 +124,8 @@ class ImagePickerPlugin extends ImagePickerPlatform {
List<XFile> files = await getFiles(
accept: _kAcceptImageMimeType,
capture: capture,
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: imageQuality,
);
return files.first;
return _imageResizer.resizeImageIfNeeded(files.first, maxWidth, maxHeight, imageQuality);
}

/// Returns an [XFile] containing the video that was picked.
Expand Down Expand Up @@ -160,13 +160,11 @@ class ImagePickerPlugin extends ImagePickerPlatform {
double? maxWidth,
double? maxHeight,
int? imageQuality,
}) {
return getFiles(
accept: _kAcceptImageMimeType,
multiple: true,
imageQuality: imageQuality,
maxHeight: maxHeight,
maxWidth: maxWidth);
}) async {
return await Future.wait((await getFiles(
accept: _kAcceptImageMimeType, multiple: true))
.map((e) =>
_imageResizer.resizeImageIfNeeded(e, maxWidth, maxHeight, imageQuality)));
}

/// Injects a file input with the specified accept+capture attributes, and
Expand All @@ -179,22 +177,19 @@ class ImagePickerPlugin extends ImagePickerPlatform {
///
/// See https://caniuse.com/#feat=html-media-capture
@visibleForTesting
Future<List<XFile>> getFiles(
{String? accept,
String? capture,
bool multiple = false,
double? maxWidth,
double? maxHeight,
int? imageQuality}) {
Future<List<XFile>> getFiles({
String? accept,
String? capture,
bool multiple = false,
}) {
html.FileUploadInputElement input = createInputElement(
accept,
capture,
multiple: multiple,
) as html.FileUploadInputElement;
_injectAndActivate(input);

return _getSelectedXFiles(input,
maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality);
return _getSelectedXFiles(input);
}

// DOM methods
Expand All @@ -221,7 +216,7 @@ class ImagePickerPlugin extends ImagePickerPlatform {
/// Returns a list of selected files.
List<html.File>? _handleOnChangeEvent(html.Event event) {
final html.FileUploadInputElement input =
event.target as html.FileUploadInputElement;
event.target as html.FileUploadInputElement;
return _getFilesFromInput(input);
}

Expand Down Expand Up @@ -249,29 +244,25 @@ class ImagePickerPlugin extends ImagePickerPlatform {
}

/// Monitors an <input type="file"> and returns the selected file(s).
Future<List<XFile>> _getSelectedXFiles(html.FileUploadInputElement input,
{double? maxWidth, double? maxHeight, int? imageQuality}) {
Future<List<XFile>> _getSelectedXFiles(html.FileUploadInputElement input) {
final Completer<List<XFile>> _completer = Completer<List<XFile>>();
// Observe the input until we can return something
input.onChange.first.then((event) async {
final files = _handleOnChangeEvent(event);
if (!_completer.isCompleted && files != null) {
//Convert the file if one of the parameter is not null
if (imageQuality != null || maxWidth != null || maxHeight != null) {
final convertedFileUris = await Future.wait(files.map((e) async =>
await _getCompressedUri(e, imageQuality, maxHeight, maxWidth)));

int index = 0;
_completer.complete(files.map((file) {
index += 1;
final xFile = file.toXFile(path: convertedFileUris[index - 1]);
return xFile;
}).toList());
} else {
_completer.complete(files.map((file) {
return file.toXFile();
}).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 All @@ -290,7 +281,8 @@ class ImagePickerPlugin extends ImagePickerPlatform {
var target = html.querySelector('#${id}');
if (target == null) {
final html.Element targetElement =
html.Element.tag('flt-image-picker-inputs')..id = id;
html.Element.tag('flt-image-picker-inputs')
..id = id;

html.querySelector('body')!.children.add(targetElement);
target = targetElement;
Expand All @@ -301,11 +293,10 @@ class ImagePickerPlugin extends ImagePickerPlatform {
/// Creates an input element that accepts certain file types, and
/// allows to `capture` from the device's cameras (where supported)
@visibleForTesting
html.Element createInputElement(
String? accept,
String? capture, {
bool multiple = false,
}) {
html.Element createInputElement(String? accept,
String? capture, {
bool multiple = false,
}) {
if (_hasOverrides) {
return _overrides!.createInputElement(accept, capture);
}
Expand All @@ -327,87 +318,21 @@ class ImagePickerPlugin extends ImagePickerPlatform {
_target.children.add(element);
element.click();
}

Future<String> _getCompressedUri(html.File file, int? imageQuality,
double? maxHeight, double? maxWidth) async {
//max width, max height, imageQuality are not supported for gif
if (file.type == "image/gif") {
return html.Url.createObjectUrl(file);
}
final Completer<String> _completer = Completer<String>();
final blobUrl = html.Url.createObjectUrl(file);
final image = html.ImageElement(src: blobUrl);
image.onLoad.listen((event) async {
html.Url.revokeObjectUrl(blobUrl);
final canvas = html.CanvasElement();
final size = _calculateSize(image, maxWidth ?? image.width!.toDouble(),
maxHeight ?? image.height!.toDouble());
canvas.width = size[0];
canvas.height = size[1];
final ctx = canvas.context2D;
if (maxHeight == null && maxWidth == null) {
ctx.drawImage(image, 0, 0);
} else {
ctx.drawImageScaled(image, 0, 0, canvas.width!, canvas.height!);
}
final blob = await canvas.toBlob(
file.type,
(imageQuality ?? 100) /
100.0); // Image quality only works for jpeg images
_completer.complete(html.Url.createObjectUrlFromBlob(blob));
});
image.onError.listen((event) {
//Return the original image if error comes
_completer.complete(html.Url.createObjectUrl(file));
});
return _completer.future;
}

List<int> _calculateSize(
html.ImageElement img, double maxWidth, double maxHeight) {
var width = img.width!;
var height = img.height!;

// calculate the width and height, constraining the proportions
if (width > height) {
if (width > maxWidth) {
height = ((height * maxWidth) / width).round();
width = maxWidth.toInt();
}
} else {
if (height > maxHeight) {
width = ((width * maxHeight) / height).round();
height = maxHeight.toInt();
}
}
return [width, height];
}
}

extension on html.File {
XFile toXFile({String? path}) => XFile(
path ?? html.Url.createObjectUrl(this),
name: this.name,
length: this.size,
lastModified: DateTime.fromMillisecondsSinceEpoch(
this.lastModified ?? DateTime.now().millisecondsSinceEpoch,
),
mimeType: this.type,
);
}

// Some tools to override behavior for unit-testing
/// A function that creates a file input with the passed in `accept` and `capture` attributes.
@visibleForTesting
typedef OverrideCreateInputFunction = html.Element Function(
String? accept,
String? capture,
);
String? accept,
String? capture,
);

/// A function that extracts list of files from the file `input` passed in.
@visibleForTesting
typedef OverrideExtractMultipleFilesFromInputFunction = List<html.File>
Function(html.Element? input);
Function(html.Element? input);

/// Overrides for some of the functionality above.
@visibleForTesting
Expand Down
Loading