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:
Felix Rauch 2021-06-21 16:14:27 +02:00 committed by Andreas Kling
parent 5ac9494483
commit 8d91dbf6c0
Notes: sideshowbarker 2024-07-19 17:26:59 +09:00
5 changed files with 213 additions and 41 deletions

View 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

View file

@ -0,0 +1,16 @@
#000000
#111111
#222222
#333333
#444444
#555555
#666666
#777777
#888888
#999999
#aaaaaa
#bbbbbb
#cccccc
#dddddd
#eeeeee
#ffffff

View file

@ -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;
}
}

View file

@ -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;
};
}

View file

@ -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");