diff --git a/Userland/Applications/PixelPaint/CMakeLists.txt b/Userland/Applications/PixelPaint/CMakeLists.txt index 76b224bbf3..91956db580 100644 --- a/Userland/Applications/PixelPaint/CMakeLists.txt +++ b/Userland/Applications/PixelPaint/CMakeLists.txt @@ -10,6 +10,7 @@ compile_gml(EditGuideDialog.gml EditGuideDialogGML.h edit_guide_dialog_gml) compile_gml(FilterGallery.gml FilterGalleryGML.h filter_gallery_gml) compile_gml(ResizeImageDialog.gml ResizeImageDialogGML.h resize_image_dialog_gml) compile_gml(LevelsDialog.gml LevelsDialogGML.h levels_dialog_gml) +compile_gml(Filters/MedianSettings.gml Filters/MedianSettingsGML.h median_settings_gml) set(SOURCES CreateNewImageDialog.cpp @@ -31,6 +32,8 @@ set(SOURCES Filters/Invert.cpp Filters/LaplaceCardinal.cpp Filters/LaplaceDiagonal.cpp + Filters/Median.cpp + Filters/MedianSettingsGML.h Filters/Sepia.cpp Filters/Sharpen.cpp HistogramWidget.cpp diff --git a/Userland/Applications/PixelPaint/FilterTreeModel.cpp b/Userland/Applications/PixelPaint/FilterTreeModel.cpp index 07133f1e83..fd58238e99 100644 --- a/Userland/Applications/PixelPaint/FilterTreeModel.cpp +++ b/Userland/Applications/PixelPaint/FilterTreeModel.cpp @@ -17,6 +17,7 @@ #include "Filters/Invert.h" #include "Filters/LaplaceCardinal.h" #include "Filters/LaplaceDiagonal.h" +#include "Filters/Median.h" #include "Filters/Sepia.h" #include "Filters/Sharpen.h" #include @@ -51,6 +52,7 @@ ErrorOr> create_filter_tree_model(ImageEditor* add_filter_node.template operator()(blur_category); add_filter_node.template operator()(blur_category); add_filter_node.template operator()(blur_category); + add_filter_node.template operator()(blur_category); auto color_category = filter_tree_model->add_node("Color", directory_icon); add_filter_node.template operator()(color_category); diff --git a/Userland/Applications/PixelPaint/Filters/Median.cpp b/Userland/Applications/PixelPaint/Filters/Median.cpp new file mode 100644 index 0000000000..f051e00469 --- /dev/null +++ b/Userland/Applications/PixelPaint/Filters/Median.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Median.h" +#include +#include +#include + +namespace PixelPaint::Filters { + +void Median::apply(Gfx::Bitmap& target_bitmap, Gfx::Bitmap const& source_bitmap) const +{ + // FIXME: Is there a better way to work around aliasing in the source and target? + auto target = MUST(source_bitmap.clone()); + + int filter_size = static_cast(this->filter_size()); + for (int x = 0; x < target_bitmap.width(); ++x) { + for (int y = 0; y < target_bitmap.height(); ++y) { + int left = x - static_cast(m_filter_radius - 1); + int top = y - static_cast(m_filter_radius - 1); + Vector values; + values.ensure_capacity(static_cast(filter_size * filter_size)); + for (int i = left; i < left + filter_size; ++i) { + for (int j = top; j < top + filter_size; ++j) { + if (j < 0 || i < 0 || j >= source_bitmap.height() || i >= source_bitmap.width()) + continue; + values.unchecked_append(source_bitmap.get_pixel(i, j)); + } + } + // FIXME: If there was an insertion sort in AK, we should better use that here. + // Sort the values to be able to extract the median. The median is determined by grey value (luminosity). + quick_sort(values, [](auto& a, auto& b) { return a.luminosity() < b.luminosity(); }); + target->set_pixel(x, y, values[values.size() / 2]); + } + } + + // FIXME: Can we move the `target`s data into the actual target bitmap? Can't be too hard, right? + Gfx::Painter painter(target_bitmap); + painter.blit({}, target, target->rect()); +} + +RefPtr Median::get_settings_widget() +{ + if (!m_settings_widget) { + m_settings_widget = GUI::Widget::construct(); + m_settings_widget->load_from_gml(median_settings_gml); + m_settings_widget->find_descendant_of_type_named("filter_radius")->on_change = [this](auto value) { + m_filter_radius = value; + update_preview(); + }; + } + + return m_settings_widget; +} + +} diff --git a/Userland/Applications/PixelPaint/Filters/Median.h b/Userland/Applications/PixelPaint/Filters/Median.h new file mode 100644 index 0000000000..e41e9e2776 --- /dev/null +++ b/Userland/Applications/PixelPaint/Filters/Median.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Filter.h" +#include + +namespace PixelPaint::Filters { + +class Median : public Filter { +public: + virtual void apply(Gfx::Bitmap& target_bitmap, Gfx::Bitmap const& source_bitmap) const override; + + virtual RefPtr get_settings_widget() override; + + virtual StringView filter_name() override { return "Median Filter"sv; } + + Median(ImageEditor* editor) + : Filter(editor) + { + } + +private: + unsigned filter_size() const { return m_filter_radius * 2 - 1; } + + unsigned m_filter_radius { 2 }; +}; + +} diff --git a/Userland/Applications/PixelPaint/Filters/MedianSettings.gml b/Userland/Applications/PixelPaint/Filters/MedianSettings.gml new file mode 100644 index 0000000000..62295bb191 --- /dev/null +++ b/Userland/Applications/PixelPaint/Filters/MedianSettings.gml @@ -0,0 +1,16 @@ +@GUI::Widget { + fill_with_background_color: true + layout: @GUI::HorizontalBoxLayout {} + + @GUI::Label { + text: "Median filter radius" + text_alignment: "CenterLeft" + width: "shrink" + } + + @GUI::SpinBox { + name: "filter_radius" + min: 1 + max: 5000 + } +}