aseprite/src/commands/cmd_palette_editor.cpp

699 lines
20 KiB
C++
Raw Normal View History

2007-11-16 18:25:45 +00:00
/* ASE - Allegro Sprite Editor
2011-01-18 23:49:53 +00:00
* Copyright (C) 2001-2011 David Capello
2007-09-23 20:13:58 +00:00
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include <allegro.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include "app.h"
#include "app/color.h"
#include "app/color_utils.h"
#include "base/bind.h"
#include "commands/command.h"
#include "commands/params.h"
#include "console.h"
#include "core/cfg.h"
#include "dialogs/filesel.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "gfx/size.h"
#include "gui/gui.h"
#include "gui/graphics.h"
#include "modules/editors.h"
#include "modules/gui.h"
#include "modules/palettes.h"
#include "raster/image.h"
#include "raster/palette.h"
#include "raster/quantization.h"
#include "raster/sprite.h"
#include "raster/stock.h"
#include "raster/undo.h"
#include "skin/skin_slider_property.h"
#include "sprite_wrappers.h"
#include "ui_context.h"
2010-08-23 21:11:47 +00:00
#include "widgets/color_bar.h"
#include "widgets/color_sliders.h"
#include "widgets/editor.h"
#include "widgets/palette_view.h"
#include "widgets/hex_color_entry.h"
#include "widgets/statebar.h"
using namespace gfx;
class PaletteEntryEditor : public Frame
{
public:
PaletteEntryEditor();
~PaletteEntryEditor();
void setColor(const Color& color);
protected:
bool onProcessMessage(JMessage msg);
void onExit();
void onCloseFrame();
void onFgBgColorChange(const Color& color);
void onColorSlidersChange(ColorSlidersChangeEvent& ev);
void onColorHexEntryChange(const Color& color);
void onColorTypeButtonClick(Event& ev);
void onMoreOptionsClick(Event& ev);
void onLoadCommand(Event& ev);
void onSaveCommand(Event& ev);
void onRampCommand(Event& ev);
void onQuantizeCommand(Event& ev);
private:
void selectColorType(Color::Type type);
void setPaletteEntry(const Color& color);
void setPaletteEntryChannel(const Color& color, ColorSliders::Channel channel);
void setNewPalette(Palette* palette, const char* operationName);
void updateCurrentSpritePalette(const char* operationName);
void updateColorBar();
Box m_vbox;
Box m_topBox;
Box m_bottomBox;
RadioButton m_rgbButton;
RadioButton m_hsvButton;
HexColorEntry m_hexColorEntry;
Button m_moreOptions;
RgbSliders m_rgbSliders;
HsvSliders m_hsvSliders;
Button m_loadButton;
Button m_saveButton;
Button m_rampButton;
Button m_quantizeButton;
// This variable is used to avoid updating the m_hexColorEntry text
// when the color change is generated from a
// HexColorEntry::ColorChange signal. In this way we don't override
// what the user is writting in the text field.
bool m_disableHexUpdate;
int m_redrawTimerId;
bool m_redrawAll;
};
//////////////////////////////////////////////////////////////////////
// PaletteEditorCommand
static PaletteEntryEditor* g_frame = NULL;
class PaletteEditorCommand : public Command
{
public:
PaletteEditorCommand();
Command* clone() { return new PaletteEditorCommand(*this); }
protected:
void onLoadParams(Params* params);
void onExecute(Context* context);
private:
bool m_open;
bool m_close;
bool m_switch;
bool m_background;
};
PaletteEditorCommand::PaletteEditorCommand()
2011-01-20 23:46:58 +00:00
: Command("PaletteEditor",
"PaletteEditor",
CmdRecordableFlag)
{
m_open = true;
m_close = false;
m_switch = false;
m_background = false;
}
void PaletteEditorCommand::onLoadParams(Params* params)
{
std::string target = params->get("target");
if (target == "foreground") m_background = false;
else if (target == "background") m_background = true;
std::string open_str = params->get("open");
if (open_str == "true") m_open = true;
else m_open = false;
std::string close_str = params->get("close");
if (close_str == "true") m_close = true;
else m_close = false;
std::string switch_str = params->get("switch");
if (switch_str == "true") m_switch = true;
else m_switch = false;
}
void PaletteEditorCommand::onExecute(Context* context)
{
// If this is the first time the command is execute...
if (!g_frame) {
// If the command says "Close the palette editor" and it is not
// created yet, we just do nothing.
if (m_close)
return;
// If this is "open" or "switch", we have to create the frame.
g_frame = new PaletteEntryEditor();
}
// If the frame is already created and it's visible, close it (only in "switch" or "close" modes)
else if (g_frame->isVisible() && (m_switch || m_close)) {
// Hide the frame
g_frame->closeWindow(NULL);
return;
}
if (m_switch || m_open) {
if (!g_frame->isVisible()) {
// Default bounds
g_frame->remap_window();
int width = MAX(jrect_w(g_frame->rc), JI_SCREEN_W/2);
g_frame->setBounds(Rect(JI_SCREEN_W - width - jrect_w(app_get_toolbar()->rc),
JI_SCREEN_H - jrect_h(g_frame->rc) - jrect_h(app_get_statusbar()->rc),
width, jrect_h(g_frame->rc)));
// Load window configuration
load_window_pos(g_frame, "PaletteEditor");
}
// Run the frame in background.
g_frame->open_window_bg();
}
// Show the specified target color
{
Color color =
(m_background ? context->getSettings()->getBgColor():
context->getSettings()->getFgColor());
g_frame->setColor(color);
}
}
//////////////////////////////////////////////////////////////////////
// PaletteEntryEditor implementation
//
// Based on ColorSelector class.
PaletteEntryEditor::PaletteEntryEditor()
: Frame(false, "Palette Editor (F4)")
, m_vbox(JI_VERTICAL)
, m_topBox(JI_HORIZONTAL)
, m_bottomBox(JI_HORIZONTAL)
, m_rgbButton("RGB", 1, JI_BUTTON)
, m_hsvButton("HSV", 1, JI_BUTTON)
, m_moreOptions("+")
, m_loadButton("Load")
, m_saveButton("Save")
, m_rampButton("Ramp")
, m_quantizeButton("Quantize")
, m_disableHexUpdate(false)
, m_redrawAll(false)
{
m_redrawTimerId = jmanager_add_timer(this, 250);
m_topBox.setBorder(gfx::Border(0));
m_topBox.child_spacing = 0;
m_bottomBox.setBorder(gfx::Border(0));
setup_mini_look(&m_rgbButton);
setup_mini_look(&m_hsvButton);
setup_mini_look(&m_moreOptions);
setup_mini_look(&m_loadButton);
setup_mini_look(&m_saveButton);
setup_mini_look(&m_rampButton);
setup_mini_look(&m_quantizeButton);
// Top box
jwidget_add_child(&m_topBox, &m_rgbButton);
jwidget_add_child(&m_topBox, &m_hsvButton);
jwidget_add_child(&m_topBox, &m_hexColorEntry);
{
Box* filler = new Box(JI_HORIZONTAL);
jwidget_expansive(filler, true);
jwidget_add_child(&m_topBox, filler);
}
jwidget_add_child(&m_topBox, &m_moreOptions);
// Bottom box
{
Box* box = new Box(JI_HORIZONTAL);
box->child_spacing = 0;
jwidget_add_child(box, &m_loadButton);
jwidget_add_child(box, &m_saveButton);
jwidget_add_child(&m_bottomBox, box);
}
jwidget_add_child(&m_bottomBox, &m_rampButton);
jwidget_add_child(&m_bottomBox, &m_quantizeButton);
// Main vertical box
jwidget_add_child(&m_vbox, &m_topBox);
jwidget_add_child(&m_vbox, &m_rgbSliders);
jwidget_add_child(&m_vbox, &m_hsvSliders);
jwidget_add_child(&m_vbox, &m_bottomBox);
jwidget_add_child(this, &m_vbox);
// Hide (or show) the "More Options" depending the saved value in .cfg file
m_bottomBox.setVisible(get_config_bool("PaletteEditor", "ShowMoreOptions", false));
m_rgbButton.Click.connect(&PaletteEntryEditor::onColorTypeButtonClick, this);
m_hsvButton.Click.connect(&PaletteEntryEditor::onColorTypeButtonClick, this);
m_moreOptions.Click.connect(&PaletteEntryEditor::onMoreOptionsClick, this);
m_loadButton.Click.connect(&PaletteEntryEditor::onLoadCommand, this);
m_saveButton.Click.connect(&PaletteEntryEditor::onSaveCommand, this);
m_rampButton.Click.connect(&PaletteEntryEditor::onRampCommand, this);
m_quantizeButton.Click.connect(&PaletteEntryEditor::onQuantizeCommand, this);
m_rgbSliders.ColorChange.connect(&PaletteEntryEditor::onColorSlidersChange, this);
m_hsvSliders.ColorChange.connect(&PaletteEntryEditor::onColorSlidersChange, this);
m_hexColorEntry.ColorChange.connect(&PaletteEntryEditor::onColorHexEntryChange, this);
selectColorType(Color::RgbType);
// We hook fg/bg color changes (by eyedropper mainly) to update the selected entry color
app_get_colorbar()->FgColorChange.connect(&PaletteEntryEditor::onFgBgColorChange, this);
app_get_colorbar()->BgColorChange.connect(&PaletteEntryEditor::onFgBgColorChange, this);
// We hook the Frame::Close event to save the frame position before closing it.
this->Close.connect(Bind<void>(&PaletteEntryEditor::onCloseFrame, this));
// We hook App::Exit signal to destroy the g_frame singleton at exit.
App::instance()->Exit.connect(&PaletteEntryEditor::onExit, this);
initTheme();
}
PaletteEntryEditor::~PaletteEntryEditor()
{
jmanager_remove_timer(m_redrawTimerId);
m_redrawTimerId = -1;
}
void PaletteEntryEditor::setColor(const Color& color)
{
m_rgbSliders.setColor(color);
m_hsvSliders.setColor(color);
if (!m_disableHexUpdate)
m_hexColorEntry.setColor(color);
}
bool PaletteEntryEditor::onProcessMessage(JMessage msg)
{
if (msg->type == JM_TIMER &&
msg->timer.timer_id == m_redrawTimerId) {
// Redraw all editors
if (m_redrawAll) {
m_redrawAll = false;
jmanager_stop_timer(m_redrawTimerId);
try {
const CurrentSpriteReader sprite(UIContext::instance());
update_editors_with_sprite(sprite);
}
catch (...) {
// Do nothing
}
}
// Redraw just the current editor
else {
m_redrawAll = true;
current_editor->editor_update();
}
}
return Frame::onProcessMessage(msg);
}
void PaletteEntryEditor::onExit()
{
delete this;
}
void PaletteEntryEditor::onCloseFrame()
{
// Save window configuration
save_window_pos(this, "PaletteEditor");
}
void PaletteEntryEditor::onFgBgColorChange(const Color& color)
{
if (color.isValid() && color.getType() == Color::IndexType) {
setColor(color);
}
}
void PaletteEntryEditor::onColorSlidersChange(ColorSlidersChangeEvent& ev)
{
setColor(ev.getColor());
setPaletteEntryChannel(ev.getColor(), ev.getModifiedChannel());
updateCurrentSpritePalette("Color Change");
updateColorBar();
}
void PaletteEntryEditor::onColorHexEntryChange(const Color& color)
{
// Disable updating the hex entry so we don't override what the user
// is writting in the text field.
m_disableHexUpdate = true;
setColor(color);
setPaletteEntry(color);
updateCurrentSpritePalette("Color Change");
updateColorBar();
m_disableHexUpdate = false;
}
void PaletteEntryEditor::onColorTypeButtonClick(Event& ev)
{
RadioButton* source = static_cast<RadioButton*>(ev.getSource());
if (source == &m_rgbButton) selectColorType(Color::RgbType);
else if (source == &m_hsvButton) selectColorType(Color::HsvType);
}
void PaletteEntryEditor::onMoreOptionsClick(Event& ev)
{
Size reqSize;
if (m_bottomBox.isVisible()) {
set_config_bool("PaletteEditor", "ShowMoreOptions", false);
m_bottomBox.setVisible(false);
// Get the required size of the "More options" panel
reqSize = m_bottomBox.getPreferredSize();
reqSize.h += 4;
// Remove the space occupied by the "More options" panel
{
JRect rect = jrect_new(rc->x1, rc->y1,
rc->x2, rc->y2 - reqSize.h);
move_window(rect);
jrect_free(rect);
}
}
else {
set_config_bool("PaletteEditor", "ShowMoreOptions", true);
m_bottomBox.setVisible(true);
// Get the required size of the whole window
reqSize = getPreferredSize();
// Add space for the "more_options" panel
if (jrect_h(rc) < reqSize.h) {
JRect rect = jrect_new(rc->x1, rc->y1,
rc->x2, rc->y1 + reqSize.h);
// Show the expanded area inside the screen
if (rect->y2 > JI_SCREEN_H)
jrect_displace(rect, 0, JI_SCREEN_H - rect->y2);
move_window(rect);
jrect_free(rect);
}
else
setBounds(getBounds()); // TODO layout() method is missing
}
// Redraw the window
invalidate();
}
void PaletteEntryEditor::onLoadCommand(Event& ev)
{
Palette *palette;
base::string filename = ase_file_selector("Load Palette", "", "png,pcx,bmp,tga,lbm,col");
if (!filename.empty()) {
palette = Palette::load(filename.c_str());
if (!palette) {
2011-01-27 20:21:33 +00:00
Alert::show("Error<<Loading palette file||&Close");
}
else {
setNewPalette(palette, "Load Palette");
delete palette;
}
}
}
void PaletteEntryEditor::onSaveCommand(Event& ev)
{
base::string filename;
int ret;
again:
filename = ase_file_selector("Save Palette", "", "png,pcx,bmp,tga,col");
if (!filename.empty()) {
if (exists(filename.c_str())) {
2011-01-27 20:21:33 +00:00
ret = Alert::show("Warning<<File exists, overwrite it?<<%s||&Yes||&No||&Cancel",
get_filename(filename.c_str()));
if (ret == 2)
goto again;
else if (ret != 1)
return;
}
Palette* palette = get_current_palette();
if (!palette->save(filename.c_str())) {
2011-01-27 20:21:33 +00:00
Alert::show("Error<<Saving palette file||&Close");
}
}
}
void PaletteEntryEditor::onRampCommand(Event& ev)
{
PaletteView* palette_editor = app_get_colorbar()->getPaletteView();
int range_type = palette_editor->getRangeType();
int i1 = palette_editor->get1stColor();
int i2 = palette_editor->get2ndColor();
Palette* src_palette = get_current_palette();
Palette* dst_palette = new Palette(0, 256);
bool array[256];
palette_editor->getSelectedEntries(array);
src_palette->copyColorsTo(dst_palette);
if ((i1 >= 0) && (i2 >= 0)) {
// Make the ramp
if (range_type == PALETTE_EDITOR_RANGE_LINEAL) {
// Lineal ramp
dst_palette->makeHorzRamp(i1, i2);
}
else if (range_type == PALETTE_EDITOR_RANGE_RECTANGULAR) {
// Rectangular ramp
dst_palette->makeRectRamp(i1, i2, palette_editor->getColumns());
}
}
setNewPalette(dst_palette, "Color Ramp");
delete dst_palette;
}
void PaletteEntryEditor::onQuantizeCommand(Event& ev)
{
Palette* palette = NULL;
{
const CurrentSpriteReader sprite(UIContext::instance());
if (sprite == NULL) {
2011-01-27 20:21:33 +00:00
Alert::show("Error<<There is no sprite selected to quantize.||&OK");
return;
}
if (sprite->getImgType() != IMAGE_RGB) {
2011-01-27 20:21:33 +00:00
Alert::show("Error<<You can use this command only for RGB sprites||&OK");
return;
}
palette = quantization::create_palette_from_rgb(sprite);
}
setNewPalette(palette, "Quantize Palette");
delete palette;
}
void PaletteEntryEditor::setPaletteEntry(const Color& color)
{
bool array[256];
PaletteView* palView = app_get_colorbar()->getPaletteView();
palView->getSelectedEntries(array);
ase_uint32 new_pal_color = _rgba(color.getRed(),
color.getGreen(),
color.getBlue(), 255);
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
if (array[c])
palette->setEntry(c, new_pal_color);
}
}
void PaletteEntryEditor::setPaletteEntryChannel(const Color& color, ColorSliders::Channel channel)
{
bool array[256];
PaletteView* palView = app_get_colorbar()->getPaletteView();
palView->getSelectedEntries(array);
ase_uint32 src_color;
int r, g, b;
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
if (array[c]) {
// Get the current RGB values of the palette entry
src_color = palette->getEntry(c);
r = _rgba_getr(src_color);
g = _rgba_getg(src_color);
b = _rgba_getb(src_color);
switch (color.getType()) {
case Color::RgbType:
// Setup the new RGB values depending of the modified channel.
switch (channel) {
case ColorSliders::Red:
r = color.getRed();
case ColorSliders::Green:
g = color.getGreen();
break;
case ColorSliders::Blue:
b = color.getBlue();
break;
}
break;
case Color::HsvType:
{
// Convert RGB to HSV
Hsv hsv(Rgb(r, g, b));
// Only modify the desired HSV channel
switch (channel) {
case ColorSliders::Hue:
hsv.hue(color.getHue());
break;
case ColorSliders::Saturation:
hsv.saturation(double(color.getSaturation()) / 100.0);
break;
case ColorSliders::Value:
hsv.value(double(color.getValue()) / 100.0);
break;
}
// Convert HSV back to RGB
Rgb rgb(hsv);
r = rgb.red();
g = rgb.green();
b = rgb.blue();
}
break;
}
palette->setEntry(c, _rgba(r, g, b, 255));
}
}
}
void PaletteEntryEditor::selectColorType(Color::Type type)
{
m_rgbSliders.setVisible(type == Color::RgbType);
m_hsvSliders.setVisible(type == Color::HsvType);
switch (type) {
case Color::RgbType: m_rgbButton.setSelected(true); break;
case Color::HsvType: m_hsvButton.setSelected(true); break;
}
m_vbox.layout();
m_vbox.invalidate();
}
void PaletteEntryEditor::setNewPalette(Palette* palette, const char* operationName)
{
// Copy the palette
palette->copyColorsTo(get_current_palette());
// Set the palette calling the hooks
set_current_palette(palette, false);
// Update the sprite palette
updateCurrentSpritePalette(operationName);
// Redraw the entire screen
jmanager_refresh_screen();
}
void PaletteEntryEditor::updateCurrentSpritePalette(const char* operationName)
{
if (UIContext::instance()->getCurrentSprite()) {
try {
CurrentSpriteWriter sprite(UIContext::instance());
Palette* newPalette = get_current_palette(); // System current pal
Palette* currentSpritePalette = sprite->getPalette(sprite->getCurrentFrame()); // Sprite current pal
int from, to;
// Check differences between current sprite palette and current system palette
from = to = -1;
currentSpritePalette->countDiff(newPalette, &from, &to);
if (from >= 0 && to >= from) {
// Add undo information to save the range of pal entries that will be modified.
if (sprite->getUndo()->isEnabled()) {
sprite->getUndo()->setLabel(operationName);
sprite->getUndo()->undo_set_palette_colors(sprite, currentSpritePalette, from, to);
}
// Change the sprite palette
sprite->setPalette(newPalette, false);
}
}
catch (base::Exception& e) {
Console::showException(e);
}
}
PaletteView* palette_editor = app_get_colorbar()->getPaletteView();
palette_editor->invalidate();
if (!jmanager_timer_is_running(m_redrawTimerId))
jmanager_start_timer(m_redrawTimerId);
m_redrawAll = false;
}
void PaletteEntryEditor::updateColorBar()
{
app_get_colorbar()->invalidate();
}
//////////////////////////////////////////////////////////////////////
// CommandFactory
2011-01-20 23:46:58 +00:00
Command* CommandFactory::createPaletteEditorCommand()
{
return new PaletteEditorCommand;
}