Serenity Operating System
1/*
2 * Copyright (c) 2022, Torsten Engelmann <engelTorsten@gmx.de>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "LevelsDialog.h"
8#include <Applications/PixelPaint/LevelsDialogGML.h>
9#include <LibGUI/Button.h>
10#include <LibGUI/Label.h>
11#include <LibGUI/ValueSlider.h>
12
13namespace PixelPaint {
14
15LevelsDialog::LevelsDialog(GUI::Window* parent_window, ImageEditor* editor)
16 : GUI::Dialog(parent_window)
17{
18 set_title("Levels");
19 set_icon(parent_window->icon());
20
21 auto main_widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors();
22 main_widget->load_from_gml(levels_dialog_gml).release_value_but_fixme_should_propagate_errors();
23
24 resize(305, 202);
25 set_resizable(false);
26
27 m_editor = editor;
28
29 m_brightness_slider = main_widget->find_descendant_of_type_named<GUI::ValueSlider>("brightness_slider");
30 m_contrast_slider = main_widget->find_descendant_of_type_named<GUI::ValueSlider>("contrast_slider");
31 m_gamma_slider = main_widget->find_descendant_of_type_named<GUI::ValueSlider>("gamma_slider");
32 auto context_label = main_widget->find_descendant_of_type_named<GUI::Label>("context_label");
33 auto apply_button = main_widget->find_descendant_of_type_named<GUI::Button>("apply_button");
34 auto cancel_button = main_widget->find_descendant_of_type_named<GUI::Button>("cancel_button");
35
36 VERIFY(m_brightness_slider);
37 VERIFY(m_contrast_slider);
38 VERIFY(m_gamma_slider);
39 VERIFY(context_label);
40 VERIFY(apply_button);
41 VERIFY(cancel_button);
42 VERIFY(m_editor->active_layer());
43
44 context_label->set_text(DeprecatedString::formatted("Working on layer: {}", m_editor->active_layer()->name()));
45 m_gamma_slider->set_value(100);
46
47 m_brightness_slider->on_change = [this](auto) {
48 generate_new_image();
49 };
50
51 m_contrast_slider->on_change = [this](auto) {
52 generate_new_image();
53 };
54
55 m_gamma_slider->on_change = [this](auto) {
56 generate_new_image();
57 };
58
59 apply_button->on_click = [this](auto) {
60 if (m_did_change)
61 m_editor->did_complete_action("Levels"sv);
62
63 cleanup_resources();
64 done(ExecResult::OK);
65 };
66
67 cancel_button->on_click = [this](auto) {
68 done(ExecResult::Cancel);
69 };
70}
71
72void LevelsDialog::revert_possible_changes()
73{
74 // FIXME: Find a faster way to revert all the changes that we have done.
75 if (m_did_change && m_reference_bitmap) {
76 for (int x = 0; x < m_reference_bitmap->width(); x++) {
77 for (int y = 0; y < m_reference_bitmap->height(); y++) {
78 m_editor->active_layer()->content_bitmap().set_pixel(x, y, m_reference_bitmap->get_pixel(x, y));
79 }
80 }
81 m_editor->layers_did_change();
82 }
83
84 cleanup_resources();
85}
86
87void LevelsDialog::generate_new_image()
88{
89 (void)ensure_reference_bitmap();
90 if (m_reference_bitmap.is_null())
91 return;
92
93 generate_precomputed_color_correction();
94 Color current_pixel_color;
95 Color new_pixel_color;
96 Gfx::StorageFormat storage_format = Gfx::determine_storage_format(m_editor->active_layer()->content_bitmap().format());
97
98 for (int x = 0; x < m_reference_bitmap->width(); x++) {
99 for (int y = 0; y < m_reference_bitmap->height(); y++) {
100 current_pixel_color = m_reference_bitmap->get_pixel(x, y);
101
102 new_pixel_color.set_alpha(current_pixel_color.alpha());
103 new_pixel_color.set_red(m_precomputed_color_correction[current_pixel_color.red()]);
104 new_pixel_color.set_green(m_precomputed_color_correction[current_pixel_color.green()]);
105 new_pixel_color.set_blue(m_precomputed_color_correction[current_pixel_color.blue()]);
106
107 switch (storage_format) {
108 case Gfx::StorageFormat::BGRx8888:
109 case Gfx::StorageFormat::BGRA8888:
110 m_editor->active_layer()->content_bitmap().scanline(y)[x] = new_pixel_color.value();
111 break;
112 default:
113 m_editor->active_layer()->content_bitmap().set_pixel(x, y, new_pixel_color);
114 }
115 }
116 }
117
118 m_editor->active_layer()->did_modify_bitmap();
119 m_did_change = true;
120}
121
122ErrorOr<void> LevelsDialog::ensure_reference_bitmap()
123{
124 if (m_reference_bitmap.is_null())
125 m_reference_bitmap = TRY(m_editor->active_layer()->content_bitmap().clone());
126
127 return {};
128}
129
130void LevelsDialog::cleanup_resources()
131{
132 if (m_reference_bitmap)
133 m_reference_bitmap = nullptr;
134}
135
136void LevelsDialog::generate_precomputed_color_correction()
137{
138 int delta_brightness = m_brightness_slider->value();
139 float contrast_correction_factor = static_cast<float>(259 * (m_contrast_slider->value() + 255) / static_cast<float>(255 * (259 - m_contrast_slider->value())));
140 float gamma_correction = 1 / (m_gamma_slider->value() / 100.0);
141
142 for (int color_val = 0; color_val < 256; color_val++) {
143 m_precomputed_color_correction[color_val] = color_val + delta_brightness;
144 m_precomputed_color_correction[color_val] = m_precomputed_color_correction[color_val] < 0 ? 0 : m_precomputed_color_correction[color_val];
145 m_precomputed_color_correction[color_val] = m_precomputed_color_correction[color_val] > 255 ? 255 : m_precomputed_color_correction[color_val];
146
147 m_precomputed_color_correction[color_val] = 255 * AK::pow<float>((m_precomputed_color_correction[color_val] / 255.0), gamma_correction);
148
149 m_precomputed_color_correction[color_val] = contrast_correction_factor * (m_precomputed_color_correction[color_val] - 128) + 128;
150 m_precomputed_color_correction[color_val] = m_precomputed_color_correction[color_val] < 0 ? 0 : m_precomputed_color_correction[color_val];
151 m_precomputed_color_correction[color_val] = m_precomputed_color_correction[color_val] > 255 ? 255 : m_precomputed_color_correction[color_val];
152 }
153}
154
155}