/* * Copyright (c) 2023, Torsten Engelmann * * SPDX-License-Identifier: BSD-2-Clause */ #include "ImageMasking.h" #include #include #include #include #include #include #include #include namespace PixelPaint { ImageMasking::ImageMasking(GUI::Window* parent_window, ImageEditor* editor) : GUI::Dialog(parent_window) { set_title("Luminosity Mask"); set_icon(parent_window->icon()); auto main_widget = set_main_widget().release_value_but_fixme_should_propagate_errors(); main_widget->load_from_gml(luminosity_masking_gml).release_value_but_fixme_should_propagate_errors(); resize(300, 170); set_resizable(false); m_editor = editor; m_full_masking_slider = main_widget->find_descendant_of_type_named("full_masking"); m_edge_masking_slider = main_widget->find_descendant_of_type_named("edge_masking"); auto range_illustration_container = main_widget->find_descendant_of_type_named("range_illustration"); auto mask_visibility = main_widget->find_descendant_of_type_named("mask_visibility"); auto apply_button = main_widget->find_descendant_of_type_named("apply_button"); auto cancel_button = main_widget->find_descendant_of_type_named("cancel_button"); VERIFY(m_full_masking_slider); VERIFY(m_edge_masking_slider); VERIFY(range_illustration_container); VERIFY(mask_visibility); VERIFY(apply_button); VERIFY(cancel_button); VERIFY(m_editor->active_layer()); m_full_masking_slider->set_gradient_color(Color(0, 0, 0, 255), Color(255, 255, 255, 255)); m_edge_masking_slider->set_gradient_color(Color(0, 0, 0, 255), Color(255, 255, 255, 255)); auto illustration_widget = range_illustration_container->try_add(m_edge_masking_slider, m_full_masking_slider).release_value(); illustration_widget->set_width(range_illustration_container->width()); illustration_widget->set_height(range_illustration_container->height()); // check that edges of full and edge masking are not intersecting, and refine the mask with the updated values m_full_masking_slider->on_range_change = [this, illustration_widget](int lower, int upper) { if (lower < m_edge_masking_slider->lower_range()) m_full_masking_slider->set_lower_range(AK::max(lower, m_edge_masking_slider->lower_range())); if (upper > m_edge_masking_slider->upper_range()) m_full_masking_slider->set_upper_range(AK::min(upper, m_edge_masking_slider->upper_range())); illustration_widget->update(); generate_new_mask(); }; m_edge_masking_slider->on_range_change = [this, illustration_widget](int lower, int upper) { if (lower > m_full_masking_slider->lower_range()) m_edge_masking_slider->set_lower_range(AK::min(lower, m_full_masking_slider->lower_range())); if (upper < m_full_masking_slider->upper_range()) m_edge_masking_slider->set_upper_range(AK::max(upper, m_full_masking_slider->upper_range())); illustration_widget->update(); generate_new_mask(); }; mask_visibility->set_checked(m_editor->active_layer()->mask_visibility()); mask_visibility->on_checked = [this](auto checked) { m_editor->active_layer()->set_mask_visibility(checked); m_editor->update(); }; apply_button->on_click = [this](auto) { if (m_did_change) m_editor->did_complete_action("Luminosity Masking"sv); cleanup_resources(); done(ExecResult::OK); }; cancel_button->on_click = [this](auto) { done(ExecResult::Cancel); }; generate_new_mask(); } void ImageMasking::revert_possible_changes() { if (m_did_change && m_reference_mask) { MUST(m_editor->active_layer()->set_bitmaps(m_editor->active_layer()->content_bitmap(), m_reference_mask.release_nonnull())); m_editor->layers_did_change(); } cleanup_resources(); } void ImageMasking::generate_new_mask() { ensure_reference_mask().release_value_but_fixme_should_propagate_errors(); if (m_reference_mask.is_null()) return; int min_luminosity_start = m_edge_masking_slider->lower_range(); int min_luminosity_full = m_full_masking_slider->lower_range(); int max_luminosity_full = m_full_masking_slider->upper_range(); int max_luminosity_end = m_edge_masking_slider->upper_range(); int current_content_luminosity, approximation_alpha; bool has_start_range = min_luminosity_start != min_luminosity_full; bool has_end_range = max_luminosity_end != max_luminosity_full; Gfx::Color reference_mask_pixel, content_pixel; for (int y = 0; y < m_reference_mask->height(); y++) { for (int x = 0; x < m_reference_mask->width(); x++) { reference_mask_pixel = m_reference_mask->get_pixel(x, y); if (!reference_mask_pixel.alpha()) continue; content_pixel = m_editor->active_layer()->content_bitmap().get_pixel(x, y); current_content_luminosity = content_pixel.luminosity(); if (!content_pixel.alpha() || current_content_luminosity < min_luminosity_start || current_content_luminosity > max_luminosity_end) { reference_mask_pixel.set_alpha(0); } else if (current_content_luminosity >= min_luminosity_start && current_content_luminosity < min_luminosity_full && has_start_range) { approximation_alpha = reference_mask_pixel.alpha() * static_cast((current_content_luminosity - min_luminosity_start)) / (min_luminosity_full - min_luminosity_start); reference_mask_pixel.set_alpha(approximation_alpha); } else if (current_content_luminosity > max_luminosity_full && current_content_luminosity <= max_luminosity_end && has_end_range) { approximation_alpha = reference_mask_pixel.alpha() * (1 - static_cast((current_content_luminosity - max_luminosity_full)) / (max_luminosity_end - max_luminosity_full)); reference_mask_pixel.set_alpha(approximation_alpha); } m_editor->active_layer()->mask_bitmap()->set_pixel(x, y, reference_mask_pixel); } } m_editor->active_layer()->did_modify_bitmap(); m_did_change = true; } ErrorOr ImageMasking::ensure_reference_mask() { if (m_reference_mask.is_null()) m_reference_mask = TRY(m_editor->active_layer()->mask_bitmap()->clone()); return {}; } void ImageMasking::cleanup_resources() { if (m_reference_mask) m_reference_mask = nullptr; } void RangeIllustrationWidget::paint_event(GUI::PaintEvent&) { GUI::Painter painter(*this); painter.fill_rect(Gfx::IntRect(0, 0, width(), height()), palette().color(background_role())); float fraction = width() / 255.0f; Gfx::Path illustration; illustration.move_to({ fraction * m_edge_mask_values->lower_range(), static_cast(height()) }); illustration.line_to({ fraction * m_full_mask_values->lower_range(), 0 }); illustration.line_to({ fraction * m_full_mask_values->upper_range(), 0 }); illustration.line_to({ fraction * m_edge_mask_values->upper_range(), static_cast(height()) }); illustration.close(); painter.fill_path(illustration, Color::MidGray); } }