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