This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[image_picker_for_web] Added support for maxWidth, maxHeight and imageQuality #4389
Merged
fluttergithubbot
merged 37 commits into
flutter:master
from
balvinderz:compress_file_web
Oct 18, 2021
Merged
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 c3c1344
image scaling and quality done
balvinderz cf0ee4d
implement max width max height and imageQuality for web
balvinderz 130f78a
added maxWidth max height and imageQuality for web
balvinderz b8d3d65
dartfmt
balvinderz be720c2
revert pubspec.yaml of image_picker
balvinderz 754cec5
run format
balvinderz 942ac46
added image resizer class
balvinderz 982d40a
Merge branch 'master' of https://github.com/flutter/plugins into comp…
balvinderz 6145cf5
Merge branch 'compress_file_web' of https://github.com/balvinderz/plu…
balvinderz 91f92e6
fix calculate size logic
balvinderz b10408f
fix if condition in resizeImage
balvinderz 42d8191
image resizer done
balvinderz 019a738
added utils test'
balvinderz 2b1842d
update readme
balvinderz 5f7a0dd
add image resizer tests
balvinderz f61b965
dartfmt
balvinderz 5b477e6
add licenses
balvinderz 60e0b9f
remove path dependancy
balvinderz 7a63323
fix typo and remove extra space
balvinderz c75b036
fix doc comment
balvinderz f5e6d45
remove double spaces
balvinderz d38de67
review changes
balvinderz 5c352f6
fix typo
balvinderz 08b8240
use completeError
balvinderz 7151d71
remove unused imports
balvinderz a3bbc58
dartfmt
balvinderz d81990b
fix typo
balvinderz c90eff3
remove 1 doc comment
balvinderz 811b3fb
update tests and resizing algorithm
balvinderz 3e4d7c1
Review changes
balvinderz 554e996
move image resizer files in src
balvinderz d15b8b1
dartfmt
balvinderz b87f0cf
dartfmt again
balvinderz d99a702
Fix some review comments
ditman 5e825ab
Slight update in the CHANGELOG
ditman c4d057e
Merge branch 'master' into compress_file_web
ditman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
packages/image_picker/image_picker_for_web/example/integration_test/image_resizer_test.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
packages/image_picker/image_picker_for_web/lib/src/image_resizer.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
packages/image_picker/image_picker_for_web/lib/src/image_resizer_utils.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.