Use ‘ui.decodeImageFromList’ to display an image created from a list of bytes

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;
  }

}

Leave a Comment