File indexing completed on 2024-05-12 15:26:42

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