implement decodeFromPixels (flutter/engine#21298)

This commit is contained in:
Dan Field 2020-09-23 21:42:01 -07:00 committed by GitHub
parent 3b207ab1f7
commit 972e469ebd
2 changed files with 143 additions and 38 deletions

View File

@ -450,22 +450,9 @@ Future<Codec> instantiateImageCodec(
_instantiateImageCodec(list, callback));
}
String? _instantiateImageCodec(
Uint8List list,
engine.Callback<Codec> callback, {
int? width,
int? height,
int? rowBytes,
PixelFormat? format,
}) {
String? _instantiateImageCodec(Uint8List list, engine.Callback<Codec> callback) {
if (engine.experimentalUseSkia) {
if (width == null) {
engine.skiaInstantiateImageCodec(list, callback);
} else {
assert(height != null);
assert(format != null);
engine.skiaInstantiateImageCodec(list, callback, width, height, format!.index, rowBytes);
}
engine.skiaInstantiateImageCodec(list, callback);
return null;
}
final html.Blob blob = html.Blob(<dynamic>[list.buffer]);
@ -502,6 +489,81 @@ Future<void> _decodeImageFromListAsync(Uint8List list, ImageDecoderCallback call
final FrameInfo frameInfo = await codec.getNextFrame();
callback(frameInfo.image);
}
Future<Codec> _createBmp(
Uint8List pixels,
int width,
int height,
int rowBytes,
PixelFormat format,
) {
// See https://en.wikipedia.org/wiki/BMP_file_format for format examples.
final int bufferSize = 0x36 + (width * height * 4);
final ByteData bmpData = ByteData(bufferSize);
// 'BM' header
bmpData.setUint8(0x00, 0x42);
bmpData.setUint8(0x01, 0x4D);
// Size of data
bmpData.setUint32(0x02, bufferSize, Endian.little);
// Offset where pixel array begins
bmpData.setUint32(0x0A, 0x36, Endian.little);
// Bytes in DIB header
bmpData.setUint32(0x0E, 0x28, Endian.little);
// width
bmpData.setUint32(0x12, width, Endian.little);
// height
bmpData.setUint32(0x16, height, Endian.little);
// Color panes
bmpData.setUint16(0x1A, 0x01, Endian.little);
// 32 bpp
bmpData.setUint16(0x1C, 0x20, Endian.little);
// no compression
bmpData.setUint32(0x1E, 0x00, Endian.little);
// raw bitmap data size
bmpData.setUint32(0x22, width * height, Endian.little);
// print DPI width
bmpData.setUint32(0x26, width, Endian.little);
// print DPI height
bmpData.setUint32(0x2A, height, Endian.little);
// colors in the palette
bmpData.setUint32(0x2E, 0x00, Endian.little);
// important colors
bmpData.setUint32(0x32, 0x00, Endian.little);
int pixelDestinationIndex = 0;
late bool swapRedBlue;
switch (format) {
case PixelFormat.bgra8888:
swapRedBlue = true;
break;
case PixelFormat.rgba8888:
swapRedBlue = false;
break;
}
for (int pixelSourceIndex = 0; pixelSourceIndex < pixels.length; pixelSourceIndex += 4) {
final int r = swapRedBlue ? pixels[pixelSourceIndex + 2] : pixels[pixelSourceIndex];
final int b = swapRedBlue ? pixels[pixelSourceIndex] : pixels[pixelSourceIndex + 2];
final int g = pixels[pixelSourceIndex + 1];
final int a = pixels[pixelSourceIndex + 3];
// Set the pixel past the header data.
bmpData.setUint8(pixelDestinationIndex + 0x36, r);
bmpData.setUint8(pixelDestinationIndex + 0x37, g);
bmpData.setUint8(pixelDestinationIndex + 0x38, b);
bmpData.setUint8(pixelDestinationIndex + 0x39, a);
pixelDestinationIndex += 4;
if (rowBytes != width && pixelSourceIndex % width == 0) {
pixelSourceIndex += rowBytes - width;
}
}
final Completer<Codec> codecCompleter = Completer<Codec>();
_instantiateImageCodec(
bmpData.buffer.asUint8List(),
(Codec codec) => codecCompleter.complete(codec),
);
return codecCompleter.future;
}
void decodeImageFromPixels(
Uint8List pixels,
@ -514,19 +576,29 @@ void decodeImageFromPixels(
int? targetHeight,
bool allowUpscaling = true,
}) {
final Future<Codec> codecFuture = _futurize((engine.Callback<Codec> callback) {
return _instantiateImageCodec(
if (engine.experimentalUseSkia) {
engine.skiaInstantiateImageCodec(
pixels,
callback,
width: width,
height: height,
format: format,
rowBytes: rowBytes,
(Codec codec) {
codec.getNextFrame().then((FrameInfo info) {
callback(info.image);
});
},
width,
height,
format.index,
rowBytes,
);
});
codecFuture
.then((Codec codec) => codec.getNextFrame())
.then((FrameInfo frameInfo) => callback(frameInfo.image));
return;
}
void Function(Codec) callbacker = (Codec codec) {
codec.getNextFrame().then((FrameInfo frameInfo) {
callback(frameInfo.image);
});
};
_createBmp(pixels, width, height, rowBytes ?? width, format).then(callbacker);
}
class Shadow {
@ -684,27 +756,19 @@ class ImageDescriptor {
int get bytesPerPixel =>
throw UnsupportedError('ImageDescriptor.bytesPerPixel is not supported on web.');
void dispose() => _data = null;
Future<Codec> instantiateCodec({int? targetWidth, int? targetHeight}) {
Future<Codec> instantiateCodec({int? targetWidth, int? targetHeight}) async {
if (_data == null) {
throw StateError('Object is disposed');
}
if (_width == null) {
return instantiateImageCodec(
return await instantiateImageCodec(
_data!,
targetWidth: targetWidth,
targetHeight: targetHeight,
allowUpscaling: false,
);
}
return _futurize((engine.Callback<Codec> callback) {
return _instantiateImageCodec(
_data!,
callback,
width: _width,
height: _height,
format: _format,
rowBytes: _rowBytes,
);
});
return await _createBmp(_data!, width, height, _rowBytes ?? width, _format!);
}
}

View File

@ -3,6 +3,9 @@
// found in the LICENSE file.
// @dart = 2.6
import 'dart:async';
import 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/ui.dart' as ui;
@ -15,6 +18,44 @@ void main() {
void testMain() async {
await ui.webOnlyInitializeTestDomRenderer();
group('HtmCodec', () {
test('supports raw images - RGBA8888', () async {
final Completer<ui.Image> completer = Completer<ui.Image>();
const int width = 200;
const int height = 300;
final Uint32List list = Uint32List(width * height);
for (int index = 0; index < list.length; index += 1) {
list[index] = 0xFF0000FF;
}
ui.decodeImageFromPixels(
list.buffer.asUint8List(),
width,
height,
ui.PixelFormat.rgba8888,
(ui.Image image) => completer.complete(image),
);
final ui.Image image = await completer.future;
expect(image.width, width);
expect(image.height, height);
});
test('supports raw images - BGRA8888', () async {
final Completer<ui.Image> completer = Completer<ui.Image>();
const int width = 200;
const int height = 300;
final Uint32List list = Uint32List(width * height);
for (int index = 0; index < list.length; index += 1) {
list[index] = 0xFF0000FF;
}
ui.decodeImageFromPixels(
list.buffer.asUint8List(),
width,
height,
ui.PixelFormat.bgra8888,
(ui.Image image) => completer.complete(image),
);
final ui.Image image = await completer.future;
expect(image.width, width);
expect(image.height, height);
});
test('loads sample image', () async {
final HtmlCodec codec = HtmlCodec('sample_image1.png');
final ui.FrameInfo frameInfo = await codec.getNextFrame();