summaryrefslogtreecommitdiff
path: root/Userland/Applications/PixelPaint/VectorscopeWidget.h
blob: 03c9a7e37ca7852881c4d7203c0705e29ca20354 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
 * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include "Image.h"
#include "ScopeWidget.h"
#include <AK/Array.h>

namespace PixelPaint {

// Gfx::Color can produce 64-bit floating-point HSV.
// However, as it internally only uses 8 bits for each color channel, the hue can never have a higher usable resolution than 256 steps.
static constexpr size_t const u_v_steps = 160;

// Convert to and from U or V (-1 to +1) and an index suitable for the vectorscope table.
constexpr size_t u_v_to_index(float u_v)
{
    float normalized_u_v = (u_v + 1.0f) / 2.0f;
    return static_cast<size_t>(floorf(normalized_u_v * u_v_steps)) % u_v_steps;
}
constexpr float u_v_from_index(size_t index)
{
    float normalized_u_v = static_cast<float>(index) / u_v_steps;
    return normalized_u_v * 2.0f - 1.0f;
}

struct PrimaryColorVector;

struct ColorVector {
    constexpr ColorVector(float u, float v)
        : u(u)
        , v(v)
    {
    }

    constexpr explicit ColorVector(Color color)
        : ColorVector(color.to_yuv().u, color.to_yuv().v)
    {
    }

    static constexpr ColorVector from_indices(size_t u_index, size_t v_index)
    {
        return ColorVector(u_v_from_index(u_index), u_v_from_index(v_index));
    }

    constexpr Gfx::FloatPoint to_vector(float scope_size) const
    {
        auto x = u * scope_size / 2.0f;
        // Computer graphics y increases downwards, but mathematical y increases upwards.
        auto y = -v * scope_size / 2.0f;
        return { x, y };
    }

    constexpr operator PrimaryColorVector() const;

    float u;
    float v;
};

struct PrimaryColorVector : public ColorVector {
    constexpr PrimaryColorVector(Color::NamedColor named_color, char symbol)
        : ColorVector(Color { named_color })
        , symbol(symbol)
    {
    }

    constexpr PrimaryColorVector(Color color, char symbol)
        : ColorVector(color.to_yuv().u, color.to_yuv().v)
        , symbol(symbol)
    {
    }

    constexpr PrimaryColorVector(float u, float v, char symbol)
        : ColorVector(u, v)
        , symbol(symbol)
    {
    }

    char symbol;
};

constexpr ColorVector::operator PrimaryColorVector() const
{
    return PrimaryColorVector { u, v, 'X' };
}

// Color vectors that are found in this percentage of pixels and above are displayed with maximum brightness in the scope.
static constexpr float const pixel_percentage_for_max_brightness = 0.01f;
// Which normalized brightness value (and above) gets to be rendered at 100% opacity.
static constexpr float const alpha_range = 2.5f;
// Skin tone line. This was determined manually with a couple of common hex skin tone colors.
static constexpr PrimaryColorVector const skin_tone_color = { Color::from_hsv(18.0, 1.0, 1.0), 'S' };
// Used for primary color box graticules.
static constexpr Array<PrimaryColorVector, 6> const primary_colors = { {
    { Color::Red, 'R' },
    { Color::Magenta, 'M' },
    { Color::Blue, 'B' },
    { Color::Cyan, 'C' },
    { Color::Green, 'G' },
    { Color::Yellow, 'Y' },
} };

// Vectorscopes are a standard tool in professional video/film color grading.
// The Vectorscope shows image colors along the I and Q axis (from YIQ color space), which, to oversimplify, means that you get a weirdly shifted hue circle with the radius being the saturation.
// The brightness for each point in the scope is determined by the number of "color vectors" at that point.
// FIXME: We would want a lot of the scope settings to be user-adjustable. For example: scale, color/bw scope, graticule brightness
class VectorscopeWidget final
    : public ScopeWidget {
    C_OBJECT(VectorscopeWidget);

public:
    virtual ~VectorscopeWidget() override = default;

    virtual void image_changed() override;

private:
    virtual void paint_event(GUI::PaintEvent&) override;

    ErrorOr<void> rebuild_vectorscope_data();
    void rebuild_vectorscope_image();

    // First index is u, second index is v. The value is y.
    Array<Array<float, u_v_steps + 1>, u_v_steps + 1> m_vectorscope_data;
    RefPtr<Gfx::Bitmap> m_vectorscope_image;
};

}