1 /*
2  *  Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
3  *
4  *  This library is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU Lesser General Public License as published
6  *  by the Free Software Foundation; either version 2.1 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "KoBasicHistogramProducers.h"
20 
21 #include <QString>
22 #include <klocalizedstring.h>
23 
24 #include <KoConfig.h>
25 #ifdef HAVE_OPENEXR
26 #include <half.h>
27 #endif
28 
29 // #include "Ko_global.h"
30 #include "KoIntegerMaths.h"
31 #include "KoChannelInfo.h"
32 
33 static const KoColorSpace* m_labCs = 0;
34 
35 
KoBasicHistogramProducer(const KoID & id,int channelCount,int nrOfBins)36 KoBasicHistogramProducer::KoBasicHistogramProducer(const KoID& id, int channelCount, int nrOfBins)
37     : m_channels(channelCount)
38     , m_nrOfBins(nrOfBins)
39     , m_colorSpace(0)
40     , m_id(id)
41 {
42     m_bins.resize(m_channels);
43     for (int i = 0; i < m_channels; i++)
44         m_bins[i].resize(m_nrOfBins);
45     m_outLeft.resize(m_channels);
46     m_outRight.resize(m_channels);
47     m_count = 0;
48     m_from = 0.0;
49     m_width = 1.0;
50 }
51 
KoBasicHistogramProducer(const KoID & id,int nrOfBins,const KoColorSpace * cs)52 KoBasicHistogramProducer::KoBasicHistogramProducer(const KoID& id, int nrOfBins, const KoColorSpace *cs)
53     : m_nrOfBins(nrOfBins),
54       m_colorSpace(cs),
55       m_id(id)
56 {
57     Q_ASSERT(cs);
58     m_channels = cs->channelCount();
59 
60     m_bins.resize(m_channels);
61     for (int i = 0; i < m_channels; i++)
62         m_bins[i].resize(m_nrOfBins);
63     m_outLeft.resize(m_channels);
64     m_outRight.resize(m_channels);
65     m_count = 0;
66     m_from = 0.0;
67     m_width = 1.0;
68 }
69 
70 
clear()71 void KoBasicHistogramProducer::clear()
72 {
73     m_count = 0;
74     for (int i = 0; i < m_channels; i++) {
75         for (int j = 0; j < m_nrOfBins; j++) {
76             m_bins[i][j] = 0;
77         }
78         m_outRight[i] = 0;
79         m_outLeft[i] = 0;
80     }
81 }
82 
makeExternalToInternal()83 void KoBasicHistogramProducer::makeExternalToInternal()
84 {
85     // This function assumes that the pixel is has no 'gaps'. That is to say: if we start
86     // at byte 0, we can get to the end of the pixel by adding consecutive size()s of
87     // the channels
88     QList<KoChannelInfo *> c = channels();
89     uint count = c.count();
90     int currentPos = 0;
91 
92     for (uint i = 0; i < count; i++) {
93         for (uint j = 0; j < count; j++) {
94             if (c[j]->pos() == currentPos) {
95                 m_external.append(j);
96                 break;
97             }
98         }
99         currentPos += c.at(m_external.at(m_external.count() - 1))->size();
100     }
101 }
102 
103 // ------------ U8 ---------------------
104 
KoBasicU8HistogramProducer(const KoID & id,const KoColorSpace * cs)105 KoBasicU8HistogramProducer::KoBasicU8HistogramProducer(const KoID& id, const KoColorSpace *cs)
106     : KoBasicHistogramProducer(id, 256, cs)
107 {
108 }
109 
positionToString(qreal pos) const110 QString KoBasicU8HistogramProducer::positionToString(qreal pos) const
111 {
112     return QString("%1").arg(static_cast<quint8>(pos * UINT8_MAX));
113 }
114 
addRegionToBin(const quint8 * pixels,const quint8 * selectionMask,quint32 nPixels,const KoColorSpace * cs)115 void KoBasicU8HistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
116 {
117     qint32 pSize = cs->pixelSize();
118 
119     if (selectionMask) {
120         while (nPixels > 0) {
121             if (!(m_skipUnselected && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
122 
123                 for (int i = 0; i < m_channels; i++) {
124                     m_bins[i][pixels[i]]++;
125                 }
126                 m_count++;
127 
128             }
129 
130             pixels += pSize;
131             selectionMask++;
132             nPixels--;
133         }
134     } else {
135         while (nPixels > 0) {
136             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
137 
138                 for (int i = 0; i < m_channels; i++) {
139                     m_bins[i][pixels[i]]++;
140                 }
141                 m_count++;
142 
143             }
144 
145             pixels += pSize;
146             nPixels--;
147         }
148     }
149 }
150 
151 // ------------ U16 ---------------------
152 
KoBasicU16HistogramProducer(const KoID & id,const KoColorSpace * cs)153 KoBasicU16HistogramProducer::KoBasicU16HistogramProducer(const KoID& id, const KoColorSpace *cs)
154     : KoBasicHistogramProducer(id, 256, cs)
155 {
156 }
157 
positionToString(qreal pos) const158 QString KoBasicU16HistogramProducer::positionToString(qreal pos) const
159 {
160     return QString("%1").arg(static_cast<quint8>(pos * UINT8_MAX));
161 }
162 
maximalZoom() const163 qreal KoBasicU16HistogramProducer::maximalZoom() const
164 {
165     return 1.0 / 255.0;
166 }
167 
addRegionToBin(const quint8 * pixels,const quint8 * selectionMask,quint32 nPixels,const KoColorSpace * cs)168 void KoBasicU16HistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
169 {
170     // The view
171     quint16 from = static_cast<quint16>(m_from * UINT16_MAX);
172     quint16 width = static_cast<quint16>(m_width * UINT16_MAX + 0.5); // We include the end
173     quint16 to = from + width;
174     qreal factor = 255.0 / width;
175 
176     qint32 pSize = cs->pixelSize();
177 
178     if (selectionMask) {
179         const quint16* pixel = reinterpret_cast<const quint16*>(pixels);
180         while (nPixels > 0) {
181             if (!((m_skipUnselected && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
182                 for (int i = 0; i < m_channels; i++) {
183                     quint16 value = pixel[i];
184                     if (value > to)
185                         m_outRight[i]++;
186                     else if (value < from)
187                         m_outLeft[i]++;
188                     else
189                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
190                 }
191                 m_count++;
192             }
193             pixels += pSize;
194             selectionMask++;
195             nPixels--;
196         }
197     } else {
198         while (nPixels > 0) {
199             const quint16* pixel = reinterpret_cast<const quint16*>(pixels);
200 
201             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
202                 for (int i = 0; i < m_channels; i++) {
203                     quint16 value = pixel[i];
204                     if (value > to)
205                         m_outRight[i]++;
206                     else if (value < from)
207                         m_outLeft[i]++;
208                     else
209                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
210                 }
211                 m_count++;
212             }
213             pixels += pSize;
214             nPixels--;
215 
216         }
217     }
218 }
219 
220 // ------------ Float32 ---------------------
KoBasicF32HistogramProducer(const KoID & id,const KoColorSpace * cs)221 KoBasicF32HistogramProducer::KoBasicF32HistogramProducer(const KoID& id, const KoColorSpace *cs)
222     : KoBasicHistogramProducer(id, 256, cs)
223 {
224 }
225 
positionToString(qreal pos) const226 QString KoBasicF32HistogramProducer::positionToString(qreal pos) const
227 {
228     return QString("%1").arg(static_cast<float>(pos)); // XXX I doubt this is correct!
229 }
230 
maximalZoom() const231 qreal KoBasicF32HistogramProducer::maximalZoom() const
232 {
233     // XXX What _is_ the maximal zoom here? I don't think there is one with floats, so this seems a fine compromis for the moment
234     return 1.0 / 255.0;
235 }
236 
addRegionToBin(const quint8 * pixels,const quint8 * selectionMask,quint32 nPixels,const KoColorSpace * cs)237 void KoBasicF32HistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
238 {
239     // The view
240     float from = static_cast<float>(m_from);
241     float width = static_cast<float>(m_width);
242     float to = from + width;
243     float factor = 255.0 / width;
244 
245     qint32 pSize = cs->pixelSize();
246 
247     if (selectionMask) {
248         while (nPixels > 0) {
249 
250             const float* pixel = reinterpret_cast<const float*>(pixels);
251             if (!((m_skipUnselected && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
252                 for (int i = 0; i < m_channels; i++) {
253                     float value = pixel[i];
254                     if (value > to)
255                         m_outRight[i]++;
256                     else if (value < from)
257                         m_outLeft[i]++;
258                     else
259                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
260                 }
261                 m_count++;
262             }
263 
264             pixels += pSize;
265             selectionMask++;
266             nPixels--;
267 
268         }
269     } else {
270         while (nPixels > 0) {
271 
272             const float* pixel = reinterpret_cast<const float*>(pixels);
273             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
274                 for (int i = 0; i < m_channels; i++) {
275                     float value = pixel[i];
276                     if (value > to)
277                         m_outRight[i]++;
278                     else if (value < from)
279                         m_outLeft[i]++;
280                     else
281                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
282                 }
283                 m_count++;
284             }
285 
286             pixels += pSize;
287             nPixels--;
288 
289         }
290     }
291 }
292 
293 #ifdef HAVE_OPENEXR
294 // ------------ Float16 Half ---------------------
KoBasicF16HalfHistogramProducer(const KoID & id,const KoColorSpace * cs)295 KoBasicF16HalfHistogramProducer::KoBasicF16HalfHistogramProducer(const KoID& id,
296                                                                  const KoColorSpace *cs)
297     : KoBasicHistogramProducer(id, 256, cs)
298 {
299 }
300 
positionToString(qreal pos) const301 QString KoBasicF16HalfHistogramProducer::positionToString(qreal pos) const
302 {
303     return QString("%1").arg(static_cast<float>(pos)); // XXX I doubt this is correct!
304 }
305 
maximalZoom() const306 qreal KoBasicF16HalfHistogramProducer::maximalZoom() const
307 {
308     // XXX What _is_ the maximal zoom here? I don't think there is one with floats, so this seems a fine compromis for the moment
309     return 1.0 / 255.0;
310 }
311 
addRegionToBin(const quint8 * pixels,const quint8 * selectionMask,quint32 nPixels,const KoColorSpace * cs)312 void KoBasicF16HalfHistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
313 {
314     // The view
315     float from = static_cast<float>(m_from);
316     float width = static_cast<float>(m_width);
317     float to = from + width;
318     float factor = 255.0 / width;
319 
320     qint32 pSize = cs->pixelSize();
321     if (selectionMask) {
322         while (nPixels > 0) {
323             const half* pixel = reinterpret_cast<const half*>(pixels);
324             if (!((m_skipUnselected  && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
325                 for (int i = 0; i < m_channels; i++) {
326                     float value = pixel[i];
327                     if (value > to)
328                         m_outRight[i]++;
329                     else if (value < from)
330                         m_outLeft[i]++;
331                     else
332                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
333                 }
334                 m_count++;
335             }
336             pixels += pSize;
337             selectionMask++;
338             nPixels--;
339         }
340     } else {
341         while (nPixels > 0) {
342             const half* pixel = reinterpret_cast<const half*>(pixels);
343             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
344                 for (int i = 0; i < m_channels; i++) {
345                     float value = pixel[i];
346                     if (value > to)
347                         m_outRight[i]++;
348                     else if (value < from)
349                         m_outLeft[i]++;
350                     else
351                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
352                 }
353                 m_count++;
354             }
355             pixels += pSize;
356             nPixels--;
357         }
358     }
359 }
360 #endif
361 
362 // ------------ Generic RGB ---------------------
KoGenericRGBHistogramProducer()363 KoGenericRGBHistogramProducer::KoGenericRGBHistogramProducer()
364     : KoBasicHistogramProducer(KoID("GENRGBHISTO", i18n("Generic RGB Histogram")), 3, 256)
365 {
366     /* we set 0 as colorspece, because we are not based on a specific colorspace. This
367        is no problem for the superclass since we override channels() */
368     m_channelsList.append(new KoChannelInfo(i18n("R"), 0, 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(255, 0, 0)));
369     m_channelsList.append(new KoChannelInfo(i18n("G"), 1, 1, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(0, 255, 0)));
370     m_channelsList.append(new KoChannelInfo(i18n("B"), 2, 2, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(0, 0, 255)));
371 }
372 
channels()373 QList<KoChannelInfo *> KoGenericRGBHistogramProducer::channels()
374 {
375     return m_channelsList;
376 }
377 
positionToString(qreal pos) const378 QString KoGenericRGBHistogramProducer::positionToString(qreal pos) const
379 {
380     return QString("%1").arg(static_cast<quint8>(pos * UINT8_MAX));
381 }
382 
maximalZoom() const383 qreal KoGenericRGBHistogramProducer::maximalZoom() const
384 {
385     return 1.0;
386 }
387 
388 
addRegionToBin(const quint8 * pixels,const quint8 * selectionMask,quint32 nPixels,const KoColorSpace * cs)389 void KoGenericRGBHistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
390 {
391     for (int i = 0; i < m_channels; i++) {
392         m_outRight[i] = 0;
393         m_outLeft[i] = 0;
394     }
395 
396     QColor c;
397     qint32 pSize = cs->pixelSize();
398     if (selectionMask) {
399         while (nPixels > 0) {
400             if (!((m_skipUnselected  && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
401                 cs->toQColor(pixels, &c);
402                 m_bins[0][c.red()]++;
403                 m_bins[1][c.green()]++;
404                 m_bins[2][c.blue()]++;
405 
406                 m_count++;
407             }
408             pixels += pSize;
409             selectionMask++;
410             nPixels--;
411         }
412 
413     } else {
414         while (nPixels > 0) {
415 
416             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
417                 cs->toQColor(pixels, &c);
418                 m_bins[0][c.red()]++;
419                 m_bins[1][c.green()]++;
420                 m_bins[2][c.blue()]++;
421 
422                 m_count++;
423             }
424             pixels += pSize;
425             nPixels--;
426         }
427     }
428 }
429 
KoGenericRGBHistogramProducerFactory()430 KoGenericRGBHistogramProducerFactory::KoGenericRGBHistogramProducerFactory()
431     : KoHistogramProducerFactory(KoID("GENRGBHISTO", i18n("Generic RGB Histogram")))
432 {
433 }
434 
435 // ------------ Generic L*a*b* ---------------------
KoGenericLabHistogramProducer()436 KoGenericLabHistogramProducer::KoGenericLabHistogramProducer()
437     : KoBasicHistogramProducer(KoID("GENLABHISTO", i18n("L*a*b* Histogram")), 3, 256)
438 {
439     /* we set 0 as colorspace, because we are not based on a specific colorspace. This
440        is no problem for the superclass since we override channels() */
441     m_channelsList.append(new KoChannelInfo(i18n("L*"), 0, 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8));
442     m_channelsList.append(new KoChannelInfo(i18n("a*"), 1, 1, KoChannelInfo::COLOR, KoChannelInfo::UINT8));
443     m_channelsList.append(new KoChannelInfo(i18n("b*"), 2, 2, KoChannelInfo::COLOR, KoChannelInfo::UINT8));
444 
445     if (!m_labCs) {
446         m_labCs = KoColorSpaceRegistry::instance()->lab16();
447     }
448     m_colorSpace = m_labCs;
449 }
~KoGenericLabHistogramProducer()450 KoGenericLabHistogramProducer::~KoGenericLabHistogramProducer()
451 {
452     delete m_channelsList[0];
453     delete m_channelsList[1];
454     delete m_channelsList[2];
455 }
456 
channels()457 QList<KoChannelInfo *> KoGenericLabHistogramProducer::channels()
458 {
459     return m_channelsList;
460 }
461 
positionToString(qreal pos) const462 QString KoGenericLabHistogramProducer::positionToString(qreal pos) const
463 {
464     return QString("%1").arg(static_cast<quint16>(pos * UINT16_MAX));
465 }
466 
maximalZoom() const467 qreal KoGenericLabHistogramProducer::maximalZoom() const
468 {
469     return 1.0;
470 }
471 
472 
addRegionToBin(const quint8 * pixels,const quint8 * selectionMask,quint32 nPixels,const KoColorSpace * cs)473 void KoGenericLabHistogramProducer::addRegionToBin(const quint8 *pixels, const quint8 *selectionMask, quint32 nPixels,  const KoColorSpace *cs)
474 {
475     for (int i = 0; i < m_channels; i++) {
476         m_outRight[i] = 0;
477         m_outLeft[i] = 0;
478     }
479 
480     qint32 dstPixelSize = m_colorSpace->pixelSize();
481 
482     quint8 *dstPixels = new quint8[nPixels * dstPixelSize];
483     cs->convertPixelsTo(pixels, dstPixels, m_colorSpace, nPixels, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::Empty);
484 
485     qint32 pSize = cs->pixelSize();
486 
487     if (selectionMask) {
488         while (nPixels > 0) {
489             if (!((m_skipUnselected  && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
490                 m_count++;
491             }
492             pixels += pSize;
493             selectionMask++;
494             nPixels--;
495         }
496     } else {
497         quint8 *dst = dstPixels;
498         while (nPixels > 0) {
499             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))  {
500 
501                 m_bins[0][m_colorSpace->scaleToU8(dst, 0)]++;
502                 m_bins[1][m_colorSpace->scaleToU8(dst, 1)]++;
503                 m_bins[2][m_colorSpace->scaleToU8(dst, 2)]++;
504 
505                 m_count++;
506             }
507             dst+= dstPixelSize;
508             nPixels--;
509         }
510     }
511     delete[] dstPixels;
512 }
513 
KoGenericLabHistogramProducerFactory()514 KoGenericLabHistogramProducerFactory::KoGenericLabHistogramProducerFactory()
515     : KoHistogramProducerFactory(KoID("GENLABHISTO", i18n("Generic L*a*b* Histogram")))
516 {
517 }
518