2
votes

Creating a QBitmap from raw data stored in the XBM format, can easily be done like so QBitmap::fromData(width, height, data, QImage::Format_MonoLSB), with data being the raw data of the XBM file.

Now, what if we want to create a QImage this time? Indeed, say we want to create a QImage from the same XBM raw data. One additional constraint though: no QBitmap should be used here, meaning converting a QBitmap to get a QImage would not be an acceptable solution.

I tried QImage(data, width, height, QImage::Format_MonoLSB) but it doesn't produce the proper image since QImage expects bits to be 32-bits aligned, cf. Qt's 4.8 documentation:

QImage::QImage(uchar * data, int width, int height, Format format)
Constructs an image with the given width, height and format, that uses an existing memory buffer, data. The width and height must be specified in pixels, data must be 32-bit aligned, and each scanline of data in the image must also be 32-bit aligned.

The issue is the XBM is 1-bit aligned (not 32-bits aligned), therefore the QImage produced by QImage(data, width, height, QImage::Format_MonoLSB) is garbage (undefined behavior, "random" image).

So, how to create a QImage from the raw data of an XBM?


EDIT: Here's an XBM file with which a QImage should constructed:

#define x_width 66
#define x_height 27
static char x_bits[] = {
   0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, ...
};
2
QImage image; image.loadFromData(data, dataLen, "xbm"); ?peppe
The third parameter is the string "xbm", or nothing if you want Qt to autodetect (which I'm not sure it would happen given that XBM has no headers, IIRC). In other words it's the format of the image data.peppe
@KubaOber yes, indeed, I must have mistaken with the param in QImage's constructor. Anyways, even when passing "XBM" as third param, it doesn't work, because loadFromData expects to recieve the actual content of the XBM file, whereas I can only provide the data inside the static char x_bits array (cf. EDIT).gpalex

2 Answers

1
votes

Here is one implementation. It is thread-safe and you could distribute the construction of such images over multiple threads, if there's very many of them. It processes 32 bits of output at the time.

It is necessary to pass the number of available bytes to fromLittleEndian to avoid reading past the end of the input data.

screenshot

#include <QtWidgets>
#include <cstdint>

inline bool isAlignedTo(const void * p, int bits) {
  return (reinterpret_cast<uintptr_t>(p) & ((bits/8)-1)) == 0;
}

inline uint32_t fromLittleEndian(const void *p, int bytes) {
  Q_ASSERT(bytes > 0);
  uint32_t val;
  if (bytes >= 4) {
    val = *reinterpret_cast<const uint32_t*>(p);
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
    val = val >> 24 | (val >> 8 & 0xFF00U) | (val << 8 & 0xFF0000U) | val << 24;
#endif
  } else {
    auto pp = reinterpret_cast<const uint8_t*>(p);
    val = *pp++;
    if (bytes > 1) val |= static_cast<uint32_t>(*pp++) << 8;
    if (bytes > 2) val |= static_cast<uint32_t>(*pp) << 16;
  }
  return val;
}

QImage fromXBM(const uchar * data, int width, int height,
               QImageCleanupFunction cleanup = 0, void * cleanupData = 0) {
  Q_ASSERT(width && height);
  if ((width & 31) == 0 && isAlignedTo(data, 32)) // 32-bit aligned
    return QImage(data, width, height, QImage::Format_MonoLSB, cleanup, cleanupData);
  if ((width & 7) == 0) // 8-bit aligned
    return QImage(data, width, height, width/8, QImage::Format_MonoLSB, cleanup, cleanupData);

  // Note: Source pixels are stored LSB first, MSB last.
  QImage img(width, height, QImage::Format_MonoLSB);
  Q_ASSERT((img.bytesPerLine() & 0x3) == 0); // must be 32-bit aligned
  uint32_t * dst = reinterpret_cast<uint32_t*>(img.bits());
  const int wordsPerRow = width / 32 + ((width & 31) ? 1 : 0);
  int shift = 0;
  int bytesLeft = (width * height)/8 + ((width * height & 7) ? 1 : 0);
  uint32_t src = fromLittleEndian(data, bytesLeft);
  bytesLeft -= 4;
  data += 4;
  while (height--) {
    QList<uint32_t> w;
    int widthLeft = width;
    for (int c = 0; c < wordsPerRow; ++c) {
      Q_ASSERT(widthLeft > 0);
      uint32_t word = src >> shift;
      if (32-shift < widthLeft) {
        src = fromLittleEndian(data, bytesLeft);
        bytesLeft -= 4;
        data += 4;
        if (shift) word |= src << (32-shift);
      }
      widthLeft -= 32;
      *dst++ = word;
    }
    shift = (shift + width) & 31;
  }
  // Dispose of the data since we've made a copy
  if (cleanup) cleanup(cleanupData);
  return img;
}

// w = 5 h = 5
// 1...1   0x11,3
// .1.1.   0x40 0x01,6
// ..1..        0x10,1
// .1.1.        0x00 0x05,4
// 1...1             0x10  0x01,7
//         0x51 0x11 0x15  0x01

const int X_w = 5, X_h = 5;
const uchar X_bits[] = { 0x51, 0x11, 0x15, 0x01 };

// 17 iterations of rule 30 cellular automaton, see
// Stephen Wolfram, The New Kind Of Science
// from https://lost-contact.mit.edu/afs/hep.wisc.edu/apps/Mathematica-7.0/Documentation/English/System/ExampleData/
const int rule30_w = 40, rule30_h = 17;
const uchar rule30_bits[] = {
 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0xc0,
 0x04, 0x00, 0x00, 0x00, 0x60, 0x0f, 0x00, 0x00, 0x00, 0x30, 0x11, 0x00,
 0x00, 0x00, 0xd8, 0x3b, 0x00, 0x00, 0x00, 0x4c, 0x48, 0x00, 0x00, 0x00,
 0xf6, 0xfc, 0x00, 0x00, 0x00, 0x13, 0x07, 0x01, 0x00, 0x80, 0xbd, 0x89,
 0x03, 0x00, 0xc0, 0x84, 0xde, 0x04, 0x00, 0x60, 0xcf, 0x42, 0x0f, 0x00,
 0x30, 0x71, 0x66, 0x11, 0x00, 0xd8, 0x9b, 0x3b, 0x3b, 0x00, 0x4c, 0xe8,
 0xc8, 0x49, 0x00, 0xf6, 0x2c, 0x7d, 0xfe, 0x00, 0x13, 0xe7, 0x85, 0x03,
 0x01
};

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  auto X = fromXBM(X_bits, X_w, X_h);
  X.invertPixels();
  auto bigX = X.scaled(X.size() * 4);
  auto rule30 = fromXBM(rule30_bits, rule30_w, rule30_h);
  rule30.invertPixels();
  auto big30 = rule30.scaled(rule30.size()*2);
  QWidget w;
  QHBoxLayout layout(&w);
  QLabel labelX, label30;
  layout.addWidget(&labelX);
  layout.addWidget(&label30);
  labelX.setPixmap(QPixmap::fromImage(bigX));
  label30.setPixmap(QPixmap::fromImage(big30));
  w.show();
  return a.exec();
}
1
votes

Ok, so it turns out that Qt alreay has implemented a simple code that builds a QImage from the raw data of an XBM:

QImage image(size, monoFormat);
image.setColor(0, QColor(Qt::color0).rgb());
image.setColor(1, QColor(Qt::color1).rgb());

// Need to memcpy each line separatly since QImage is 32bit aligned and
// this data is only byte aligned...
int bytesPerLine = (size.width() + 7) / 8;
for (int y = 0; y < size.height(); ++y)
{
   memcpy(image.scanLine(y), bits + bytesPerLine * y, bytesPerLine);
}

This code is used in qbitmap.cpp, more specifically in:
QBitmap::fromData(const QSize &size, const uchar *bits, QImage::Format monoFormat)