Skip to content

Commit

Permalink
fix: Deprecate Images.decodeImageFromPixels (#1318)
Browse files Browse the repository at this point in the history
- Images.decodeImageFromPixels is marked.
- A simplified version is available as ImageExtension.fromPixels.
  • Loading branch information
st-pasha committed Jan 15, 2022
1 parent 825fb0c commit 1a80130
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 117 deletions.
16 changes: 2 additions & 14 deletions doc/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ with that key was not previously loaded, it will throw an exception.
To add an already loaded image to the cache, the `add` method can be used and you can set the key
that the image should have in the cache.

You can also use `ImageExtension.fromPixels()` to dynamically create an image during the game.

For `clear` and `clearCache`, do note that `dispose` is called for each removed image from the
cache, so make sure that you don't use the image afterwards.

Expand Down Expand Up @@ -385,17 +387,3 @@ spritesheet.getSprite(0, 0) // row, column;

You can see a full example of the `SpriteSheet` class
[here](https://github.com/flame-engine/flame/tree/main/examples/lib/stories/sprites/spritesheet.dart).

## `Flame.images.decodeImageFromPixels()`

The [dart-ui decodeImageFromPixels](https://api.flutter.dev/flutter/dart-ui/decodeImageFromPixels.html)
currently does not support the web platform. So if you are looking for a way to manipulate pixel
data on the web this method can be used as a replacement for `dart-ui decodeImageFromPixels`:

```dart
Image image = await Flame.images.decodeImageFromPixels(
data, // A Uint8List containing pixel data in the RGBA format.
200,
200,
);
```
70 changes: 21 additions & 49 deletions packages/flame/lib/src/assets/images.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,28 @@ import 'package:flutter/services.dart';
import '../flame.dart';

class Images {
Images({this.prefix = 'assets/images/'})
: assert(prefix.isEmpty || prefix.endsWith('/'));

final String prefix;
final Map<String, _ImageAssetLoader> _loadedFiles = {};

Images({this.prefix = 'assets/images/'});

/// Adds an [image] to the cache that can be fetched with with the specified
/// [name].
/// Adds an [image] into the cache under the key [name].
void add(String name, Image image) {
_loadedFiles[name] = _ImageAssetLoader(Future.value(image))
..loadedImage = image;
}

/// Remove the image with the specified [name] from the cache.
/// Remove the image [name] from the cache.
///
/// This calls [Image.dispose], so make sure that you don't use the previously
/// cached image once it is cleared (removed) from the cache.
void clear(String name) {
_loadedFiles.remove(name)?.loadedImage?.dispose();
}

/// Clear all cached images.
/// Remove all cached images.
///
/// This calls [Image.dispose] for all images in the cache, so make sure that
/// you don't use any of the previously cached images once [clearCache] has
/// been called.
Expand All @@ -40,12 +42,12 @@ class Images {
_loadedFiles.clear();
}

/// Gets the specified image with [name] from the cache.
/// Gets the specified image [name] from the cache.
Image fromCache(String name) {
final image = _loadedFiles[name];
assert(
image?.loadedImage != null,
'Tried to access an inexistent entry on cache "$name", make sure to '
'Tried to access a nonexistent entry on cache "$name", make sure to '
'use the load method before accessing a file on the cache',
);
return image!.loadedImage!;
Expand Down Expand Up @@ -94,25 +96,23 @@ class Images {
/// If you want the image to be decoded as it would be on the web you can set
/// [runAsWeb] to `true`. Keep in mind that it is slightly slower than the
/// native [ui.decodeImageFromPixels]. By default it is set to [kIsWeb].
@Deprecated(
'Use Image.fromPixels() instead. This function will be removed in 1.1.0',
)
Future<Image> decodeImageFromPixels(
Uint8List pixels,
int width,
int height, {
bool runAsWeb = kIsWeb,
bool runAsWeb = false,
}) {
final completer = Completer<Image>();
if (runAsWeb) {
completer.complete(_createBmp(pixels, width, height));
} else {
ui.decodeImageFromPixels(
pixels,
width,
height,
PixelFormat.rgba8888,
completer.complete,
);
}

ui.decodeImageFromPixels(
pixels,
width,
height,
PixelFormat.rgba8888,
completer.complete,
);
return completer.future;
}

Expand Down Expand Up @@ -140,34 +140,6 @@ class Images {
decodeImageFromList(bytes, completer.complete);
return completer.future;
}

Future<Image> _createBmp(Uint8List pixels, int width, int height) async {
final size = (width * height * 4) + 122;
final bmp = Uint8List(size);
bmp.buffer.asByteData()
..setUint8(0x0, 0x42)
..setUint8(0x1, 0x4d)
..setInt32(0x2, size, Endian.little)
..setInt32(0xa, 122, Endian.little)
..setUint32(0xe, 108, Endian.little)
..setUint32(0x12, width, Endian.little)
..setUint32(0x16, -height, Endian.little)
..setUint16(0x1a, 1, Endian.little)
..setUint32(0x1c, 32, Endian.little)
..setUint32(0x1e, 3, Endian.little)
..setUint32(0x22, width * height * 4, Endian.little)
..setUint32(0x36, 0x000000ff, Endian.little)
..setUint32(0x3a, 0x0000ff00, Endian.little)
..setUint32(0x3e, 0x00ff0000, Endian.little)
..setUint32(0x42, 0xff000000, Endian.little);

bmp.setRange(122, size, pixels);

final codec = await instantiateImageCodec(bmp);
final frame = await codec.getNextFrame();

return frame.image;
}
}

class _ImageAssetLoader {
Expand Down
30 changes: 24 additions & 6 deletions packages/flame/lib/src/extensions/image.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';

import '../flame.dart';
import 'color.dart';
import 'vector2.dart';

export 'dart:ui' show Image;

extension ImageExtension on Image {
/// Helper method for retrieve the pixel data in a Uint8 format.
/// Converts a raw list of pixel values into an [Image] object.
///
/// The pixels must be in the RGBA format, i.e. first 4 bytes encode the red,
/// green, blue, and alpha components of the first pixel, next 4 bytes encode
/// the next pixel, and so on. The pixels are in the row-major order, meaning
/// that first [width] pixels encode the first row of the image, next [width]
/// pixels the second row, and so on.
static Future<Image> fromPixels(Uint8List pixels, int width, int height) {
assert(pixels.length == width * height * 4);
final completer = Completer<Image>();
decodeImageFromPixels(
pixels,
width,
height,
PixelFormat.rgba8888,
completer.complete,
);
return completer.future;
}

/// Helper method to retrieve the pixel data of the image as a [Uint8List].
///
/// Pixel order used the [ImageByteFormat.rawRgba] meaning it is: R G B A.
Future<Uint8List> pixelsInUint8() async {
Expand Down Expand Up @@ -43,8 +63,7 @@ extension ImageExtension on Image {
newPixelData[i + 2] = color.blue;
newPixelData[i + 3] = color.alpha;
}

return Flame.images.decodeImageFromPixels(newPixelData, width, height);
return fromPixels(newPixelData, width, height);
}

/// Change each pixel's color to be brighter and return a new [Image].
Expand All @@ -69,7 +88,6 @@ extension ImageExtension on Image {
newPixelData[i + 2] = color.blue;
newPixelData[i + 3] = color.alpha;
}

return Flame.images.decodeImageFromPixels(newPixelData, width, height);
return fromPixels(newPixelData, width, height);
}
}
24 changes: 24 additions & 0 deletions packages/flame/test/extensions/image_extension_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'dart:typed_data';

import 'package:flame/extensions.dart';
import 'package:test/test.dart';

final output = List.filled(8 * 8 * 4, 255);

void main() {
group('ImageExtension', () {
test('fromPixels', () async {
final data = Uint8List(8 * 8 * 4);
for (var i = 0; i < data.length; i += 4) {
data[i] = 255;
data[i + 1] = 255;
data[i + 2] = 255;
data[i + 3] = 255;
}
final image = await ImageExtension.fromPixels(data, 8, 8);
final bytes = await image.toByteData();

expect(bytes!.buffer.asUint8List(), equals(output));
});
});
}
48 changes: 0 additions & 48 deletions packages/flame/test/images_test.dart

This file was deleted.

0 comments on commit 1a80130

Please sign in to comment.