File indexing completed on 2024-05-12 15:59:30

0001 /*
0002  *  SPDX-FileCopyrightText: 2005 Bart Coppens <kde@bartcoppens.be>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 
0007 #include "KoBasicHistogramProducers.h"
0008 
0009 #include <QString>
0010 #include <klocalizedstring.h>
0011 
0012 #include <KoConfig.h>
0013 #ifdef HAVE_OPENEXR
0014 #include <half.h>
0015 #endif
0016 
0017 // #include "Ko_global.h"
0018 #include "KoIntegerMaths.h"
0019 #include "KoChannelInfo.h"
0020 
0021 static const KoColorSpace* m_labCs = 0;
0022 
0023 
0024 KoBasicHistogramProducer::KoBasicHistogramProducer(const KoID& id, int channelCount, int nrOfBins)
0025     : m_channels(channelCount)
0026     , m_nrOfBins(nrOfBins)
0027     , m_colorSpace(0)
0028     , m_id(id)
0029 {
0030     m_bins.resize(m_channels);
0031     for (int i = 0; i < m_channels; i++)
0032         m_bins[i].resize(m_nrOfBins);
0033     m_outLeft.resize(m_channels);
0034     m_outRight.resize(m_channels);
0035     m_count = 0;
0036     m_from = 0.0;
0037     m_width = 1.0;
0038 }
0039 
0040 KoBasicHistogramProducer::KoBasicHistogramProducer(const KoID& id, int nrOfBins, const KoColorSpace *cs)
0041     : m_nrOfBins(nrOfBins),
0042       m_colorSpace(cs),
0043       m_id(id)
0044 {
0045     Q_ASSERT(cs);
0046     m_channels = cs->channelCount();
0047 
0048     m_bins.resize(m_channels);
0049     for (int i = 0; i < m_channels; i++)
0050         m_bins[i].resize(m_nrOfBins);
0051     m_outLeft.resize(m_channels);
0052     m_outRight.resize(m_channels);
0053     m_count = 0;
0054     m_from = 0.0;
0055     m_width = 1.0;
0056 }
0057 
0058 
0059 void KoBasicHistogramProducer::clear()
0060 {
0061     m_count = 0;
0062     for (int i = 0; i < m_channels; i++) {
0063         for (int j = 0; j < m_nrOfBins; j++) {
0064             m_bins[i][j] = 0;
0065         }
0066         m_outRight[i] = 0;
0067         m_outLeft[i] = 0;
0068     }
0069 }
0070 
0071 void KoBasicHistogramProducer::makeExternalToInternal()
0072 {
0073     // This function assumes that the pixel is has no 'gaps'. That is to say: if we start
0074     // at byte 0, we can get to the end of the pixel by adding consecutive size()s of
0075     // the channels
0076     QList<KoChannelInfo *> c = channels();
0077     uint count = c.count();
0078     int currentPos = 0;
0079 
0080     for (uint i = 0; i < count; i++) {
0081         for (uint j = 0; j < count; j++) {
0082             if (c[j]->pos() == currentPos) {
0083                 m_external.append(j);
0084                 break;
0085             }
0086         }
0087         currentPos += c.at(m_external.at(m_external.count() - 1))->size();
0088     }
0089 }
0090 
0091 // ------------ U8 ---------------------
0092 
0093 KoBasicU8HistogramProducer::KoBasicU8HistogramProducer(const KoID& id, const KoColorSpace *cs)
0094     : KoBasicHistogramProducer(id, 256, cs)
0095 {
0096 }
0097 
0098 QString KoBasicU8HistogramProducer::positionToString(qreal pos) const
0099 {
0100     return QString("%1").arg(static_cast<quint8>(pos * UINT8_MAX));
0101 }
0102 
0103 void KoBasicU8HistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
0104 {
0105     quint32 dstPixelSize = m_colorSpace->pixelSize();
0106     quint8 *dstPixels = new quint8[nPixels * dstPixelSize];
0107     cs->convertPixelsTo(pixels, dstPixels, m_colorSpace, nPixels, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::Empty);
0108 
0109     if (selectionMask) {
0110         quint8 *dst = dstPixels;
0111         while (nPixels > 0) {
0112             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
0113 
0114                 for (int i = 0; i < (int)m_colorSpace->channelCount(); i++) {
0115                     m_bins[i][m_colorSpace->scaleToU8(dst,i)]++;
0116                 }
0117                 m_count++;
0118             }
0119             dst += dstPixelSize;
0120             selectionMask++;
0121             nPixels--;
0122         }
0123     } else {
0124         quint8 *dst = dstPixels;
0125         while (nPixels > 0) {
0126             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
0127 
0128                 for (int i = 0; i < (int)m_colorSpace->channelCount(); i++) {
0129                     m_bins[i][m_colorSpace->scaleToU8(dst,i)]++;
0130                 }
0131                 m_count++;
0132             }
0133             dst += dstPixelSize;
0134             nPixels--;
0135         }
0136     }
0137 }
0138 
0139 // ------------ U16 ---------------------
0140 
0141 KoBasicU16HistogramProducer::KoBasicU16HistogramProducer(const KoID& id, const KoColorSpace *cs)
0142     : KoBasicHistogramProducer(id, 256, cs)
0143 {
0144 }
0145 
0146 QString KoBasicU16HistogramProducer::positionToString(qreal pos) const
0147 {
0148     return QString("%1").arg(static_cast<quint8>(pos * UINT8_MAX));
0149 }
0150 
0151 qreal KoBasicU16HistogramProducer::maximalZoom() const
0152 {
0153     return 1.0 / 255.0;
0154 }
0155 
0156 void KoBasicU16HistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
0157 {
0158     // The view
0159     quint16 from = static_cast<quint16>(m_from * UINT16_MAX);
0160     quint16 width = static_cast<quint16>(m_width * UINT16_MAX + 0.5); // We include the end
0161     quint16 to = from + width;
0162     qreal factor = 255.0 / width;
0163 
0164     quint32 dstPixelSize = m_colorSpace->pixelSize();
0165     quint8 *dstPixels = new quint8[nPixels * dstPixelSize];
0166     cs->convertPixelsTo(pixels, dstPixels, m_colorSpace, nPixels, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::Empty);
0167     quint8 *dst = dstPixels;
0168     QVector<float> channels(m_colorSpace->channelCount());
0169 
0170     if (selectionMask) {
0171         while (nPixels > 0) {
0172             if (!((m_skipUnselected && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
0173                 m_colorSpace->normalisedChannelsValue(dst,channels);
0174                 for (int i = 0; i < (int)m_colorSpace->channelCount(); i++) {
0175                     quint16 value = channels[i]*UINT16_MAX;
0176                     if (value > to)
0177                         m_outRight[i]++;
0178                     else if (value < from)
0179                         m_outLeft[i]++;
0180                     else
0181                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
0182                 }
0183                 m_count++;
0184             }
0185             dst += dstPixelSize;
0186             selectionMask++;
0187             nPixels--;
0188         }
0189     } else {
0190         while (nPixels > 0) {
0191             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
0192                 m_colorSpace->normalisedChannelsValue(dst,channels);
0193                 for (int i = 0; i < (int)m_colorSpace->channelCount(); i++) {
0194                     quint16 value = channels[i]*UINT16_MAX;
0195 
0196                     if (value > to)
0197                         m_outRight[i]++;
0198                     else if (value < from)
0199                         m_outLeft[i]++;
0200                     else
0201                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
0202                 }
0203                 m_count++;
0204             }
0205             dst += dstPixelSize;
0206             nPixels--;
0207         }
0208     }
0209 }
0210 
0211 // ------------ Float32 ---------------------
0212 KoBasicF32HistogramProducer::KoBasicF32HistogramProducer(const KoID& id, const KoColorSpace *cs)
0213     : KoBasicHistogramProducer(id, 256, cs)
0214 {
0215 }
0216 
0217 QString KoBasicF32HistogramProducer::positionToString(qreal pos) const
0218 {
0219     return QString("%1").arg(static_cast<float>(pos)); // XXX I doubt this is correct!
0220 }
0221 
0222 qreal KoBasicF32HistogramProducer::maximalZoom() const
0223 {
0224     // XXX What _is_ the maximal zoom here? I don't think there is one with floats, so this seems a fine compromise for the moment
0225     return 1.0 / 255.0;
0226 }
0227 
0228 void KoBasicF32HistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
0229 {
0230     // The view
0231     float from = static_cast<float>(m_from);
0232     float width = static_cast<float>(m_width);
0233     float to = from + width;
0234     float factor = 255.0 / width;
0235 
0236     quint32 dstPixelSize = m_colorSpace->pixelSize();
0237     quint8 *dstPixels = new quint8[nPixels * dstPixelSize];
0238     cs->convertPixelsTo(pixels, dstPixels, m_colorSpace, nPixels, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::Empty);
0239     quint8 *dst = dstPixels;
0240     QVector<float> channels(m_colorSpace->channelCount());
0241 
0242     if (selectionMask) {
0243         while (nPixels > 0) {
0244             if (!((m_skipUnselected && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
0245                 m_colorSpace->normalisedChannelsValue(dst,channels);
0246                 for (int i = 0; i < (int)m_colorSpace->channelCount(); i++) {
0247                     float value = channels[i];
0248                     if (value > to)
0249                         m_outRight[i]++;
0250                     else if (value < from)
0251                         m_outLeft[i]++;
0252                     else
0253                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
0254                 }
0255                 m_count++;
0256             }
0257             dst += dstPixelSize;
0258             selectionMask++;
0259             nPixels--;
0260 
0261         }
0262     } else {
0263         while (nPixels > 0) {
0264             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
0265                 m_colorSpace->normalisedChannelsValue(dst,channels);
0266                 for (int i = 0; i < (int)m_colorSpace->channelCount(); i++) {
0267                     float value = channels[i];
0268                     if (value > to)
0269                         m_outRight[i]++;
0270                     else if (value < from)
0271                         m_outLeft[i]++;
0272                     else
0273                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
0274                 }
0275                 m_count++;
0276             }
0277             dst += dstPixelSize;
0278             nPixels--;
0279 
0280         }
0281     }
0282 }
0283 
0284 #ifdef HAVE_OPENEXR
0285 // ------------ Float16 Half ---------------------
0286 KoBasicF16HalfHistogramProducer::KoBasicF16HalfHistogramProducer(const KoID& id,
0287                                                                  const KoColorSpace *cs)
0288     : KoBasicHistogramProducer(id, 256, cs)
0289 {
0290 }
0291 
0292 QString KoBasicF16HalfHistogramProducer::positionToString(qreal pos) const
0293 {
0294     return QString("%1").arg(static_cast<float>(pos)); // XXX I doubt this is correct!
0295 }
0296 
0297 qreal KoBasicF16HalfHistogramProducer::maximalZoom() const
0298 {
0299     // XXX What _is_ the maximal zoom here? I don't think there is one with floats, so this seems a fine compromise for the moment
0300     return 1.0 / 255.0;
0301 }
0302 
0303 void KoBasicF16HalfHistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
0304 {
0305     // The view
0306     float from = static_cast<float>(m_from);
0307     float width = static_cast<float>(m_width);
0308     float to = from + width;
0309     float factor = 255.0 / width;
0310 
0311     quint32 dstPixelSize = m_colorSpace->pixelSize();
0312     quint8 *dstPixels = new quint8[nPixels * dstPixelSize];
0313     cs->convertPixelsTo(pixels, dstPixels, m_colorSpace, nPixels, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::Empty);
0314     quint8 *dst = dstPixels;
0315     QVector<float> channels(m_colorSpace->channelCount());
0316 
0317     if (selectionMask) {
0318         while (nPixels > 0) {
0319             if (!((m_skipUnselected  && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
0320                 m_colorSpace->normalisedChannelsValue(dst,channels);
0321                 for (int i = 0; i < (int)m_colorSpace->channelCount(); i++) {
0322                     float value = channels[i];
0323                     if (value > to)
0324                         m_outRight[i]++;
0325                     else if (value < from)
0326                         m_outLeft[i]++;
0327                     else
0328                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
0329                 }
0330                 m_count++;
0331             }
0332             dst += dstPixelSize;
0333             selectionMask++;
0334             nPixels--;
0335         }
0336     } else {
0337         while (nPixels > 0) {
0338             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
0339                 m_colorSpace->normalisedChannelsValue(dst,channels);
0340                 for (int i = 0; i < (int)m_colorSpace->channelCount(); i++) {
0341                     float value = channels[i];
0342                     if (value > to)
0343                         m_outRight[i]++;
0344                     else if (value < from)
0345                         m_outLeft[i]++;
0346                     else
0347                         m_bins[i][static_cast<quint8>((value - from) * factor)]++;
0348                 }
0349                 m_count++;
0350             }
0351             dst += dstPixelSize;
0352             nPixels--;
0353         }
0354     }
0355 }
0356 #endif
0357 
0358 // ------------ Generic RGB ---------------------
0359 KoGenericRGBHistogramProducer::KoGenericRGBHistogramProducer()
0360     : KoBasicHistogramProducer(KoID("GENRGBHISTO", i18n("Generic RGB Histogram")), 3, 256)
0361 {
0362     /* we set 0 as colorspece, because we are not based on a specific colorspace. This
0363        is no problem for the superclass since we override channels() */
0364     m_channelsList.append(new KoChannelInfo(i18n("R"), 0, 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(255, 0, 0)));
0365     m_channelsList.append(new KoChannelInfo(i18n("G"), 1, 1, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(0, 255, 0)));
0366     m_channelsList.append(new KoChannelInfo(i18n("B"), 2, 2, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(0, 0, 255)));
0367 }
0368 
0369 QList<KoChannelInfo *> KoGenericRGBHistogramProducer::channels()
0370 {
0371     return m_channelsList;
0372 }
0373 
0374 QString KoGenericRGBHistogramProducer::positionToString(qreal pos) const
0375 {
0376     return QString("%1").arg(static_cast<quint8>(pos * UINT8_MAX));
0377 }
0378 
0379 qreal KoGenericRGBHistogramProducer::maximalZoom() const
0380 {
0381     return 1.0;
0382 }
0383 
0384 
0385 void KoGenericRGBHistogramProducer::addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *cs)
0386 {
0387     for (int i = 0; i < m_channels; i++) {
0388         m_outRight[i] = 0;
0389         m_outLeft[i] = 0;
0390     }
0391 
0392     QColor c;
0393     qint32 pSize = cs->pixelSize();
0394     if (selectionMask) {
0395         while (nPixels > 0) {
0396             if (!((m_skipUnselected  && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
0397                 cs->toQColor(pixels, &c);
0398                 m_bins[0][c.red()]++;
0399                 m_bins[1][c.green()]++;
0400                 m_bins[2][c.blue()]++;
0401 
0402                 m_count++;
0403             }
0404             pixels += pSize;
0405             selectionMask++;
0406             nPixels--;
0407         }
0408 
0409     } else {
0410         while (nPixels > 0) {
0411 
0412             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8)) {
0413                 cs->toQColor(pixels, &c);
0414                 m_bins[0][c.red()]++;
0415                 m_bins[1][c.green()]++;
0416                 m_bins[2][c.blue()]++;
0417 
0418                 m_count++;
0419             }
0420             pixels += pSize;
0421             nPixels--;
0422         }
0423     }
0424 }
0425 
0426 KoGenericRGBHistogramProducerFactory::KoGenericRGBHistogramProducerFactory()
0427     : KoHistogramProducerFactory(KoID("GENRGBHISTO", i18n("Generic RGB Histogram")))
0428 {
0429 }
0430 
0431 // ------------ Generic L*a*b* ---------------------
0432 KoGenericLabHistogramProducer::KoGenericLabHistogramProducer()
0433     : KoBasicHistogramProducer(KoID("GENLABHISTO", i18n("L*a*b* Histogram")), 3, 256)
0434 {
0435     /* we set 0 as colorspace, because we are not based on a specific colorspace. This
0436        is no problem for the superclass since we override channels() */
0437     m_channelsList.append(new KoChannelInfo(i18n("L*"), 0, 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8));
0438     m_channelsList.append(new KoChannelInfo(i18n("a*"), 1, 1, KoChannelInfo::COLOR, KoChannelInfo::UINT8));
0439     m_channelsList.append(new KoChannelInfo(i18n("b*"), 2, 2, KoChannelInfo::COLOR, KoChannelInfo::UINT8));
0440 
0441     if (!m_labCs) {
0442         m_labCs = KoColorSpaceRegistry::instance()->lab16();
0443     }
0444     m_colorSpace = m_labCs;
0445 }
0446 KoGenericLabHistogramProducer::~KoGenericLabHistogramProducer()
0447 {
0448     delete m_channelsList[0];
0449     delete m_channelsList[1];
0450     delete m_channelsList[2];
0451 }
0452 
0453 QList<KoChannelInfo *> KoGenericLabHistogramProducer::channels()
0454 {
0455     return m_channelsList;
0456 }
0457 
0458 QString KoGenericLabHistogramProducer::positionToString(qreal pos) const
0459 {
0460     return QString("%1").arg(static_cast<quint16>(pos * UINT16_MAX));
0461 }
0462 
0463 qreal KoGenericLabHistogramProducer::maximalZoom() const
0464 {
0465     return 1.0;
0466 }
0467 
0468 
0469 void KoGenericLabHistogramProducer::addRegionToBin(const quint8 *pixels, const quint8 *selectionMask, quint32 nPixels,  const KoColorSpace *cs)
0470 {
0471     for (int i = 0; i < m_channels; i++) {
0472         m_outRight[i] = 0;
0473         m_outLeft[i] = 0;
0474     }
0475 
0476     qint32 dstPixelSize = m_colorSpace->pixelSize();
0477 
0478     quint8 *dstPixels = new quint8[nPixels * dstPixelSize];
0479     cs->convertPixelsTo(pixels, dstPixels, m_colorSpace, nPixels, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::Empty);
0480 
0481     qint32 pSize = cs->pixelSize();
0482 
0483     if (selectionMask) {
0484         while (nPixels > 0) {
0485             if (!((m_skipUnselected  && *selectionMask == 0) || (m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))) {
0486                 m_count++;
0487             }
0488             pixels += pSize;
0489             selectionMask++;
0490             nPixels--;
0491         }
0492     } else {
0493         quint8 *dst = dstPixels;
0494         while (nPixels > 0) {
0495             if (!(m_skipTransparent && cs->opacityU8(pixels) == OPACITY_TRANSPARENT_U8))  {
0496 
0497                 m_bins[0][m_colorSpace->scaleToU8(dst, 0)]++;
0498                 m_bins[1][m_colorSpace->scaleToU8(dst, 1)]++;
0499                 m_bins[2][m_colorSpace->scaleToU8(dst, 2)]++;
0500 
0501                 m_count++;
0502             }
0503             dst+= dstPixelSize;
0504             nPixels--;
0505         }
0506     }
0507     delete[] dstPixels;
0508 }
0509 
0510 KoGenericLabHistogramProducerFactory::KoGenericLabHistogramProducerFactory()
0511     : KoHistogramProducerFactory(KoID("GENLABHISTO", i18n("Generic L*a*b* Histogram")))
0512 {
0513 }