When modifying a QImage using scanLine, artifacts show up. Why? - c++

My application has different color modes. Therefore, I need to render my icons in different colors.
My icons are grayscale SVGs. Each color mode defines two colors for icons. One color shall replace black, the other shall replace white.
The code I use to implement this is as follows:
struct Gradient {
QRgb a, b;
/// index in [0, 255]. alpha from mask modifies dest alpha.
QRgb at(int index, int alpha) const {
const int rindex = 255 - index;
return qRgba(
(qRed(a) * rindex + qRed(b) * index) / 255,
(qGreen(a) * rindex + qGreen(b) * index) / 255,
(qBlue(a) * rindex + qBlue(b) * index) / 255,
(((qAlpha(a) * rindex + qAlpha(b) * index) / 255) * alpha) / 255
);
}
};
class Icon {
public:
explicit Icon(const QString &path): path{path} {}
/// must set once before rendering pixmaps.
/// generates a mask image from the source SVG with the given size.
void setSize(QSize size) {
if (mask.size() != size) {
QSvgRenderer renderer(path);
if (renderer.isValid()) {
mask = QImage(size, QImage::Format_ARGB32_Premultiplied);
mask.fill(Qt::transparent);
QPainter svgPainter(&mask);
renderer.render(&svgPainter);
}
}
}
/// renders a pixmap that uses primary where the mask is black,
/// secondary where the mask is white. Mask defines transparency.
QPixmap paint(const QColor &primary, const QColor &secondary) const {
QImage buffer(mask.size(), QImage::Format_ARGB32_Premultiplied);
Gradient gradient{primary.rgb(), secondary.rgb()};
for (int y = 0; y < mask.height(); ++y) {
const QRgb *srcLine = reinterpret_cast<const QRgb*>(mask.constScanLine(y));
QRgb *destLine = reinterpret_cast<QRgb*>(buffer.scanLine(y));
for (int x = 0; x < mask.width(); ++x) {
const QRgb &src = srcLine[x];
// using red as indicator for the grayscale color.
destLine[x] = gradient.at(qRed(src), qAlpha(src));
}
}
QPixmap pixmap;
pixmap.convertFromImage(buffer);
return pixmap;
}
bool isNull() const {
return mask.isNull();
}
private:
QString path;
QImage mask;
};
This code generates strange artifacts in the output:
From this input:
This is rendered with primary and secondary colors both #657590. The dark blue is the backing widget's background color. The original icon just has the cog outline.
Why are the additional rectangular parts created? They are not part of the source image. I tried to use buffer.setPixel instead of scanLine, but that produced the same output.

The problem was the image format QImage::Format_ARGB32_Premultiplied. The way I modified the image, QImage::Format_ARGB32 was appropriate. Using that format solved the problem.

Related

QImage 16 bit grayscale with QQuickPaintedItem

I have unsigned 16 bit image data that I displayed by subclassing QQuickPaintedItem in Qt 5.12.3. I used QImage with Format_RGB32 and scaled the data from [0, 16383] to [0, 255] and set that as the color value for all three R,G,B. Now, I am using Qt 5.15.2 which has a QImage FORMAT_GrayScale16 that I'd like to use but for some reason the image is displayed incorrectly. The code I used to convert my unsigned 16 bit image data to QImage for both formats is shown below. The QQuickPaintedItem is a basic subclassing with drawImage(window(), my_qimage); that I pass the QImage as returned from the code below. Why is the new format not displaying correctly?
Format_RGB32 method
QImage image(image_dim, QImage::Format_RGB32);
unsigned int pixel = 0;
for (uint16_t row = 0; row < nrow; row++) {
uint *scanLine = reinterpret_cast<uint *>(image.scanLine(row));
for (uint16_t col = 0; col < ncols; col++) {
uint16_t value = xray_image.data()[pixel++]; // Get each pixel
unsigned short color_value = uint16_t((float(value) / 16383) * 255.0f); // scale [0, 255]
*scanLine++ = qRgb(int(color_value), int(color_value), int(color_value));
}
}
return image;
Format_Grayscale16 method
QImage image(image_dim, QImage::Format_Grayscale16);
unsigned int pixel = 0;
for (uint16_t row = 0; row < nrow; row++) {
// **EDIT WRONG:** uint *scanLine = reinterpret_cast<uint *>(image.scanLine(row));
uint16_t *scanLine = reinterpret_cast<uint16_t *>(image.scanLine(row));
for (uint16_t col = 0; col < ncols; col++) {
*scanLine++ = xray_image.data()[pixel++]; // Get each pixel
}
}
return image;
This worked for me (below is just fragments):
class Widget : public QWidget
{
private:
QImage m_image;
QImage m_newImage;
QGraphicsScene *m_scene;
QPixmap m_pixmap;
};
...
m_image.load("your/file/path/here");
m_newImage = m_image.convertToFormat(QImage::Format_Grayscale8);
m_pixmap.convertFromImage(m_newImage);
m_scene = new QGraphicsScene(this);
m_scene->addPixmap(m_pixmap);
m_scene->setSceneRect(m_pixmap.rect());
ui->graphicsView->setScene(m_scene);
Based on your OP, you probably want to render it differently. graphicsView is just a QGraphicsView defined in design mode.

How to automate isolating line art from the background with code. Is there a way?

This is what I like to do with code. Nothing is manually done in the process with Photoshop, so I think there is a way? but can't quite figure it out.
This is what I did in Python:
from PIL import Image
im_rgb = Image.open('lena.jpg')
im_a = Image.open('frame.png').convert('L').resize(im_rgb.size)
im_rgba = im_rgb.copy()
im_rgba.putalpha(im_a)
im_rgba.save('final.png')
but I'm looking for a solution in Java/Kotlin on Android Studio while I could live with a sample in Dart or C++ as well.
from PIL import Image
im_rgb = Image.open('lena.jpg')
im_a = Image.open('frame.png').convert('L').resize(im_rgb.size)
im_rgba = im_rgb.copy()
im_rgba.putalpha(im_a)
im_rgba.save('final.png')
`
I figured it out by myself on Python. but it is not really as complete as I wanted it to be initially. I would still like to know how to do it with Java/Kotlin on Android Studio or with C++ or Dart.
What OP described, I know from GIMP where it is called Color to Alpha.
While I used that command from time to time I tried to imagine how this could be implemented.
Multiple approaches come into my mind:
a very simple: compare every pixel with a pivot color and set alpha to 0 in case of match
a threshold-based: determine the Euclidean distance of pixel to pivot color in RGB space (as 3D space) and set alpha according to distance when under a given threshold
threshold-based in HSV space: the similar approach like above but applied to HSV space (for better color matching).
Out of curiosity, I wrote a sample application to try this out.
First a C++ code for color to alpha transformation:
imageColorToAlpha.h:
#ifndef IMAGE_COLOR_TO_ALPHA_H
#define IMAGE_COLOR_TO_ALPHA_H
// standard C++ header:
#include <cstdint>
#include <functional>
// convenience types
typedef std::uint32_t Pixel;
typedef std::uint8_t Comp;
// convenience constants
const int ShiftR = 16;
const int ShiftG = 8;
const int ShiftB = 0;
const int ShiftA = 24;
const Pixel MaskR = 0xff << ShiftR;
const Pixel MaskG = 0xff << ShiftG;
const Pixel MaskB = 0xff << ShiftB;
const Pixel MaskA = 0xff << ShiftA;
const Pixel MaskRGB = MaskR | MaskG | MaskB;
// convenience functions
inline Comp getR(Pixel pixel) { return Comp(pixel >> ShiftR); }
inline Comp getG(Pixel pixel) { return Comp(pixel >> ShiftG); }
inline Comp getB(Pixel pixel) { return Comp(pixel >> ShiftB); }
inline Comp getA(Pixel pixel) { return Comp(pixel >> ShiftA); }
inline void setR(Pixel &pixel, Comp r)
{
pixel &= ~MaskR;
pixel |= r << ShiftR;
}
inline void setG(Pixel &pixel, Comp g)
{
pixel &= ~MaskG;
pixel |= g << ShiftG;
}
inline void setB(Pixel &pixel, Comp b)
{
pixel &= ~MaskB;
pixel |= b << ShiftB;
}
inline void setA(Pixel &pixel, Comp r)
{
pixel &= ~MaskA;
pixel |= r << ShiftA;
}
inline void set(Pixel &pixel, Comp r, Comp g, Comp b)
{
pixel &= ~MaskRGB;
pixel |= r << ShiftR | g << ShiftG | b << ShiftB;
}
inline void set(Pixel &pixel, Comp r, Comp g, Comp b, Comp a)
{
pixel = r << ShiftR | g << ShiftG | b << ShiftB | a << ShiftA;
}
extern void transformImage(
size_t w, size_t h, // width and height
size_t bytesPerRow, // bytes per row (to handle row alignment)
const Pixel *imgSrc, // source image
Pixel *imgDst, // destination image
std::function<Pixel(Pixel)> transform);
// color to alpha (very simple)
extern Pixel colorToAlpha(Pixel pixel, Pixel color);
// color to alpha (with threshold)
extern Pixel colorToAlpha(
Pixel pixel, Pixel color, unsigned threshold);
// convenience functions for image
inline void colorToAlphaSimple(
size_t w, size_t h, // width and height
size_t bytesPerRow, // bytes per row (to handle row alignment)
const Pixel *imgSrc, // source image
Pixel *imgDst, // destination image
Pixel color) // pivot color
{
transformImage(w, h, bytesPerRow, imgSrc, imgDst,
[color](Pixel pixel) { return colorToAlpha(pixel, color); });
}
inline void colorToAlphaThreshold(
size_t w, size_t h, // width and height
size_t bytesPerRow, // bytes per row (to handle row alignment)
const Pixel *imgSrc, // source image
Pixel *imgDst, // destination image
Pixel color, // pivot color
unsigned threshold) // threshold
{
transformImage(w, h, bytesPerRow, imgSrc, imgDst,
[color, threshold](Pixel pixel) {
return colorToAlpha(pixel, color, threshold);
});
}
inline void fillRGB(
size_t w, size_t h, // width and height
size_t bytesPerRow, // bytes per row (to handle row alignment)
Pixel *img, // image to modify
Pixel color) // fill color (alpha ignored)
{
color &= MaskRGB;
transformImage(w, h, bytesPerRow, img, img,
[color](Pixel pixel) {
pixel &= ~MaskRGB; pixel |= color; return pixel;
});
}
#endif // IMAGE_COLOR_TO_ALPHA_H
and the corresponding imageColorToAlpha.cc:
// standard C++ header:
#include <cmath>
// header of this module:
#include "imageColorToAlpha.h"
void transformImage(
size_t w, size_t h, // width and height
size_t bytesPerRow, // bytes per row (to handle row alignment)
const Pixel *imgSrc, // source image
Pixel *imgDst, // destination image
std::function<Pixel(Pixel)> transform)
{
for (size_t y = 0; y < h; ++y) {
const Pixel *pixelSrc = (const Pixel*)((const Comp*)imgSrc + y * bytesPerRow);
Pixel *pixelDst = (Pixel*)((Comp*)imgDst + y * bytesPerRow);
for (size_t x = 0; x < w; ++x) pixelDst[x] = transform(pixelSrc[x]);
}
}
Pixel colorToAlpha(Pixel pixel, Pixel color)
{
// eliminate current alpha values from pixel and color
pixel &= MaskRGB; color &= MaskRGB;
// compare pixel with color
const int match = pixel == color;
// set alpha according to match of pixel and color
setA(pixel, ~(match * 0xff));
// done
return pixel;
}
Pixel colorToAlpha(Pixel pixel, Pixel color, unsigned threshold)
{
// delta values per component
const int dR = (int)getR(pixel) - (int)getR(color);
const int dG = (int)getG(pixel) - (int)getG(color);
const int dB = (int)getB(pixel) - (int)getB(color);
// square Euclidean distance
const unsigned dSqr = dR * dR + dG * dG + dB * dB;
// compute alpha
Comp a = 0xff;
if (dSqr < threshold * threshold) { // distance below threshold?
// compute alpha weighted by distance
const double d = sqrt((double)dSqr);
const double f = d / threshold;
a = (Comp)(f * 0xff);
}
// done
setA(pixel, a);
return pixel;
}
This image manipulation code is based on the C++ std library only.
This is intended to make the code as exemplary and re-usable as possible.
However, the code for decoding image file formats is often neither short nor simple.
Hence, I wrote a wrapper application in Qt to show this in action.
Qt provides image support as well as the frame work for a desktop application and seemed to me as most appropriate for this task (beside of the fact that I've some experience with it).
The Qt wrapper application testQImageColorToAlpha.cc:
// Qt header:
#include <QtWidgets>
// own header:
#include "imageColorToAlpha.h"
#include "qColorButton.h"
// convenience functions
QPixmap fromImage(const QImage &qImg)
{
QPixmap qPixmap;
qPixmap.convertFromImage(qImg);
return qPixmap;
}
QPixmap fromAlphaImage(
const QImage &qImg,
QColor qColor1 = Qt::darkGray,
QColor qColor2 = Qt::gray,
int whCell = 32)
{
QPixmap qPixmap(qImg.width(), qImg.height());
{ QPainter qPainter(&qPixmap);
// draw chessboard
qPixmap.fill(qColor1);
for (int y = 0; y < qImg.height(); y += 2 * whCell) {
for (int x = 0; x < qImg.width(); x += 2 * whCell) {
qPainter.fillRect(x, y, whCell, whCell, qColor2);
qPainter.fillRect(x + whCell, y + whCell, whCell, whCell, qColor2);
}
}
// overlay with image
qPainter.drawImage(0, 0, qImg);
} // close Qt painter
// done
return qPixmap;
}
enum {
SingleValue,
RGBRange
};
QImage colorToAlphaSimple(
const QImage &qImgSrc, QColor qColor,
bool fill, QColor qColorFill)
{
// ensure expected format for input image
QImage qImg = qImgSrc.convertToFormat(QImage::Format_ARGB32);
const int w = qImg.width(), h = qImg.height(), bpr = qImg.bytesPerLine();
// allocate storage for output image
QImage qImgDst(w, h, QImage::Format_ARGB32);
colorToAlphaSimple(w, h, bpr,
(const Pixel*)qImg.constBits(), (Pixel*)qImgDst.bits(), qColor.rgba());
// override RGB if required
if (fill) fillRGB(w, h, bpr, (Pixel*)qImgDst.bits(), qColorFill.rgba());
// done
return qImgDst;
}
QImage colorToAlphaThreshold(
const QImage &qImgSrc, QColor qColor, unsigned threshold,
bool fill, QColor qColorFill)
{
// ensure expected format for input image
QImage qImg = qImgSrc.convertToFormat(QImage::Format_ARGB32);
const int w = qImg.width(), h = qImg.height(), bpr = qImg.bytesPerLine();
// allocate storage for output image
QImage qImgDst(w, h, QImage::Format_ARGB32);
colorToAlphaThreshold(w, h, bpr,
(const Pixel*)qImg.constBits(), (Pixel*)qImgDst.bits(), qColor.rgba(), threshold);
// override RGB if required
if (fill) fillRGB(w, h, bpr, (Pixel*)qImgDst.bits(), qColorFill.rgba());
// done
return qImgDst;
}
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup data
QImage qImgIn("cat.drawn.png");
QImage qImgOut(qImgIn);
// setup GUI
// main window
QWidget qWin;
qWin.setWindowTitle(QString::fromUtf8("Color to Alpha"));
QGridLayout qGrid;
// input image
QHBoxLayout qHBoxLblIn;
QLabel qLblIn(QString::fromUtf8("Input Image:"));
qHBoxLblIn.addWidget(&qLblIn);
qHBoxLblIn.addStretch(1);
QPushButton qBtnLoad(QString::fromUtf8("Open..."));
qHBoxLblIn.addWidget(&qBtnLoad);
qGrid.addLayout(&qHBoxLblIn, 0, 0);
QLabel qLblImgIn;
qLblImgIn.setPixmap(fromImage(qImgIn));
qGrid.addWidget(&qLblImgIn, 1, 0);
// config. color to alpha
QGroupBox qBoxCfg(QString::fromUtf8("Configuration:"));
QFormLayout qFormCfg;
QComboBox qMenuColorToAlpha;
qMenuColorToAlpha.addItem(QString::fromUtf8("Single Value"));
qMenuColorToAlpha.addItem(QString::fromUtf8("With Threshold"));
qFormCfg.addRow(QString::fromUtf8("Color to Transparency:"), &qMenuColorToAlpha);
QColorButton qBtnColor(Qt::white);
qFormCfg.addRow(QString::fromUtf8("Pivot Color:"), &qBtnColor);
qBoxCfg.setLayout(&qFormCfg);
QSpinBox qEditRange;
qEditRange.setRange(1, 255);
qFormCfg.addRow(QString::fromUtf8("Range:"), &qEditRange);
QFrame qHSepCfg;
qHSepCfg.setFrameStyle(QFrame::HLine | QFrame::Plain);
qFormCfg.addRow(&qHSepCfg);
QHBoxLayout qHBoxFill;
QCheckBox qTglFill;
qTglFill.setChecked(false);
qHBoxFill.addWidget(&qTglFill);
QColorButton qBtnColorFill(Qt::black);
qHBoxFill.addWidget(&qBtnColorFill, 1);
qFormCfg.addRow(QString::fromUtf8("Fill Color:"), &qHBoxFill);
qGrid.addWidget(&qBoxCfg, 1, 1);
// output image
QHBoxLayout qHBoxLblOut;
QLabel qLblOut(QString::fromUtf8("Output Image:"));
qHBoxLblOut.addWidget(&qLblOut);
qHBoxLblOut.addStretch(1);
QColorButton qBtnBgColor1(QString::fromUtf8("Color 1"), Qt::darkGray);
qHBoxLblOut.addWidget(&qBtnBgColor1);
QColorButton qBtnBgColor2(QString::fromUtf8("Color 2"), Qt::gray);
qHBoxLblOut.addWidget(&qBtnBgColor2);
qGrid.addLayout(&qHBoxLblOut, 0, 2);
QLabel qLblImgOut;
qLblImgOut.setPixmap(fromAlphaImage(qImgOut));
qGrid.addWidget(&qLblImgOut, 1, 2);
// main window
qWin.setLayout(&qGrid);
qWin.show();
// helper
auto update = [&]() {
const int algo = qMenuColorToAlpha.currentIndex();
switch (algo) {
case SingleValue:
qImgOut
= colorToAlphaSimple(qImgIn, qBtnColor.color(),
qTglFill.isChecked(), qBtnColorFill.color());
break;
case RGBRange:
qImgOut
= colorToAlphaThreshold(qImgIn, qBtnColor.color(), qEditRange.value(),
qTglFill.isChecked(), qBtnColorFill.color());
break;
}
qEditRange.setEnabled(algo >= RGBRange);
qBtnColorFill.setEnabled(qTglFill.isChecked());
qLblImgOut.setPixmap(
fromAlphaImage(qImgOut, qBtnBgColor1.color(), qBtnBgColor2.color()));
};
// install signal handlers
QObject::connect(
&qBtnLoad, &QPushButton::clicked,
[&]() {
QString filePath
= QFileDialog::getOpenFileName(
&qWin, QString::fromUtf8("Open Image File"),
QString(),
QString::fromUtf8(
"Image Files (*.png *.jpg *.jpeg);;"
"PNG Files (*.png);;"
"JPEG Files (*.jpg *.jpeg);;"
"All Files (*)"));
if (filePath.isEmpty()) return; // choice aborted
QImage qImg;
qImg.load(filePath);
if (qImg.isNull()) return; // file loading failed
qImgIn = qImg;
qLblImgIn.setPixmap(fromImage(qImgIn));
update();
});
QObject::connect(
&qMenuColorToAlpha,
QOverload<int>::of(&QComboBox::currentIndexChanged),
[&](int) { update(); });
QObject::connect(&qBtnColor, &QPushButton::clicked,
[&]() { qBtnColor.chooseColor(); update(); });
QObject::connect(
&qEditRange, QOverload<int>::of(&QSpinBox::valueChanged),
[&](int) { update(); });
QObject::connect(&qTglFill, &QCheckBox::toggled,
[&](bool) { update(); });
QObject::connect(&qBtnColorFill, &QPushButton::clicked,
[&]() { qBtnColorFill.chooseColor(); update(); });
QObject::connect(&qBtnBgColor1, &QPushButton::clicked,
[&]() { qBtnBgColor1.chooseColor(); update(); });
QObject::connect(&qBtnBgColor2, &QPushButton::clicked,
[&]() { qBtnBgColor2.chooseColor(); update(); });
// runtime loop
update();
return app.exec();
}
and a helper class qColorButton.h:
// borrowed from https://stackoverflow.com/a/55889624/7478597
#ifndef Q_COLOR_BUTTON_H
#define Q_COLOR_BUTTON_H
// Qt header:
#include <QColorDialog>
#include <QPushButton>
// a Qt push button for color selection
class QColorButton: public QPushButton {
private:
QColor _qColor;
public:
explicit QColorButton(
const QString &text = QString(), const QColor &qColor = Qt::black,
QWidget *pQParent = nullptr):
QPushButton(text, pQParent)
{
setColor(qColor);
}
explicit QColorButton(
const QColor &qColor = Qt::black,
QWidget *pQParent = nullptr):
QColorButton(QString(), qColor, pQParent)
{ }
virtual ~QColorButton() = default;
QColorButton(const QColorButton&) = delete;
QColorButton& operator=(const QColorButton&) = delete;
const QColor& color() const { return _qColor; }
void setColor(const QColor &qColor)
{
_qColor = qColor;
QFontMetrics qFontMetrics(font());
const int h = qFontMetrics.height();
QPixmap qPixmap(h, h);
qPixmap.fill(_qColor);
setIcon(qPixmap);
}
QColor chooseColor()
{
setColor(QColorDialog::getColor(_qColor, this, text()));
return _qColor;
}
};
#endif // Q_COLOR_BUTTON_H
When started, a default image is loaded and the simple matching is applied:
I downloaded the sample image from jloog.com/images/.
The result looks a bit poor.
The white background is matched but there appear white artefacts around black drawing.
This results from sampling where pixels which covered the drawing as well as the background got respective shades of gray.
So, a better approach is to turn the distance from pivot color into a respective alpha value whereby the threshold defines the range as well as a limit upto that colors shall be considered:
That looks better.
Now, I became curious how well this works in “real” photos:
The result is better when I was afraid.
However, it shows the limits of the approach I got so far.
Update:
While I was researching the web to get the precise conversion from RGB to HSV, I learnt a lot about the different HSL and HSV models I was not aware of before.
Finally, I stumbled into Color difference where I found some interesting statements:
As most definitions of color difference are distances within a color space, the standard means of determining distances is the Euclidean distance. If one presently has an RGB (Red, Green, Blue) tuple and wishes to find the color difference, computationally one of the easiest is to call R, G, B linear dimensions defining the color space.
…
There are a number of color distance formulae that attempt to use color spaces like HSV with the hue as a circle, placing the various colors within a three dimensional space of either a cylinder or cone, but most of these are just modifications of RGB; without accounting for differences in human color perception they will tend to be on par with a simple Euclidean metric.
So, I discarded the idea with matching in HSV space.
Instead, I made a very simple extension which IMHO provides a significant improvement concerning monochrom drawings:
The pixels with mixed drawing and background are transformed into shades of alpha but the RGB values are left untouched.
This is not quite correct because it should actually become the foreground color (pencil color) blended with alpha.
To fix this, I added an option to override the RGB values of the output with a color of choice.
This is the result with overridden color:
Btw. it allows a nice little extra effect – the pencil color can be modified:
(The above sample source code has been updated to reflect the last changes.)
To build the sample, either CMake can be used with this CMakeLists.txt:
project(QImageColorToAlpha)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
#set(CMAKE_CXX_STANDARD 17)
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories(
"${CMAKE_SOURCE_DIR}")
add_executable(testQImageColorToAlpha
testQImageColorToAlpha.cc
qColorButton.h # qColorButton.cc
imageColorToAlpha.h imageColorToAlpha.cc)
target_link_libraries(testQImageColorToAlpha
Qt5::Widgets)
# define QT_NO_KEYWORDS to prevent confusion between of Qt signal-slots and
# other signal-slot APIs
target_compile_definitions(testQImageColorToAlpha PUBLIC QT_NO_KEYWORDS)
which I used to build it in VS2017.
Alternatively, a minimal Qt project file testQImageColorToAlpha.pro:
SOURCES = testQImageColorToAlpha.cc imageColorToAlpha.cc
QT += widgets
which I tested in cygwin:
$ qmake-qt5 testQImageColorToAlpha.pro
$ make && ./testQImageColorToAlpha
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQImageColorToAlpha.o testQImageColorToAlpha.cc
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o imageColorToAlpha.o imageColorToAlpha.cc
g++ -o testQImageColorToAlpha.exe testQImageColorToAlpha.o imageColorToAlpha.o -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
Qt Version: 5.9.4

Expression must be a modifiable L value error

I am writing a code that reads a ppm file and stores the width, the height and the pixels of the file to an image object. In my image class I have a pointer that holds the image data. I also have an error in a method that sets the rgb values for a x, y pixel.
typedef load compontent_t
class Image
{
public:
enum channel_t { RED = 0, GREEN, BLUE };
protected:
component_t * buffer; //! Holds the image data.
unsigned int width, //! The width of the image (in pixels)
height; //! The height of the image (in pixels)
// data mutators
/*! Sets the RGB values for an (x,y) pixel.
*
* The method should perform any necessary bounds checking.
*
* \param x is the (zero-based) horizontal index of the pixel to set.
* \param y is the (zero-based) vertical index of the pixel to set.
* \param value is the new color for the (x,y) pixel.
*/
void setPixel(unsigned int x, unsigned int y, Color & value) {
if (x > 0 && x < width && y > 0 && y < height) {
size_t locpixel = y*width + x;
size_t componentofpixel = locpixel * 3;
*buffer + componentofpixel = value.r;
*buffer + componentofpixel + 1 = value.g;
*buffer + componentofpixel + 2 = value.b;
}
else {
cout << "Pixel out of bounds" << endl;
}
}
Image(unsigned int width, unsigned int height, component_t * data_ptr): width(width), height(height),buffer(data_ptr) {}
So in the setPixel method when i am trying to find the correct spot of the buffer to set the rgb value it shows me the error: "expression must be a modifiable L value"

cv::Mat to QImage strange behavior

I'm using the code suggested in ( how to convert an opencv cv::Mat to qimage ) to display a cv::Mat in my Qt application. However, I'm getting strange results. The black parts are displayed as black, but all other values are inverted.
Conversion code:
QImage ImgConvert::matToQImage(Mat_<double> src)
{
double scale = 255.0;
QImage dest(src.cols, src.rows, QImage::Format_ARGB32);
for (int y = 0; y < src.rows; ++y) {
const double *srcrow = src[y];
QRgb *destrow = (QRgb*)dest.scanLine(y);
for (int x = 0; x < src.cols; ++x) {
unsigned int color = srcrow[x] * scale;
destrow[x] = qRgba(color, color, color, 255);
}
}
return dest;
}
Display code:
void MainWindow::redraw()
{
static QImage image = ImgConvert::matToQImage(im);
static QGraphicsPixmapItem item( QPixmap::fromImage(image));
static QGraphicsScene* scene = new QGraphicsScene;
scene->addItem(&item);
ui->graphicsView->setScene(scene);
ui->graphicsView->repaint();
}
Right now I'm using if(color>0) color = 255-color; to correct for this effect, but I'd much rather understand where it's coming from.
Also, a second mini-question: if I remove the static declarations in redraw(), the image gets removed from memory immediately when the method exits. Is this the best way to fix this, and am I going to have any unintended side effects if I display multiple frames?
I don't know. Setting an array first for me sounds like a cleaner way, see https://stackoverflow.com/a/3387400/1705967 , that could give you ideas.
Although I also use Ypnos's solution with a great success on color images. :)
Ah, and as for the second question, don't worry about the QPixmap. It makes the image data private (clones when necessary) as I have experienced so you won't overwrite it by mistake.
In case anyone is having this problem, I quickly and dirtily fixed it by subtracting the pixel value to 256:
QImage ImgConvert::matToQImage(Mat_<double> src)
{
double scale = 255.0;
QImage dest(src.cols, src.rows, QImage::Format_ARGB32);
for (int y = 0; y < src.rows; ++y) {
const double *srcrow = src[y];
QRgb *destrow = (QRgb*)dest.scanLine(y);
for (int x = 0; x < src.cols; ++x) {
unsigned int color = 256 - (srcrow[x] * scale);
destrow[x] = qRgba(color, color, color, 255);
}
}
return dest;
}
This will slightly corrupt the image, though, modifying by 1 its bright. My purpose was visualizing so the difference was negligible to the eye, however for certain applications in image processing this corruption might be critical. I could not find why was this happening and as I was in a hurry I did not look any further.

Get intermediate color from a gradient

Say I have a liner gradient as shown:
QLinearGradient linearGrad(QPointF(0, 0), QPointF(0, 100));
linearGrad.setColorAt(1, Qt::red);
linearGrad.setColorAt(0.5, Qt::yellow);
linearGrad.setColorAt(0, Qt::green);
How to get the color of the point QPointF(0, 28.5) in this gradient?
Indeed I want to have this kind of color distribution to be able to choose intermediate colors. I don't care if it is done by using QLinearGradient or something else.
I store the colors of gradient in one QList and then compute with color interpolation.
QColor ColorGradient::getColor(double value)
{
qDebug()<< "ColorGradient::getColor:";
//Asume mGradientColors.count()>1 and value=[0,1]
double stepbase = 1.0/(mGradientColors.count()-1);
int interval=mGradientColors.count()-1; //to fix 1<=0.99999999;
for (int i=1; i<mGradientColors.count();i++)//remove begin and end
{
if(value<=i*stepbase ){interval=i;break;}
}
double percentage = (value-stepbase*(interval-1))/stepbase;
QColor color(interpolate(mGradientColors[interval],mGradientColors[interval-1],percentage));
return color;
}
QColor ColorGradient::interpolate(QColor start,QColor end,double ratio)
{
int r = (int)(ratio*start.red() + (1-ratio)*end.red());
int g = (int)(ratio*start.green() + (1-ratio)*end.green());
int b = (int)(ratio*start.blue() + (1-ratio)*end.blue());
return QColor::fromRgb(r,g,b);
}
Mason Zhang answer does work, and very well !
Let controlPoints() return a QMap<qreal,QColor>, with a key between 0.0 and 1.0.
Here is how i did (thanks to Mason Zhang)
QColor getColor(qreal key) const
{
// key must belong to [0,1]
key = Clip(key, 0.0, 1.0) ;
// directly get color if known
if(controlPoints().contains(key))
{
return controlPoints().value(key) ;
}
// else, emulate a linear gradient
QPropertyAnimation interpolator ;
const qreal granularite = 100.0 ;
interpolator.setEasingCurve(QEasingCurve::Linear) ;
interpolator.setDuration(granularite) ;
foreach( qreal key, controlPoints().keys() )
{
interpolator.setKeyValueAt(key, controlPoints().value(key)) ;
}
interpolator.setCurrentTime(key*granularite) ;
return interpolator.currentValue().value<QColor>() ;
}
There is only way to make it:
There is a static member in QPixmap class
QPixmap QPixmap::grabWindow( WId window, int x = 0, int y = 0, int width = -1, int height = -1 )
1) draw your gradient on your widget;
2) grab you widget's surface into pixmap using that function; WId can be received from QWidget::effectiveWinId ();
3) convert token pixmap into QImage (there is an constructor available);
4) int QImage::pixelIndex( int x, int y ) returns the pixel index at (x, y) in QImage's color table. In your case you must calculate percentage value from widget's height ( pWidget->height() / 100 * 28.5 ).
5) QRgb QImage::color( int i ) returns the color in the color table at index i.
So returned Color is the color you were seeking.
QVariantAnimation has the similar functionality, and QVariantAnimation::keyValueAt can return the value you need. You may step into the code of QVariantAnimation and see how keyValueAt works.