mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 21:42:43 +00:00 
			
		
		
		
	PixelPaint: Add Median filter
The median filter replaces a pixel with the median of all pixels (usually grey value is used) in a square neighborhood. This is a standard image processing filter used for denoising, as despite its simplicity it can e.g. retain edges quite well. The first implementation is quite inefficient mostly to environmental constraints. Due to how images are passed to the processing function, two unnecessary copies happen. And because there's no fast sorting algorithm for small arrays (insertion sort) yet, quick sort needs to be used which is quite slow on this scale.
This commit is contained in:
		
							parent
							
								
									1712b6b3ed
								
							
						
					
					
						commit
						ec52d16f7a
					
				
					 5 changed files with 113 additions and 0 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
|  | @ -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 <LibGUI/FileIconProvider.h> | ||||
|  | @ -51,6 +52,7 @@ ErrorOr<NonnullRefPtr<GUI::TreeViewModel>> create_filter_tree_model(ImageEditor* | |||
|     add_filter_node.template operator()<Filters::BoxBlur3>(blur_category); | ||||
|     add_filter_node.template operator()<Filters::BoxBlur5>(blur_category); | ||||
|     add_filter_node.template operator()<Filters::Sharpen>(blur_category); | ||||
|     add_filter_node.template operator()<Filters::Median>(blur_category); | ||||
| 
 | ||||
|     auto color_category = filter_tree_model->add_node("Color", directory_icon); | ||||
|     add_filter_node.template operator()<Filters::Grayscale>(color_category); | ||||
|  |  | |||
							
								
								
									
										59
									
								
								Userland/Applications/PixelPaint/Filters/Median.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								Userland/Applications/PixelPaint/Filters/Median.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org> | ||||
|  * | ||||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #include "Median.h" | ||||
| #include <AK/QuickSort.h> | ||||
| #include <Applications/PixelPaint/Filters/MedianSettingsGML.h> | ||||
| #include <LibGUI/SpinBox.h> | ||||
| 
 | ||||
| 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<int>(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<int>(m_filter_radius - 1); | ||||
|             int top = y - static_cast<int>(m_filter_radius - 1); | ||||
|             Vector<Color, 16> values; | ||||
|             values.ensure_capacity(static_cast<size_t>(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<GUI::Widget> 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<GUI::SpinBox>("filter_radius")->on_change = [this](auto value) { | ||||
|             m_filter_radius = value; | ||||
|             update_preview(); | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     return m_settings_widget; | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										33
									
								
								Userland/Applications/PixelPaint/Filters/Median.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Userland/Applications/PixelPaint/Filters/Median.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org> | ||||
|  * | ||||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "Filter.h" | ||||
| #include <LibGUI/Widget.h> | ||||
| 
 | ||||
| namespace PixelPaint::Filters { | ||||
| 
 | ||||
| class Median : public Filter { | ||||
| public: | ||||
|     virtual void apply(Gfx::Bitmap& target_bitmap, Gfx::Bitmap const& source_bitmap) const override; | ||||
| 
 | ||||
|     virtual RefPtr<GUI::Widget> 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 }; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										16
									
								
								Userland/Applications/PixelPaint/Filters/MedianSettings.gml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Userland/Applications/PixelPaint/Filters/MedianSettings.gml
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 kleines Filmröllchen
						kleines Filmröllchen