Serenity Operating System
1/*
2 * Copyright (c) 2022, Torsten Engelmann
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "HistogramWidget.h"
8#include "Image.h"
9#include "ImageEditor.h"
10#include "Layer.h"
11#include <LibGUI/Painter.h>
12#include <LibGfx/Palette.h>
13#include <LibGfx/Path.h>
14
15REGISTER_WIDGET(PixelPaint, HistogramWidget);
16
17namespace PixelPaint {
18
19ErrorOr<void> HistogramWidget::rebuild_histogram_data()
20{
21 if (!m_image)
22 return {};
23
24 auto full_bitmap = TRY(m_image->compose_bitmap(Gfx::BitmapFormat::BGRA8888));
25
26 m_data.red.clear_with_capacity();
27 m_data.green.clear_with_capacity();
28 m_data.blue.clear_with_capacity();
29 m_data.brightness.clear_with_capacity();
30
31 for (int i = 0; i < 256; i++) {
32 m_data.red.append(0);
33 m_data.green.append(0);
34 m_data.blue.append(0);
35 m_data.brightness.append(0);
36 }
37
38 Color pixel_color;
39 for (int x = 0; x < full_bitmap->width(); x++) {
40 for (int y = 0; y < full_bitmap->height(); y++) {
41 pixel_color = full_bitmap->get_pixel(x, y);
42 if (!pixel_color.alpha())
43 continue;
44
45 m_data.red[pixel_color.red()]++;
46 m_data.green[pixel_color.green()]++;
47 m_data.blue[pixel_color.blue()]++;
48 m_data.brightness[pixel_color.luminosity()]++;
49 }
50 }
51 int max_brightness_frequency = 0;
52 int max_color_frequency = 0;
53 for (int i = 0; i < 256; i++) {
54 if (m_data.red[i] > max_color_frequency)
55 max_color_frequency = m_data.red[i];
56 if (m_data.green[i] > max_color_frequency)
57 max_color_frequency = m_data.green[i];
58 if (m_data.blue[i] > max_color_frequency)
59 max_color_frequency = m_data.blue[i];
60 if (m_data.brightness[i] > max_brightness_frequency)
61 max_brightness_frequency = m_data.brightness[i];
62 }
63
64 // Scale the frequency values to fit the widgets height.
65 auto widget_height = height();
66
67 for (int i = 0; i < 256; i++) {
68 m_data.red[i] = m_data.red[i] != 0 ? (static_cast<float>(m_data.red[i]) / max_color_frequency) * widget_height : 0;
69 m_data.green[i] = m_data.green[i] != 0 ? (static_cast<float>(m_data.green[i]) / max_color_frequency) * widget_height : 0;
70 m_data.blue[i] = m_data.blue[i] != 0 ? (static_cast<float>(m_data.blue[i]) / max_color_frequency) * widget_height : 0;
71 m_data.brightness[i] = m_data.brightness[i] != 0 ? (static_cast<float>(m_data.brightness[i]) / max_brightness_frequency) * widget_height : 0;
72 }
73
74 return {};
75}
76
77void HistogramWidget::paint_event(GUI::PaintEvent& event)
78{
79 GUI::Painter painter(*this);
80 painter.add_clip_rect(event.rect());
81
82 if (!m_image)
83 return;
84
85 int bottom_line = height() - 1;
86 float step_width = static_cast<float>(width()) / 256;
87
88 Gfx::Path brightness_path;
89 Gfx::Path red_channel_path;
90 Gfx::Path green_channel_path;
91 Gfx::Path blue_channel_path;
92 red_channel_path.move_to({ 0, bottom_line - m_data.red[0] });
93 green_channel_path.move_to({ 0, bottom_line - m_data.green[0] });
94 blue_channel_path.move_to({ 0, bottom_line - m_data.blue[0] });
95 brightness_path.move_to({ 0, bottom_line });
96 brightness_path.line_to({ 0, bottom_line });
97
98 float current_x_as_float = 0;
99 int current_x_as_int = 0;
100 int last_drawn_x = -1;
101
102 for (int data_column = 0; data_column < 256; data_column++) {
103 current_x_as_int = static_cast<int>(current_x_as_float);
104 // we would like to skip values that map to the same x position as it does not look so good in the final result
105 if (current_x_as_int == last_drawn_x) {
106 current_x_as_float += step_width;
107 continue;
108 }
109
110 red_channel_path.line_to({ current_x_as_int, bottom_line - m_data.red[data_column] });
111 green_channel_path.line_to({ current_x_as_int, bottom_line - m_data.green[data_column] });
112 blue_channel_path.line_to({ current_x_as_int, bottom_line - m_data.blue[data_column] });
113 brightness_path.line_to({ current_x_as_int, bottom_line - m_data.brightness[data_column] });
114
115 current_x_as_float += step_width;
116 last_drawn_x = current_x_as_int;
117 }
118
119 brightness_path.line_to({ last_drawn_x, bottom_line });
120 brightness_path.close();
121
122 painter.fill_path(brightness_path, Color::MidGray, Gfx::Painter::WindingRule::EvenOdd);
123 painter.stroke_path(red_channel_path, Color(Color::NamedColor::Red).with_alpha(90), 2);
124 painter.stroke_path(green_channel_path, Color(Color::NamedColor::Green).with_alpha(90), 2);
125 painter.stroke_path(blue_channel_path, Color(Color::NamedColor::Blue).with_alpha(90), 2);
126
127 if (m_color_at_mouseposition != Color::Transparent) {
128 int x = m_color_at_mouseposition.luminosity() * step_width;
129 painter.draw_line({ x, 0 }, { x, bottom_line }, Color::from_hsl(45, 1, .7), 1);
130 }
131}
132
133void HistogramWidget::image_changed()
134{
135 (void)rebuild_histogram_data();
136 update();
137}
138
139}