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 }