mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 03:27:45 +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
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) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2021, Felix Rauch <noreply@felixrau.ch>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PaletteWidget.h"
|
#include "PaletteWidget.h"
|
||||||
#include "ImageEditor.h"
|
#include "ImageEditor.h"
|
||||||
|
#include <AK/Result.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
|
#include <LibCore/File.h>
|
||||||
#include <LibGUI/BoxLayout.h>
|
#include <LibGUI/BoxLayout.h>
|
||||||
#include <LibGUI/ColorPicker.h>
|
#include <LibGUI/ColorPicker.h>
|
||||||
|
#include <LibGUI/MessageBox.h>
|
||||||
#include <LibGfx/Palette.h>
|
#include <LibGfx/Palette.h>
|
||||||
|
|
||||||
REGISTER_WIDGET(PixelPaint, PaletteWidget);
|
REGISTER_WIDGET(PixelPaint, PaletteWidget);
|
||||||
|
@ -29,6 +34,8 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual Color color() { return m_color; }
|
||||||
|
|
||||||
virtual void mousedown_event(GUI::MouseEvent& event) override
|
virtual void mousedown_event(GUI::MouseEvent& event) override
|
||||||
{
|
{
|
||||||
if (event.modifiers() & KeyModifier::Mod_Ctrl && event.button() == GUI::MouseButton::Left) {
|
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_relative_rect(rect);
|
||||||
m_primary_color_widget->set_fill_with_background_color(true);
|
m_primary_color_widget->set_fill_with_background_color(true);
|
||||||
|
|
||||||
auto& color_container = add<GUI::Widget>();
|
m_color_container = add<GUI::Widget>();
|
||||||
color_container.set_relative_rect(m_secondary_color_widget->relative_rect().right() + 2, 2, 500, 32);
|
m_color_container->set_relative_rect(m_secondary_color_widget->relative_rect().right() + 2, 2, 500, 32);
|
||||||
color_container.set_layout<GUI::VerticalBoxLayout>();
|
m_color_container->set_layout<GUI::VerticalBoxLayout>();
|
||||||
color_container.layout()->set_spacing(1);
|
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.set_layout<GUI::HorizontalBoxLayout>();
|
||||||
top_color_container.layout()->set_spacing(1);
|
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.set_layout<GUI::HorizontalBoxLayout>();
|
||||||
bottom_color_container.layout()->set_spacing(1);
|
bottom_color_container.layout()->set_spacing(1);
|
||||||
|
|
||||||
auto add_color_widget = [&](GUI::Widget& container, Color color) {
|
auto result = load_palette_file("/res/color-palettes/default.palette");
|
||||||
auto& color_widget = container.add<ColorWidget>(color, *this);
|
if (result.is_error()) {
|
||||||
color_widget.set_fill_with_background_color(true);
|
GUI::MessageBox::show_error(window(), String::formatted("Loading default palette failed: {}", result.error()));
|
||||||
auto pal = color_widget.palette();
|
display_color_list(fallback_colors());
|
||||||
pal.set_color(ColorRole::Background, color);
|
|
||||||
color_widget.set_palette(pal);
|
|
||||||
};
|
|
||||||
|
|
||||||
add_color_widget(top_color_container, Color::from_rgb(0x000000));
|
return;
|
||||||
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));
|
|
||||||
|
|
||||||
add_color_widget(bottom_color_container, Color::from_rgb(0xffffff));
|
display_color_list(result.value());
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaletteWidget::set_image_editor(ImageEditor& editor)
|
void PaletteWidget::set_image_editor(ImageEditor& editor)
|
||||||
|
@ -162,4 +143,113 @@ void PaletteWidget::set_secondary_color(Color color)
|
||||||
m_secondary_color_widget->update();
|
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) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2021, Felix Rauch <noreply@felixrau.ch>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Result.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
#include <LibGUI/Frame.h>
|
#include <LibGUI/Frame.h>
|
||||||
|
|
||||||
namespace PixelPaint {
|
namespace PixelPaint {
|
||||||
|
@ -21,6 +24,14 @@ public:
|
||||||
void set_primary_color(Color);
|
void set_primary_color(Color);
|
||||||
void set_secondary_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&);
|
void set_image_editor(ImageEditor&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -29,6 +40,7 @@ private:
|
||||||
ImageEditor* m_editor { nullptr };
|
ImageEditor* m_editor { nullptr };
|
||||||
RefPtr<GUI::Frame> m_primary_color_widget;
|
RefPtr<GUI::Frame> m_primary_color_widget;
|
||||||
RefPtr<GUI::Frame> m_secondary_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));
|
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");
|
auto& view_menu = menubar->add_menu("&View");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue