mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-11 08:50:37 +00:00
PixelPaint: Add loading and saving of color palettes
Color palettes can now be stored in and read from files. The default palette will be read from `/res/color-palettes/default.palette` instead of being hard-coded in PaletteWidget. The file format is one color per line, in any format that can be understood by `Gfx::Color::from_string`.
This commit is contained in:
parent
5ac9494483
commit
8d91dbf6c0
Notes:
sideshowbarker
2024-07-19 17:26:59 +09:00
Author: https://github.com/RauchF 🔰 Commit: https://github.com/SerenityOS/serenity/commit/8d91dbf6c07 Pull-request: https://github.com/SerenityOS/serenity/pull/8189 Reviewed-by: https://github.com/Dexesttp ✅
5 changed files with 213 additions and 41 deletions
28
Base/res/color-palettes/default.palette
Normal file
28
Base/res/color-palettes/default.palette
Normal file
|
@ -0,0 +1,28 @@
|
|||
#000000
|
||||
#808080
|
||||
#800000
|
||||
#808000
|
||||
#008000
|
||||
#008080
|
||||
#000080
|
||||
#800080
|
||||
#808040
|
||||
#004040
|
||||
#0080ff
|
||||
#004080
|
||||
#8000ff
|
||||
#804000
|
||||
#ffffff
|
||||
#c0c0c0
|
||||
#ff0000
|
||||
#ffff00
|
||||
#00ff00
|
||||
#00ffff
|
||||
#0000ff
|
||||
#ff00ff
|
||||
#ffff80
|
||||
#00ff80
|
||||
#80ffff
|
||||
#8080ff
|
||||
#ff0080
|
||||
#ff8040
|
16
Base/res/color-palettes/greyscale.palette
Normal file
16
Base/res/color-palettes/greyscale.palette
Normal file
|
@ -0,0 +1,16 @@
|
|||
#000000
|
||||
#111111
|
||||
#222222
|
||||
#333333
|
||||
#444444
|
||||
#555555
|
||||
#666666
|
||||
#777777
|
||||
#888888
|
||||
#999999
|
||||
#aaaaaa
|
||||
#bbbbbb
|
||||
#cccccc
|
||||
#dddddd
|
||||
#eeeeee
|
||||
#ffffff
|
|
@ -1,13 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Felix Rauch <noreply@felixrau.ch>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "PaletteWidget.h"
|
||||
#include "ImageEditor.h"
|
||||
#include <AK/Result.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/ColorPicker.h>
|
||||
#include <LibGUI/MessageBox.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
|
||||
REGISTER_WIDGET(PixelPaint, PaletteWidget);
|
||||
|
@ -29,6 +34,8 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual Color color() { return m_color; }
|
||||
|
||||
virtual void mousedown_event(GUI::MouseEvent& event) override
|
||||
{
|
||||
if (event.modifiers() & KeyModifier::Mod_Ctrl && event.button() == GUI::MouseButton::Left) {
|
||||
|
@ -73,56 +80,30 @@ PaletteWidget::PaletteWidget()
|
|||
m_primary_color_widget->set_relative_rect(rect);
|
||||
m_primary_color_widget->set_fill_with_background_color(true);
|
||||
|
||||
auto& color_container = add<GUI::Widget>();
|
||||
color_container.set_relative_rect(m_secondary_color_widget->relative_rect().right() + 2, 2, 500, 32);
|
||||
color_container.set_layout<GUI::VerticalBoxLayout>();
|
||||
color_container.layout()->set_spacing(1);
|
||||
m_color_container = add<GUI::Widget>();
|
||||
m_color_container->set_relative_rect(m_secondary_color_widget->relative_rect().right() + 2, 2, 500, 32);
|
||||
m_color_container->set_layout<GUI::VerticalBoxLayout>();
|
||||
m_color_container->layout()->set_spacing(1);
|
||||
|
||||
auto& top_color_container = color_container.add<GUI::Widget>();
|
||||
auto& top_color_container = m_color_container->add<GUI::Widget>();
|
||||
top_color_container.set_name("top_color_container");
|
||||
top_color_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
top_color_container.layout()->set_spacing(1);
|
||||
|
||||
auto& bottom_color_container = color_container.add<GUI::Widget>();
|
||||
auto& bottom_color_container = m_color_container->add<GUI::Widget>();
|
||||
bottom_color_container.set_name("bottom_color_container");
|
||||
bottom_color_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
bottom_color_container.layout()->set_spacing(1);
|
||||
|
||||
auto add_color_widget = [&](GUI::Widget& container, Color color) {
|
||||
auto& color_widget = container.add<ColorWidget>(color, *this);
|
||||
color_widget.set_fill_with_background_color(true);
|
||||
auto pal = color_widget.palette();
|
||||
pal.set_color(ColorRole::Background, color);
|
||||
color_widget.set_palette(pal);
|
||||
};
|
||||
auto result = load_palette_file("/res/color-palettes/default.palette");
|
||||
if (result.is_error()) {
|
||||
GUI::MessageBox::show_error(window(), String::formatted("Loading default palette failed: {}", result.error()));
|
||||
display_color_list(fallback_colors());
|
||||
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x000000));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x808080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x800000));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x808000));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x008000));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x008080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x000080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x800080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x808040));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x004040));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x0080ff));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x004080));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x8000ff));
|
||||
add_color_widget(top_color_container, Color::from_rgb(0x804000));
|
||||
return;
|
||||
}
|
||||
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xffffff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xc0c0c0));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xff0000));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xffff00));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x00ff00));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x00ffff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x0000ff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xff00ff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xffff80));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x00ff80));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x80ffff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0x8080ff));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xff0080));
|
||||
add_color_widget(bottom_color_container, Color::from_rgb(0xff8040));
|
||||
display_color_list(result.value());
|
||||
}
|
||||
|
||||
void PaletteWidget::set_image_editor(ImageEditor& editor)
|
||||
|
@ -162,4 +143,113 @@ void PaletteWidget::set_secondary_color(Color color)
|
|||
m_secondary_color_widget->update();
|
||||
}
|
||||
|
||||
void PaletteWidget::display_color_list(Vector<Color> const& colors)
|
||||
{
|
||||
int colors_to_add = colors.size();
|
||||
if (colors_to_add == 0) {
|
||||
dbgln("Empty color list given. Using fallback colors.");
|
||||
display_color_list(fallback_colors());
|
||||
return;
|
||||
}
|
||||
|
||||
auto& top_color_container = *m_color_container->find_descendant_of_type_named<GUI::Widget>("top_color_container");
|
||||
top_color_container.remove_all_children();
|
||||
|
||||
auto& bottom_color_container = *m_color_container->find_descendant_of_type_named<GUI::Widget>("bottom_color_container");
|
||||
bottom_color_container.remove_all_children();
|
||||
|
||||
auto add_color_widget = [&](GUI::Widget& container, Color color) {
|
||||
auto& color_widget = container.add<ColorWidget>(color, *this);
|
||||
color_widget.set_fill_with_background_color(true);
|
||||
auto pal = color_widget.palette();
|
||||
pal.set_color(ColorRole::Background, color);
|
||||
color_widget.set_palette(pal);
|
||||
};
|
||||
|
||||
int colors_per_row = ceil(colors_to_add / 2);
|
||||
int number_of_added_colors = 0;
|
||||
for (auto& color : colors) {
|
||||
if (number_of_added_colors < colors_per_row)
|
||||
add_color_widget(top_color_container, color);
|
||||
else
|
||||
add_color_widget(bottom_color_container, color);
|
||||
|
||||
++number_of_added_colors;
|
||||
}
|
||||
}
|
||||
|
||||
Vector<Color> PaletteWidget::colors()
|
||||
{
|
||||
Vector<Color> colors;
|
||||
|
||||
for (auto& color_container : m_color_container->child_widgets()) {
|
||||
color_container.for_each_child_of_type<ColorWidget>([&](auto& color_widget) {
|
||||
colors.append(color_widget.color());
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
Result<Vector<Color>, String> PaletteWidget::load_palette_file(String const& file_path)
|
||||
{
|
||||
auto file_or_error = Core::File::open(file_path, Core::OpenMode::ReadOnly);
|
||||
if (file_or_error.is_error())
|
||||
return file_or_error.error();
|
||||
|
||||
auto& file = *file_or_error.value();
|
||||
|
||||
Vector<Color> palette;
|
||||
|
||||
while (file.can_read_line()) {
|
||||
auto line = file.read_line();
|
||||
if (line.is_whitespace())
|
||||
continue;
|
||||
|
||||
auto color = Color::from_string(line);
|
||||
if (!color.has_value()) {
|
||||
dbgln("Could not parse \"{}\" as a color", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
palette.append(color.value());
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (palette.is_empty())
|
||||
return String { "The palette file did not contain any usable colors"sv };
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
Result<void, String> PaletteWidget::save_palette_file(Vector<Color> palette, String const& file_path)
|
||||
{
|
||||
auto file_or_error = Core::File::open(file_path, Core::OpenMode::WriteOnly);
|
||||
if (file_or_error.is_error())
|
||||
return file_or_error.error();
|
||||
|
||||
auto& file = *file_or_error.value();
|
||||
|
||||
for (auto& color : palette) {
|
||||
file.write(color.to_string_without_alpha());
|
||||
file.write("\n");
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Vector<Color> PaletteWidget::fallback_colors()
|
||||
{
|
||||
Vector<Color> fallback_colors;
|
||||
|
||||
fallback_colors.append(Color::from_rgb(0x000000));
|
||||
fallback_colors.append(Color::from_rgb(0xffffff));
|
||||
|
||||
return fallback_colors;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Felix Rauch <noreply@felixrau.ch>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Result.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGUI/Frame.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
@ -21,6 +24,14 @@ public:
|
|||
void set_primary_color(Color);
|
||||
void set_secondary_color(Color);
|
||||
|
||||
void display_color_list(Vector<Color> const&);
|
||||
|
||||
Vector<Color> colors();
|
||||
|
||||
static Result<Vector<Color>, String> load_palette_file(String const&);
|
||||
static Result<void, String> save_palette_file(Vector<Color>, String const&);
|
||||
static Vector<Color> fallback_colors();
|
||||
|
||||
void set_image_editor(ImageEditor&);
|
||||
|
||||
private:
|
||||
|
@ -29,6 +40,7 @@ private:
|
|||
ImageEditor* m_editor { nullptr };
|
||||
RefPtr<GUI::Frame> m_primary_color_widget;
|
||||
RefPtr<GUI::Frame> m_secondary_color_widget;
|
||||
RefPtr<GUI::Widget> m_color_container;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -265,6 +265,32 @@ int main(int argc, char** argv)
|
|||
}
|
||||
},
|
||||
window));
|
||||
edit_menu.add_action(GUI::Action::create(
|
||||
"&Load Color Palette", [&](auto&) {
|
||||
auto open_path = GUI::FilePicker::get_open_filepath(window, "Load Color Palette");
|
||||
if (!open_path.has_value())
|
||||
return;
|
||||
|
||||
auto result = PixelPaint::PaletteWidget::load_palette_file(open_path.value());
|
||||
if (result.is_error()) {
|
||||
GUI::MessageBox::show_error(window, String::formatted("Loading color palette failed: {}", result.error()));
|
||||
return;
|
||||
}
|
||||
|
||||
palette_widget.display_color_list(result.value());
|
||||
},
|
||||
window));
|
||||
edit_menu.add_action(GUI::Action::create(
|
||||
"Sa&ve Color Palette", [&](auto&) {
|
||||
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "palette");
|
||||
if (!save_path.has_value())
|
||||
return;
|
||||
|
||||
auto result = PixelPaint::PaletteWidget::save_palette_file(palette_widget.colors(), save_path.value());
|
||||
if (result.is_error())
|
||||
GUI::MessageBox::show_error(window, String::formatted("Writing color palette failed: {}", result.error()));
|
||||
},
|
||||
window));
|
||||
|
||||
auto& view_menu = menubar->add_menu("&View");
|
||||
|
||||
|
|
Loading…
Reference in a new issue