从 XBM 创建 QImage

Creating a QImage from an XBM

从以 XBM 格式存储的原始数据创建 QBitmap,可以像这样轻松完成 QBitmap::fromData(width, height, data, QImage::Format_MonoLSB), 其中 data 为 XBM 文件的原始数据。

现在,如果我们这次想创建一个 QImage 怎么办? 事实上,假设我们想从同一个 XBM raw 创建一个 QImage数据。 但是还有一个额外的约束:这里不应该使用 QBitmap,这意味着将 QBitmap 转换为 QImage 将不是一个可接受的解决方案。

我试过了QImage(data, width, height, QImage::Format_MonoLSB) 但它不会产生正确的图像,因为 QImage 期望位是 32 位对齐的,请参见。 Qt 的 4.8 文档:

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.

问题是 XBM 是 1 位对齐的(不是 32 位对齐的),因此 QImage(data, width, height, QImage::Format_MonoLSB) 生成的 QImage 是垃圾(未定义行为,"random" 图像) .

那么,如何从 XBM 的原始 data 创建 QImage


编辑: 这是一个 XBM 文件,应该用它来构造 QImage:

#define x_width 66
#define x_height 27
static char x_bits[] = {
   0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, ...
};

这是一种实现方式。它是线程安全的,如果有很多线程,您可以将此类图像的构造分布在多个线程上。它当时处理 32 位输出。

有必要将可用字节数传递给fromLittleEndian以避免读取超过输入数据的末尾。

#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();
}

好的,事实证明 Qt 已经实现了一个简单的代码,可以从 XBM:

的原始数据构建 QImage
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);
}

此代码用于 qbitmap.cpp,更具体地说:
QBitmap::fromData(const QSize &size, const uchar *bits, QImage::Format monoFormat)