ImageColors: use OpenMP to count pixels in parallel
This could hopefully accelerate the palette generation process and make the grabbed image have higher resolution, which will be helpful to plasma/kdeplasma-addons!249 (merged) as image extractor requires higher resolution to calculate the average color more precisely.
On my laptop (AMD Ryzen 4700u) the new code is about 5x faster than the original code.
Parallel 258
Single 1470
Test code
#include <QDebug>
#include <QElapsedTimer>
#include <QImage>
#include <omp.h>
#include <cmath>
struct LabColor {
qreal l = 0;
qreal a = 0;
qreal b = 0;
};
struct XYZColor {
qreal x = 0;
qreal y = 0;
qreal z = 0;
};
XYZColor colorToXYZ(const QColor &color)
{
// http://wiki.nuaj.net/index.php/Color_Transforms#RGB_.E2.86.92_XYZ
qreal r = color.redF();
qreal g = color.greenF();
qreal b = color.blueF();
// Apply gamma correction (i.e. conversion to linear-space)
auto correct = [](qreal &v) {
if (v > 0.04045) {
v = std::pow((v + 0.055) / 1.055, 2.4);
} else {
v = v / 12.92;
}
};
correct(r);
correct(g);
correct(b);
// Observer. = 2°, Illuminant = D65
const qreal x = r * 0.4124 + g * 0.3576 + b * 0.1805;
const qreal y = r * 0.2126 + g * 0.7152 + b * 0.0722;
const qreal z = r * 0.0193 + g * 0.1192 + b * 0.9505;
return XYZColor{x, y, z};
}
LabColor colorToLab(const QColor &color)
{
// First: convert to XYZ
const auto xyz = colorToXYZ(color);
// Second: convert from XYZ to L*a*b
qreal x = xyz.x / 0.95047; // Observer= 2°, Illuminant= D65
qreal y = xyz.y / 1.0;
qreal z = xyz.z / 1.08883;
auto pivot = [](qreal &v) {
if (v > 0.008856) {
v = std::pow(v, 1.0 / 3.0);
} else {
v = (7.787 * v) + (16.0 / 116.0);
}
};
pivot(x);
pivot(y);
pivot(z);
LabColor labColor;
labColor.l = std::max(0.0, (116 * y) - 16);
labColor.a = 500 * (x - y);
labColor.b = 200 * (y - z);
return labColor;
}
double chroma(const QColor &color)
{
LabColor labColor = colorToLab(color);
// Chroma is hypotenuse of a and b
return sqrt(pow(labColor.a, 2) + pow(labColor.b, 2));
}
int main()
{
QImage sourceImage("/mnt/data/home/qydw/Pictures/Nature Ultra HD 4K Wallpapers/Nature Ultra HD 4K Wallpapers Pack-4 (25).jpg");
qInfo() << sourceImage.size();
sourceImage = sourceImage.scaledToWidth(128);
QElapsedTimer timer;
timer.start();
static const int numCore = std::min(8, omp_get_num_procs());
omp_set_num_threads(numCore);
for (int i = 0; i < 1000; ++i)
{
QList<QRgb> m_samples;
std::vector<decltype(m_samples)> tempSamples(numCore, decltype(m_samples){});
int r = 0;
int g = 0;
int b = 0;
int c = 0;
#pragma omp parallel for reduction(+ : r) reduction(+ : g) reduction(+ : b) reduction(+ : c)
for (int x = 0; x < sourceImage.width(); ++x) {
for (int y = 0; y < sourceImage.height(); ++y) {
const QColor sampleColor = sourceImage.pixelColor(x, y);
if (sampleColor.alpha() == 0) {
continue;
}
if (chroma(sampleColor) < 20) {
continue;
}
QRgb rgb = sampleColor.rgb();
++c;
r += qRed(rgb);
g += qGreen(rgb);
b += qBlue(rgb);
tempSamples[omp_get_thread_num()] << rgb;
}
} // END omp parallel for
for (const auto &s : std::as_const(tempSamples)) {
m_samples << s;
}
}
qInfo() << "Parallel" << timer.elapsed();
// Single
timer.restart();
for (int i = 0; i < 1000; ++i)
{
QList<QRgb> m_samples;
int r = 0;
int g = 0;
int b = 0;
int c = 0;
for (int x = 0; x < sourceImage.width(); ++x) {
for (int y = 0; y < sourceImage.height(); ++y) {
const QColor sampleColor = sourceImage.pixelColor(x, y);
if (sampleColor.alpha() == 0) {
continue;
}
if (chroma(sampleColor) < 20) {
continue;
}
QRgb rgb = sampleColor.rgb();
++c;
r += qRed(rgb);
g += qGreen(rgb);
b += qBlue(rgb);
m_samples << rgb;
}
}
}
qInfo() << "Single" << timer.elapsed();
return 0;
}
Edited by Fushan Wen