5

I've been search around for a while now on how to create a stateful image that can be created from an array of bytes (or simply a list of hex color values). I came across this seemingly useful function in the ui library for dart.

Can someone give an example of how it would be used? Can you decide how many bits per pixel?

Thanks in advance.

creativecreatorormaybenot
  • 114,516
  • 58
  • 291
  • 402
Daniel
  • 368
  • 1
  • 4
  • 6

2 Answers2

12

decodeImageFromList is just a convenience wrapper around instantiateImageCodec which decodes one of a handful of supported image formats (JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP). Somewhat surprisingly, there isn't yet a way to pass a raw bitmap. However, the BMP file format is basically a simple header tacked on the front of a bitmap (which can be in RGB, RGBA or use an indexed color map). The header fields tell the decoder things like width, height, bits per pixel, pixel format, etc

It's pretty easy to construct the BMP header in memory, append the bitmap and pass that to either of the above functions.

Here's a complete example. Note that the mapping from RGB332 to ARGB is a bit off. Really you should use a 256 member lookup table. The loop produces 256 approximate ARGB values for the 256 possible values of an RGB322 byte.

If you'd prefer to paint on a Canvas use instantiateImageCodec instead of the Image.memory Widget.

import 'dart:math';
import 'dart:typed_data';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'BMP Demo',
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Uint8List bmp;
  BMP332Header header;
  Random r = Random();

  @override
  void initState() {
    super.initState();
    header = BMP332Header(100, 100);
    bmp = header.appendBitmap(
        Uint8List.fromList(List<int>.generate(10000, (i) => r.nextInt(255))));
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Bitmap'),
      ),
      body: Center(
        child: Image.memory(bmp),
      ),
    );
  }
}

class BMP332Header {
  int _width; // NOTE: width must be multiple of 4 as no account is made for bitmap padding
  int _height;

  Uint8List _bmp;
  int _totalHeaderSize;

  BMP332Header(this._width, this._height) : assert(_width & 3 == 0) {
    int baseHeaderSize = 54;
    _totalHeaderSize = baseHeaderSize + 1024; // base + color map
    int fileLength = _totalHeaderSize + _width * _height; // header + bitmap
    _bmp = new Uint8List(fileLength);
    ByteData bd = _bmp.buffer.asByteData();
    bd.setUint8(0, 0x42);
    bd.setUint8(1, 0x4d);
    bd.setUint32(2, fileLength, Endian.little); // file length
    bd.setUint32(10, _totalHeaderSize, Endian.little); // start of the bitmap
    bd.setUint32(14, 40, Endian.little); // info header size
    bd.setUint32(18, _width, Endian.little);
    bd.setUint32(22, _height, Endian.little);
    bd.setUint16(26, 1, Endian.little); // planes
    bd.setUint32(28, 8, Endian.little); // bpp
    bd.setUint32(30, 0, Endian.little); // compression
    bd.setUint32(34, _width * _height, Endian.little); // bitmap size
    // leave everything else as zero

    // there are 256 possible variations of pixel
    // build the indexed color map that maps from packed byte to RGBA32
    // better still, create a lookup table see: http://unwind.se/bgr233/
    for (int rgb = 0; rgb < 256; rgb++) {
      int offset = baseHeaderSize + rgb * 4;

      int red = rgb & 0xe0;
      int green = rgb << 3 & 0xe0;
      int blue = rgb & 6 & 0xc0;

      bd.setUint8(offset + 3, 255); // A
      bd.setUint8(offset + 2, red); // R
      bd.setUint8(offset + 1, green); // G
      bd.setUint8(offset, blue); // B
    }
  }

  /// Insert the provided bitmap after the header and return the whole BMP
  Uint8List appendBitmap(Uint8List bitmap) {
    int size = _width * _height;
    assert(bitmap.length == size);
    _bmp.setRange(_totalHeaderSize, _totalHeaderSize + size, bitmap);
    return _bmp;
  }

}
Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • When you set the bytes in the ByteData object, is there any reason for this specific order of information? If so, where can I find the documentation to show all what goes where? Also, if I understand this correctly, this doesn't accept data from an outside source, but rather the data is the rgb loop, right? – Daniel Jul 17 '18 at 02:03
  • Search for `bmp header format` for the format, for example https://en.wikipedia.org/wiki/BMP_file_format . Yes it needs data from an outside source! The above code just makes the header (you only need to make the header once for a given value of width/height). You then need to append the byte array containing your bitmap (which must be width*height bytes long) to the header byte array. – Richard Heap Jul 17 '18 at 11:23
  • I updated the header class so that you just need to call `appendBitmap` and it returns you a complete valid BMP ready for `Image.memory` or `instantiateImageCodec`. Also shows its use in an example app. – Richard Heap Jul 18 '18 at 01:45
  • 1
    I'm trying to use this class in my own project to convert a ByteData object that is ui.ImageByteFormat.rawRgba format. The length of my byte data is four times longer than just the width*height. So four bytes per pixel. Is there any way to write this data to the BMP image type to display the image? – Grayson Harrington Feb 17 '21 at 17:53
  • Yes, set bpp to 32 and omit the colour map. Adjust the header and bitmap lengths accordingly. You may find it useful to hexdump the header of a working rgba bmp file to compare. – Richard Heap Feb 18 '21 at 02:30
  • 2
    Side note: The `dart:ui` function `decodeImageFromPixels` now makes creating a `dart:ui::Image` easier. No more need to prepend the BMP header. You still need to display the image (using a `CustomPainter` for example). – Jaween Apr 01 '21 at 01:22
0

Maybe helpful for flutter plugin I convert the bitmap in java from fingerprint device by:

ByteArrayOutputStream stream = new ByteArrayOutputStream();
fingerBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
fingerBitmap.recycle();

then in Flutter Dart I just use

Uint8List fingerImages;

  void mapFingerImage(dynamic imageBytes) {
    setState(() {
      fingerImages = imageBytes;
    });
  }
    Image.memory(fingerImages,
                  width: 100,
                  height: 100,
                   fit: BoxFit.contain,)
amorenew
  • 10,760
  • 10
  • 47
  • 69