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 }