File indexing completed on 2024-05-12 03:47:30

0001 /*
0002     File                 : ImageEditor.cpp
0003     Project              : LabPlot
0004     Description          : Edit Image on the basis of input color attributes
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015 Ankit Wagadre <wagadre.ankit@gmail.com>
0007     SPDX-FileCopyrightText: 2015-2016 Alexander Semke <alexander.semke@web.de>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "ImageEditor.h"
0012 #include <QElapsedTimer>
0013 #include <QMutex>
0014 #include <QThreadPool>
0015 
0016 #include <gsl/gsl_math.h>
0017 
0018 static const QRgb white = QColor(Qt::white).rgb();
0019 static const QRgb black = QColor(Qt::black).rgb();
0020 static const double colorScale = gsl_hypot3(255, 255, 255);
0021 
0022 // Intensity, Foreground, Saturation and Value are from 0 to 100, Hue is from 0 to 360
0023 static const int maxIntensity = 100;
0024 static const int maxForeground = 100;
0025 static const int maxHue = 360;
0026 static const int maxSaturation = 100;
0027 static const int maxValue = 100;
0028 
0029 QMutex mutex;
0030 
0031 class DiscretizeTask : public QRunnable {
0032 public:
0033     DiscretizeTask(int start, int end, QImage* plotImage, const QImage* originalImage, const DatapickerImage::EditorSettings& settings, QColor background)
0034         : m_start(start)
0035         , m_end(end)
0036         , m_plotImage(plotImage)
0037         , m_originalImage(originalImage)
0038         , m_settings(settings)
0039         , m_background(std::move(background)){};
0040 
0041     void run() override {
0042         for (int y = m_start; y < m_end; ++y) {
0043             mutex.lock();
0044             QRgb* line = reinterpret_cast<QRgb*>(m_plotImage->scanLine(y));
0045             mutex.unlock();
0046             for (int x = 0; x < m_plotImage->width(); ++x) {
0047                 int value = ImageEditor::discretizeHue(x, y, m_originalImage);
0048                 if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Hue, m_settings))
0049                     continue;
0050 
0051                 value = ImageEditor::discretizeSaturation(x, y, m_originalImage);
0052                 if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Saturation, m_settings))
0053                     continue;
0054 
0055                 value = ImageEditor::discretizeValue(x, y, m_originalImage);
0056                 if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Value, m_settings))
0057                     continue;
0058 
0059                 value = ImageEditor::discretizeIntensity(x, y, m_originalImage);
0060                 if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Intensity, m_settings))
0061                     continue;
0062 
0063                 value = ImageEditor::discretizeForeground(x, y, m_background, m_originalImage);
0064                 if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Foreground, m_settings))
0065                     continue;
0066 
0067                 line[x] = black;
0068             }
0069         }
0070     }
0071 
0072 private:
0073     int m_start;
0074     int m_end;
0075     QImage* m_plotImage;
0076     const QImage* m_originalImage;
0077     DatapickerImage::EditorSettings m_settings;
0078     QColor m_background;
0079 };
0080 
0081 /*!
0082  *
0083  */
0084 void ImageEditor::discretize(QImage* plotImage, const QImage* originalImage, const DatapickerImage::EditorSettings& settings, QColor background) {
0085     plotImage->fill(white);
0086     QThreadPool* pool = QThreadPool::globalInstance();
0087     int range = ceil(double(plotImage->height()) / pool->maxThreadCount());
0088     for (int i = 0; i < pool->maxThreadCount(); ++i) {
0089         const int start = i * range;
0090         int end = (i + 1) * range;
0091         if (end > plotImage->height())
0092             end = plotImage->height();
0093         auto* task = new DiscretizeTask(start, end, plotImage, originalImage, settings, background);
0094         pool->start(task);
0095     }
0096     pool->waitForDone();
0097 }
0098 
0099 bool ImageEditor::processedPixelIsOn(const QImage& plotImage, int x, int y) {
0100     if ((x < 0) || (plotImage.width() <= x) || (y < 0) || (plotImage.height() <= y))
0101         return false;
0102 
0103     // pixel is on if it is closer to black than white in gray scale. this test must be performed
0104     // on little endian and big endian systems, with or without alpha bits (which are typically high bits)
0105     const int BLACK_WHITE_THRESHOLD = 255 / 2; // put threshold in middle of range
0106     int gray = qGray(plotImage.pixel(x, y));
0107     return (gray < BLACK_WHITE_THRESHOLD);
0108 }
0109 
0110 // ##############################################################################
0111 // #####################  private helper functions  #############################
0112 // ##############################################################################
0113 QRgb ImageEditor::findBackgroundColor(const QImage* plotImage) {
0114     ColorList::iterator itrC;
0115     ColorList colors;
0116     int x, y = 0;
0117     for (x = 0; x < plotImage->width(); ++x) {
0118         ColorEntry c;
0119         c.color = plotImage->pixel(x, y);
0120         c.count = 0;
0121 
0122         bool found = false;
0123         for (itrC = colors.begin(); itrC != colors.end(); ++itrC) {
0124             if (colorCompare(c.color.rgb(), (*itrC).color.rgb())) {
0125                 found = true;
0126                 ++(*itrC).count;
0127                 break;
0128             }
0129         }
0130         if (!found)
0131             colors.append(c);
0132 
0133         if (++y >= plotImage->height())
0134             y = 0;
0135     }
0136 
0137     ColorEntry cMax;
0138     cMax.count = 0;
0139     for (itrC = colors.begin(); itrC != colors.end(); ++itrC) {
0140         if ((*itrC).count > cMax.count)
0141             cMax = (*itrC);
0142     }
0143 
0144     return cMax.color.rgb();
0145 }
0146 
0147 void ImageEditor::uploadHistogram(int* bins, QImage* originalImage, QColor background, DatapickerImage::ColorAttributes type) {
0148     // reset bin
0149     for (int i = 0; i <= colorAttributeMax(type); ++i)
0150         bins[i] = 0;
0151 
0152     for (int x = 0; x < originalImage->width(); ++x) {
0153         for (int y = 0; y < originalImage->height(); ++y) {
0154             int value = discretizeValueForeground(x, y, type, background, originalImage);
0155             bins[value] += 1;
0156         }
0157     }
0158 }
0159 
0160 int ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes type) {
0161     // Intensity, Foreground, Saturation and Value are from 0 to 100
0162     // Hue is from 0 to 360
0163     switch (type) {
0164     case DatapickerImage::ColorAttributes::None:
0165         return 0;
0166     case DatapickerImage::ColorAttributes::Intensity:
0167         return 100;
0168     case DatapickerImage::ColorAttributes::Foreground:
0169         return 100;
0170     case DatapickerImage::ColorAttributes::Hue:
0171         return 360;
0172     case DatapickerImage::ColorAttributes::Saturation:
0173         return 100;
0174     case DatapickerImage::ColorAttributes::Value:
0175     default:
0176         return 100;
0177     }
0178 }
0179 
0180 bool ImageEditor::colorCompare(QRgb color1, QRgb color2) {
0181     const long MASK = 0xf0f0f0f0;
0182     return (color1 & MASK) == (color2 & MASK);
0183 }
0184 
0185 int ImageEditor::discretizeHue(int x, int y, const QImage* originalImage) {
0186     const QColor color(originalImage->pixel(x, y));
0187     const int h = color.hue();
0188     int value = h * maxHue / 359;
0189 
0190     if (value < 0) // QColor::hue() can return -1
0191         value = 0;
0192     if (maxHue < value)
0193         value = maxHue;
0194 
0195     return value;
0196 }
0197 
0198 int ImageEditor::discretizeSaturation(int x, int y, const QImage* originalImage) {
0199     const QColor color(originalImage->pixel(x, y));
0200     const int s = color.saturation();
0201     int value = s * maxSaturation / 255;
0202 
0203     if (maxSaturation < value)
0204         value = maxSaturation;
0205 
0206     return value;
0207 }
0208 
0209 int ImageEditor::discretizeValue(int x, int y, const QImage* originalImage) {
0210     const QColor color(originalImage->pixel(x, y));
0211     const int v = color.value();
0212     int value = v * maxValue / 255;
0213 
0214     if (maxValue < value)
0215         value = maxValue;
0216 
0217     return value;
0218 }
0219 
0220 int ImageEditor::discretizeIntensity(int x, int y, const QImage* originalImage) {
0221     const QRgb color = originalImage->pixel(x, y);
0222     const int r = qRed(color);
0223     const int g = qGreen(color);
0224     const int b = qBlue(color);
0225 
0226     const double intensity = gsl_hypot3(r, g, b);
0227     int value = (int)(intensity * maxIntensity / colorScale + 0.5);
0228 
0229     if (maxIntensity < value)
0230         value = maxIntensity;
0231 
0232     return value;
0233 }
0234 
0235 int ImageEditor::discretizeForeground(int x, int y, const QColor background, const QImage* originalImage) {
0236     const QRgb color = originalImage->pixel(x, y);
0237     const int r = qRed(color);
0238     const int g = qGreen(color);
0239     const int b = qBlue(color);
0240     const int rBg = background.red();
0241     const int gBg = background.green();
0242     const int bBg = background.blue();
0243     const double distance = gsl_hypot3(r - rBg, g - gBg, b - bBg);
0244     int value = (int)(distance * maxForeground / colorScale + 0.5);
0245 
0246     if (maxForeground < value)
0247         value = maxForeground;
0248 
0249     return value;
0250 }
0251 
0252 int ImageEditor::discretizeValueForeground(int x, int y, DatapickerImage::ColorAttributes type, const QColor background, const QImage* originalImage) {
0253     const QColor color(originalImage->pixel(x, y));
0254 
0255     // convert hue from 0 to 359, saturation from 0 to 255, value from 0 to 255
0256     int value = 0;
0257     switch (type) {
0258     case DatapickerImage::ColorAttributes::None:
0259         break;
0260     case DatapickerImage::ColorAttributes::Intensity: {
0261         const int r = color.red();
0262         const int g = color.green();
0263         const int b = color.blue();
0264         const double intensity = gsl_hypot3(r, g, b);
0265         value = (int)(intensity * maxIntensity / colorScale + 0.5);
0266         if (maxIntensity < value)
0267             value = maxIntensity;
0268         break;
0269     }
0270     case DatapickerImage::ColorAttributes::Foreground: {
0271         const int r = color.red();
0272         const int g = color.green();
0273         const int b = color.blue();
0274         const int rBg = background.red();
0275         const int gBg = background.green();
0276         const int bBg = background.blue();
0277         const double distance = gsl_hypot3(r - rBg, g - gBg, b - bBg);
0278         value = (int)(distance * maxForeground / colorScale + 0.5);
0279         if (maxForeground < value)
0280             value = maxForeground;
0281         break;
0282     }
0283     case DatapickerImage::ColorAttributes::Hue: {
0284         const int h = color.hue();
0285         value = h * maxHue / 359;
0286         if (value < 0)
0287             value = 0;
0288         if (maxHue < value)
0289             value = maxHue;
0290         break;
0291     }
0292     case DatapickerImage::ColorAttributes::Saturation: {
0293         const int s = color.saturation();
0294         value = s * maxSaturation / 255;
0295         if (maxSaturation < value)
0296             value = maxSaturation;
0297         break;
0298     }
0299     case DatapickerImage::ColorAttributes::Value: {
0300         const int v = color.value();
0301         value = v * maxValue / 255;
0302         if (maxValue < value)
0303             value = maxValue;
0304         break;
0305     }
0306     }
0307 
0308     return value;
0309 }
0310 
0311 bool ImageEditor::pixelIsOn(int value, int low, int high) {
0312     if (low < high)
0313         return ((low <= value) && (value <= high));
0314     else
0315         return ((low <= value) || (value <= high));
0316 }
0317 
0318 bool ImageEditor::pixelIsOn(int value, DatapickerImage::ColorAttributes type, const DatapickerImage::EditorSettings& settings) {
0319     switch (type) {
0320     case DatapickerImage::ColorAttributes::None:
0321         break;
0322     case DatapickerImage::ColorAttributes::Intensity:
0323         return pixelIsOn(value, settings.intensityThresholdLow, settings.intensityThresholdHigh);
0324     case DatapickerImage::ColorAttributes::Foreground:
0325         return pixelIsOn(value, settings.foregroundThresholdLow, settings.foregroundThresholdHigh);
0326     case DatapickerImage::ColorAttributes::Hue:
0327         return pixelIsOn(value, settings.hueThresholdLow, settings.hueThresholdHigh);
0328     case DatapickerImage::ColorAttributes::Saturation:
0329         return pixelIsOn(value, settings.saturationThresholdLow, settings.saturationThresholdHigh);
0330     case DatapickerImage::ColorAttributes::Value:
0331         return pixelIsOn(value, settings.valueThresholdLow, settings.valueThresholdHigh);
0332     }
0333 
0334     return false;
0335 }