This repository has been archived by the owner on Jan 8, 2024. It is now read-only.
forked from flutter/plugins
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[image_picker_for_web] Added support for maxWidth, maxHeight and imag…
…eQuality (flutter#4389)
- Loading branch information
1 parent
6af41f0
commit 4005c6a
Showing
8 changed files
with
382 additions
and
17 deletions.
There are no files selected for viewing
This file contains 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 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 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 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 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 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 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.