0
votes

When I try to create a ARGB32 QImage from a reinterpret_cast<uchar*>(quint32*) using the QImage constructor the Image looses its color and alpha channel and the resulting QImage is grayscale!

The grayscale image is displayed as expected, if I was trying to display it in grayscale. So I know the scaling and indexing of ushort data to the quint32 array went well, but what is going wrong?

A Qt forum post suggested to do it the way I am doing it (as far as I can see), but maybe behavior has changed since that version of Qt? (I am Using Qt 5.9)

I realise that the documentation says:

data must be 32-bit aligned, and each scanline of data in the image must also be 32-bit aligned.

But I would expect quint32 to be 32-bit aligned even after reinterpret_cast<uchar*>()?

Now the details: I am converting the results of a calculation (an array with unsigned short values) to a semi-transparent blue-to-green-to-red image like this:

inline uchar val_to_blue(const double val) {
    if (val > 0.5)
        return 0;
    else if (val < 0.25)
        return 255;
    else // x={.5,...,.25}:a=255/(.25-.5)=-4*255 & b=-255*0.5/(0.25-0.5)=4/2*255=2*255 
        return (uchar)(val * -4.0 * 255.0) + 2 * 255;
}

inline uchar val_to_green(const double val) {
    if (val > 0.25 && val < 0.75)
        return 255;
    else if (val < 0.25)// x={0,...,.25}:a=255/(.25-0)=4*255 & b=-255*0/(0.25-0)=0 
        return (uchar)(val * 4.0 * 255.0);
    else // if (val > .75) // x={.75,...,1}:a=255/(.75-.5)=4*255 & b=-255*0.5/(0.75-0.5)=-4/2*255=-2*255 
        return (uchar)(val * -4.0 * 255.0) - 2 * 255;
}

inline uchar val_to_red(const double val) {
    if (val < 0.5)
        return 0;
    if (val > 0.75)
        return 255;
    else // x={0.5,...,0.75}:a=255/(0.75-0.5)=4*255 & b=-255*0.5/(0.75-0.5)=-4/2*255=-2*255 
        return (uchar)(val * 4.0 * 255.0) - 2 * 255;
}

inline QRgb val_to_rgba_scale(const double val) {
    return qRgba( // ax+b={0,...,255} for x={i,...,j}, a=255/(j-i), b= -255i/(j-i)
        val_to_blue(val),
        val_to_green(val),
        val_to_red(val),
        (uchar)(val * 81)
    );
}

Where val is a double between 0 and 1 scaled from the ushort data. Each QRgb value is stored at the corresponding index of a quint32 array, like this:

if (m_pData[i*m_iWidth + j] >= uppVal)
    tmpData[tmpIdx] = 0x45ff0000;
else if (m_pData[i*m_iWidth + j] <= lowVal)
    tmpData[tmpIdx] = 0x00000000;
else
    tmpData[tmpIdx] = val_to_rgba_scale((m_pData[i*m_iWidth + j] - lowVal) / (double)winWidth);

Where (m_pData[i*m_iWidth + j] - lowVal) / (double)winWidthis the ushort-to-double scaling method. This is done in a for loop.

Finally I attempt to construct the image with:

QImage tmpQImage = QImage(reinterpret_cast<unsigned char*>(tmpData), m_iWidth, m_iHeight, QImage::Format_ARGB32);

But this doesn't work as I expect, because tmpQImage.allGray() returns true when called immediately after!

What am I doing wrong, and what should I do instead to create a ARGB image and keep both the colors and alpha channel?

1
troubleshooting suggestions: Try to generate the image pixel by pixel with 2-dimensional for loop and "putpixel" method of QImage, and see if it comes out right then. If it is right, then compare the raw data in this correct image with data in the grayscale image. If it isn't right, then the problem must be in the original raw image data...hyde

1 Answers

2
votes

I tried to reproduce your problem but I couldn't.

Either the actual issue of the OP is not part of the presented code, or I accidentally missed a detail when I tried to form an MCVE from the OP.

However, I want to present what I got so far as this may be helpful to fix the OP.

My source testQImageGrayToRGB.cc:

#include <vector>

#include <QtWidgets>

typedef unsigned char uchar;

namespace AGA {

uchar val_to_blue(const double val) {
  if (val > 0.5)
    return 0;
  else if (val < 0.25)
    return 255;
  else // x={.5,...,.25}:a=255/(.25-.5)=-4*255 & b=-255*0.5/(0.25-0.5)=4/2*255=2*255 
    return (uchar)(val * -4.0 * 255.0) + 2 * 255;
}

uchar val_to_green(const double val) {
  if (val > 0.25 && val < 0.75)
    return 255;
  else if (val < 0.25)// x={0,...,.25}:a=255/(.25-0)=4*255 & b=-255*0/(0.25-0)=0 
    return (uchar)(val * 4.0 * 255.0);
  else // if (val > .75) // x={.75,...,1}:a=255/(.75-.5)=4*255 & b=-255*0.5/(0.75-0.5)=-4/2*255=-2*255 
    return (uchar)(val * -4.0 * 255.0) - 2 * 255;
}

uchar val_to_red(const double val) {
  if (val < 0.5)
    return 0;
  if (val > 0.75)
    return 255;
  else // x={0.5,...,0.75}:a=255/(0.75-0.5)=4*255 & b=-255*0.5/(0.75-0.5)=-4/2*255=-2*255 
    return (uchar)(val * 4.0 * 255.0) - 2 * 255;
}

} // namespace AGA

namespace DS {

uchar val_to_blue(const double val)
{
  return val < 0.25 ? 255
    : val < 0.5 ? (0.5 - val) * 4 * 255
    : 0;
}

uchar val_to_green(const double val)
{
  return val < 0.25 ? val * 4 * 255
    : val < 0.75 ? 255
    : (1.0 - val) * 4 * 255;
}

uchar val_to_red(const double val)
{
  return val < 0.5 ? 0
    : val < 0.75 ? (val - 0.5) * 4 * 255
    : 255;
}

} // namespace DS

std::vector<quint32> buildImageData(
  const int w, const int h,
  uchar (*pFuncValToR)(double),
  uchar (*pFuncValToG)(double),
  uchar (*pFuncValToB)(double))
{
  // make temp. buffer to build up raw image data
  std::vector<quint32> data(w * h);
  // fill raw image - make values 0 ... 1 in n steps
  const int n = w - 1;
  for (int x = 0; x < w; ++x) {
    const double v = (double)x / n;
    QRgb qRgb = qRgba(pFuncValToR(v), pFuncValToG(v), pFuncValToB(v), 255);
    for (int y = 0; y < h; ++y) data[y * w + x] = qRgb;
  }
  // done
  return data;
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version: " << QT_VERSION_STR;
  QApplication app(argc, argv);
  // build contents
  enum { w = 256, h = 32 };
  std::vector<quint32> dataAGA = buildImageData(w, h,
    &AGA::val_to_red, &AGA::val_to_green, &AGA::val_to_blue);
  QImage qImgAGA((const uchar*)dataAGA.data(), w, h, QImage::Format_ARGB32);
  std::vector<quint32> dataDS = buildImageData(w, h,
    &DS::val_to_red, &DS::val_to_green, &DS::val_to_blue);
  QImage qImgDS((const uchar*)dataDS.data(), w, h, QImage::Format_ARGB32);
  // build some GUI
  QWidget win;
  QVBoxLayout qVBox;
  QLabel qLblAGA(
    QString::fromUtf8("QImage (Functions of Andreas Gravgaard Andersen):"));
  qVBox.addWidget(&qLblAGA);
  QLabel qLblImgAGA;
  qLblImgAGA.setPixmap(QPixmap::fromImage(qImgAGA));
  qVBox.addWidget(&qLblImgAGA);
  QLabel qLblDS(
    QString::fromUtf8("QImage (Functions of Scheff):"));
  qVBox.addWidget(&qLblDS);
  QLabel qLblImgDS;
  qLblImgDS.setPixmap(QPixmap::fromImage(qImgDS));
  qVBox.addWidget(&qLblImgDS);
  win.setLayout(&qVBox);
  win.show();
  // exec. application
  return app.exec();
}

I compiled and tested it with VS2013, Qt5.6 on Windows 10 (64 bit):

Snapshot of testQImageGrayToRGB

Notes:

  1. The val_to_ functions made me a little bit suspicious: an expression casted to (uchar), then a constant term added (which definitely does not fit into (uchar), the result returned as uchar...
    Hmm...
    Therefore, I remade them – with a little bit clean-up.
    Actually, the visual comparison shows the differences are nearly invisible (with the only exception of the red line in the yellow region).

  2. I had no problems to make a QImage out of the raw quint32 array (including the cast-to-uchar*-hack).


Update:

May be, it is not obvious: The sample code is carefully designed to grant that life-time of buffer data (std::vector<quint32> dataAGA and std::vector<quint32> dataDS) is longer than the life-time of Qt images (QImage qImgAGA and QImage qImgDS). This has been done according to the Qt doc. for QImage::QImage():

The buffer must remain valid throughout the life of the QImage and all copies that have not been modified or otherwise detached from the original buffer. The image does not delete the buffer at destruction. You can provide a function pointer cleanupFunction along with an extra pointer cleanupInfo that will be called when the last copy is destroyed.

Image data may consume a significant amount of memory. Thus, the QImage implementation tries to prevent unnecessary copies (to safe memory space and time). Instead, the "user" (i.e. application developer) is responsible to ensure proper storage of image data.