Artifact c1f2d6029d6347faa4303e02c00601ebe71036e8:

  • File src/ColorizeChooser.cxx — part of check-in [1062776205] at 2021-06-23 07:53:46 on branch color-params — ColorizeChooser: draw gradient to temporary bitmap (user: fifr size: 5792)

/*
 * Copyright (c) 2021 Frank Fischer <frank-fischer@shadow-soft.de>
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see  <http://www.gnu.org/licenses/>
 */

#include "ColorizeChooser.hxx"

#include <QDebug>
#include <QtGui/QPainter>

#include <algorithm>
#include <cmath>

#include <opencv2/imgproc.hpp>

#include "fifr/util/Range.hxx"

using namespace fifr::util;

struct ColorizeChooser::Data {
    int lattice = 100;
    cv::Mat hist = {};
    int maxRadius = 0;  // the maximal radius in the histogram of a pixel to be drawn

    std::vector<qreal> angles = {30, 90, 150, 210, 270, 330};
    int blackLevel = 50;

    QImage gradient = {};
};

ColorizeChooser::ColorizeChooser(QQuickItem* parent)
    : QQuickPaintedItem(parent), d(new Data)
{
}

ColorizeChooser::~ColorizeChooser() = default;

void ColorizeChooser::setLattice(int lattice)
{
    lattice = qBound(1, lattice, 100);
    if (lattice != d->lattice) {
        d->lattice = lattice;
        emit latticeChanged();
    }
}

int ColorizeChooser::lattice() const
{
    return d->lattice;
}

void ColorizeChooser::setBlackLevel(int blackLevel)
{
    blackLevel = qBound(0, blackLevel, 255);
    if (blackLevel != d->blackLevel) {
        d->blackLevel = blackLevel;
        emit blackLevelChanged();
        update();
    }
}

int ColorizeChooser::blackLevel() const
{
    return d->blackLevel;
}

void ColorizeChooser::updateImage(const cv::Mat& image, const cv::Mat& mask)
{
    if (image.empty()) return;

    int channels[] = {0, 1};
    int histSize[] = {d->lattice, d->lattice};
    float hranges[] = {0, 180};
    float sranges[] = {0, 256};
    const float* ranges[] = {hranges, sranges};
    cv::calcHist(&image, 1, channels, mask, d->hist, 2, histSize, ranges, true, false);
    cv::normalize(d->hist, d->hist, 0, 255, cv::NORM_MINMAX);

    d->maxRadius = 0;
    for (auto i : range(d->hist.rows)) {
        for (auto j : range(d->hist.cols)) {
            if (d->hist.at<float>(i, j) < 10) continue;
            d->maxRadius = std::max(j, d->maxRadius);
        }
    }

    update();
}

void ColorizeChooser::paint(QPainter* painter)
{
    auto size = std::min(width(), height());

    if (size != d->gradient.width()) {
        d->gradient = QImage(QSize{qRound(size), qRound(size)}, QImage::Format_ARGB32);
        d->gradient.fill(Qt::transparent);

        QConicalGradient gradient(width() / 2, height() / 2, 0);
        for (auto deg : range(360)) {
            gradient.setColorAt(deg / 360.0, QColor::fromHsvF(deg / 360.0, 0.75, 1));
        }

        QPainter p(&d->gradient);
        p.setBrush(gradient);
        p.drawEllipse(QPointF{size / 2, size / 2}, size / 2, size / 2);
    }

    painter->drawImage(QPointF{(width() - size) / 2, (height() - size) / 2}, d->gradient);
    painter->setBrush(Qt::black);

    int cur_segment = 0;
    int cur_angle = (d->angles.front() + d->angles.back()) / 2 - 180;
    if (cur_angle < 0) cur_angle += 360;
    auto cur_color = QColor::fromHsvF(cur_angle / 360.0, 1, 1);

    // the colored pixels
    for (auto i : range(d->hist.rows)) {
        auto deg = static_cast<double>(i) / d->hist.rows * 360;

        if (cur_segment < d->angles.size() && deg >= d->angles[cur_segment]) {
            if (cur_segment + 1 < d->angles.size()) {
                cur_angle = (d->angles[cur_segment] + d->angles[cur_segment + 1]) / 2;
            } else {
                cur_angle = (d->angles[cur_segment] + d->angles[0] + 360) / 2;
            }
            if (cur_angle < 0) cur_angle += 360;
            cur_segment += 1;
            cur_color = QColor::fromHsvF(cur_angle / 360.0, 1, 1);
        }

        auto black = (d->blackLevel * d->lattice + 255) / 256;
        painter->setBrush(Qt::black);

        for (auto j : range(d->maxRadius)) {
            if (j == black) painter->setBrush(cur_color);
            if (d->hist.at<float>(i, j) < 1) continue;
            auto r = size / d->lattice / 2;

            auto angle = deg / 180 * M_PI;  // angle in radiant
            auto radius = static_cast<double>(j) / d->maxRadius * size / 2;
            auto x = std::cos(angle) * radius + width() / 2;
            auto y = -std::sin(angle) * radius + height() / 2;
            painter->drawEllipse(QPointF{x, y}, r, r);
        }
    }

    // the angle lines
    painter->setPen(Qt::black);
    painter->setBrush({});
    auto r = d->blackLevel * d->lattice * size / (d->maxRadius * 512);
    painter->drawEllipse(QPointF{width() / 2, height() / 2}, r, r);
    for (auto angle : d->angles) {
        auto x = std::cos(angle / 180.0 * M_PI) * size / 2 + width() / 2;
        auto y = -std::sin(angle / 180.0 * M_PI) * size / 2 + height() / 2;
        painter->drawLine(width() / 2, height() / 2, x, y);
    }
}

void ColorizeChooser::setColorAngle(int which, qreal angle)
{
    which %= d->angles.size();
    if (which < 0) which += d->angles.size();
    if (d->angles[which] != angle) {
        d->angles[which] = angle;
        std::sort(d->angles.begin(), d->angles.end());
        emit colorAnglesChanged();
        update();
    }
}

qreal ColorizeChooser::colorAngle(int which) const
{
    which %= d->angles.size();
    if (which < 0) which += d->angles.size();
    return d->angles[which];
}