aseprite/src/app/commands/cmd_palette_editor.cpp

773 lines
21 KiB
C++
Raw Normal View History

2015-02-12 23:16:25 +08:00
// Aseprite
// Copyright (C) 2001-2016 David Capello
2015-02-12 23:16:25 +08:00
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/cmd/set_palette.h"
#include "app/cmd_sequence.h"
#include "app/color.h"
#include "app/color_utils.h"
#include "app/commands/command.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/context_access.h"
#include "app/document_undo.h"
#include "app/file_selector.h"
#include "app/ini_file.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "app/transaction.h"
#include "app/ui/color_bar.h"
#include "app/ui/color_sliders.h"
#include "app/ui/editor/editor.h"
#include "app/ui/hex_color_entry.h"
#include "app/ui/palette_view.h"
#include "app/ui/skin/skin_slider_property.h"
#include "app/ui/status_bar.h"
#include "app/ui/toolbar.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "base/fs.h"
#include "base/path.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "gfx/size.h"
2012-06-18 09:49:58 +08:00
#include "ui/graphics.h"
#include "ui/ui.h"
#include <cstdio>
#include <cstring>
#include <vector>
namespace app {
using namespace gfx;
using namespace ui;
enum { RGB_MODE, HSV_MODE };
enum { ABS_MODE, REL_MODE };
class PaletteEntryEditor : public Window {
public:
PaletteEntryEditor();
void setColor(const app::Color& color);
protected:
bool onProcessMessage(Message* msg) override;
void onExit();
2012-07-09 10:24:42 +08:00
void onCloseWindow();
void onFgBgColorChange(const app::Color& _color);
void onColorSlidersChange(ColorSlidersChangeEvent& ev);
void onColorHexEntryChange(const app::Color& color);
void onColorTypeClick();
void onChangeModeClick();
private:
void selectColorType(app::Color::Type type);
void setPaletteEntry(const app::Color& color);
2015-05-11 09:53:36 +08:00
void setAbsolutePaletteEntryChannel(ColorSliders::Channel channel, const app::Color& color);
void setRelativePaletteEntryChannel(ColorSliders::Channel channel, int delta);
void setNewPalette(Palette* palette, const char* operationName);
void updateCurrentSpritePalette(const char* operationName);
void updateColorBar();
void updateWidgetsFromSelectedEntries();
void onPalChange();
2015-05-11 09:53:36 +08:00
void resetRelativeInfo();
void getPicks(PalettePicks& picks);
2015-05-11 09:53:36 +08:00
app::Color::Type m_type;
Box m_vbox;
Box m_topBox;
Box m_bottomBox;
ButtonSet m_colorType;
ButtonSet m_changeMode;
HexColorEntry m_hexColorEntry;
Label m_entryLabel;
RgbSliders m_rgbSliders;
HsvSliders m_hsvSliders;
// 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;
ui::Timer m_redrawTimer;
bool m_redrawAll;
// True if the palette change must be implant in the UndoHistory
// (e.g. when two or more changes in the palette are made in short
// time).
bool m_implantChange;
// True if the PaletteChange signal is generated by the same
// PaletteEntryEditor instance.
bool m_selfPalChange;
base::ScopedConnection m_palChangeConn;
2015-05-11 09:53:36 +08:00
// Palette used for relative changes.
Palette m_fromPalette;
std::map<ColorSliders::Channel, int> m_relDeltas;
};
2012-07-09 10:24:42 +08:00
static PaletteEntryEditor* g_window = NULL;
class PaletteEditorCommand : public Command {
public:
PaletteEditorCommand();
Command* clone() const override { return new PaletteEditorCommand(*this); }
protected:
void onLoadParams(const Params& params) override;
void onExecute(Context* context) override;
bool onChecked(Context* context) override;
private:
bool m_open;
bool m_close;
bool m_switch;
bool m_background;
};
PaletteEditorCommand::PaletteEditorCommand()
: Command("PaletteEditor",
"Palette Editor",
CmdRecordableFlag)
{
m_open = true;
m_close = false;
m_switch = false;
m_background = false;
}
void PaletteEditorCommand::onLoadParams(const 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...
2012-07-09 10:24:42 +08:00
if (!g_window) {
// 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.
2012-07-09 10:24:42 +08:00
g_window = new PaletteEntryEditor();
}
// If the frame is already created and it's visible, close it (only in "switch" or "close" modes)
2012-07-09 10:24:42 +08:00
else if (g_window->isVisible() && (m_switch || m_close)) {
// Hide the frame
2012-07-09 10:24:42 +08:00
g_window->closeWindow(NULL);
return;
}
if (m_switch || m_open) {
2012-07-09 10:24:42 +08:00
if (!g_window->isVisible()) {
// Default bounds
g_window->remapWindow();
int width = MAX(g_window->bounds().w, ui::display_w()/2);
g_window->setBounds(Rect(
ui::display_w() - width - ToolBar::instance()->bounds().w,
ui::display_h() - g_window->bounds().h - StatusBar::instance()->bounds().h,
width, g_window->bounds().h));
// Load window configuration
2012-07-09 10:24:42 +08:00
load_window_pos(g_window, "PaletteEditor");
}
// Run the frame in background.
2012-07-09 10:24:42 +08:00
g_window->openWindow();
ColorBar::instance()->setPaletteEditorButtonState(true);
}
// Show the specified target color
{
app::Color color =
(m_background ? Preferences::instance().colorBar.bgColor():
Preferences::instance().colorBar.fgColor());
2012-07-09 10:24:42 +08:00
g_window->setColor(color);
}
}
bool PaletteEditorCommand::onChecked(Context* context)
{
if(!g_window)
{
return false;
}
return g_window->isVisible();
}
//////////////////////////////////////////////////////////////////////
// PaletteEntryEditor implementation
//
// Based on ColorPopup class.
PaletteEntryEditor::PaletteEntryEditor()
: Window(WithTitleBar, "Palette Editor (F4)")
2015-05-11 09:53:36 +08:00
, m_type(app::Color::MaskType)
, m_vbox(VERTICAL)
, m_topBox(HORIZONTAL)
, m_bottomBox(HORIZONTAL)
, m_colorType(2)
, m_changeMode(2)
, m_entryLabel("")
, m_disableHexUpdate(false)
, m_redrawTimer(250, this)
, m_redrawAll(false)
, m_implantChange(false)
, m_selfPalChange(false)
, m_fromPalette(0, 0)
{
m_colorType.addItem("RGB");
m_colorType.addItem("HSB");
m_changeMode.addItem("Abs");
m_changeMode.addItem("Rel");
m_topBox.setBorder(gfx::Border(0));
m_topBox.setChildSpacing(0);
m_bottomBox.setBorder(gfx::Border(0));
// Top box
m_topBox.addChild(&m_colorType);
m_topBox.addChild(new Separator("", VERTICAL));
m_topBox.addChild(&m_changeMode);
m_topBox.addChild(new Separator("", VERTICAL));
m_topBox.addChild(&m_hexColorEntry);
m_topBox.addChild(&m_entryLabel);
2015-05-11 09:53:36 +08:00
m_topBox.addChild(new BoxFiller);
// Main vertical box
m_vbox.addChild(&m_topBox);
m_vbox.addChild(&m_rgbSliders);
m_vbox.addChild(&m_hsvSliders);
m_vbox.addChild(&m_bottomBox);
addChild(&m_vbox);
m_colorType.ItemChange.connect(base::Bind<void>(&PaletteEntryEditor::onColorTypeClick, this));
m_changeMode.ItemChange.connect(base::Bind<void>(&PaletteEntryEditor::onChangeModeClick, this));
m_rgbSliders.ColorChange.connect(&PaletteEntryEditor::onColorSlidersChange, this);
m_hsvSliders.ColorChange.connect(&PaletteEntryEditor::onColorSlidersChange, this);
m_hexColorEntry.ColorChange.connect(&PaletteEntryEditor::onColorHexEntryChange, this);
m_changeMode.setSelectedItem(ABS_MODE);
selectColorType(app::Color::RgbType);
// We hook fg/bg color changes (by eyedropper mainly) to update the selected entry color
Preferences::instance().colorBar.fgColor.AfterChange.connect(
&PaletteEntryEditor::onFgBgColorChange, this);
Preferences::instance().colorBar.bgColor.AfterChange.connect(
&PaletteEntryEditor::onFgBgColorChange, this);
2012-07-09 10:24:42 +08:00
// We hook the Window::Close event to save the frame position before closing it.
this->Close.connect(base::Bind<void>(&PaletteEntryEditor::onCloseWindow, this));
2012-07-09 10:24:42 +08:00
// We hook App::Exit signal to destroy the g_window singleton at exit.
App::instance()->Exit.connect(&PaletteEntryEditor::onExit, this);
// Hook for palette change to redraw the palette editor frame
m_palChangeConn =
App::instance()->PaletteChange.connect(&PaletteEntryEditor::onPalChange, this);
initTheme();
}
void PaletteEntryEditor::setColor(const app::Color& color)
{
m_rgbSliders.setColor(color);
m_hsvSliders.setColor(color);
if (!m_disableHexUpdate)
m_hexColorEntry.setColor(color);
PalettePicks entries;
getPicks(entries);
int i, j, i2;
// Find the first selected entry
for (i=0; i<(int)entries.size(); ++i)
if (entries[i])
break;
// Find the first unselected entry after i
for (i2=i+1; i2<(int)entries.size(); ++i2)
if (!entries[i2])
break;
// Find the last selected entry
for (j=entries.size()-1; j>=0; --j)
if (entries[j])
break;
if (i == j) {
m_entryLabel.setTextf(" Entry: %d", i);
}
else if (j-i+1 == i2-i) {
m_entryLabel.setTextf(" Range: %d-%d", i, j);
}
else if (i == int(entries.size())) {
m_entryLabel.setText(" No Entry");
}
else {
m_entryLabel.setText(" Multiple Entries");
}
m_topBox.layout();
}
bool PaletteEntryEditor::onProcessMessage(Message* msg)
{
if (msg->type() == kTimerMessage &&
static_cast<TimerMessage*>(msg)->timer() == &m_redrawTimer) {
// Redraw all editors
if (m_redrawAll) {
m_redrawAll = false;
m_implantChange = false;
m_redrawTimer.stop();
// Call all observers of PaletteChange event.
m_selfPalChange = true;
App::instance()->PaletteChange();
m_selfPalChange = false;
// Redraw all editors
try {
ContextWriter writer(UIContext::instance());
Document* document(writer.document());
if (document != NULL)
document->notifyGeneralUpdate();
}
catch (...) {
// Do nothing
}
}
// Redraw just the current editor
else {
m_redrawAll = true;
if (current_editor != NULL)
current_editor->updateEditor();
}
}
2012-07-09 10:24:42 +08:00
return Window::onProcessMessage(msg);
}
void PaletteEntryEditor::onExit()
{
delete this;
}
2012-07-09 10:24:42 +08:00
void PaletteEntryEditor::onCloseWindow()
{
// Save window configuration
save_window_pos(this, "PaletteEditor");
// Uncheck the "Edit Palette" button.
ColorBar::instance()->setPaletteEditorButtonState(false);
}
void PaletteEntryEditor::onFgBgColorChange(const app::Color& _color)
{
app::Color color = _color;
if (!color.isValid())
return;
if (color.getType() != app::Color::IndexType) {
PaletteView* paletteView = ColorBar::instance()->getPaletteView();
int index = paletteView->getSelectedEntry();
if (index < 0)
return;
color = app::Color::fromIndex(index);
}
if (color.getType() == app::Color::IndexType) {
setColor(color);
2015-05-11 09:53:36 +08:00
resetRelativeInfo();
}
}
void PaletteEntryEditor::onColorSlidersChange(ColorSlidersChangeEvent& ev)
{
2015-05-11 09:53:36 +08:00
setColor(ev.color());
if (ev.mode() == ColorSliders::Absolute)
setAbsolutePaletteEntryChannel(ev.channel(), ev.color());
else
setRelativePaletteEntryChannel(ev.channel(), ev.delta());
updateCurrentSpritePalette("Color Change");
updateColorBar();
}
void PaletteEntryEditor::onColorHexEntryChange(const app::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::onColorTypeClick()
2015-05-11 09:53:36 +08:00
{
switch (m_colorType.selectedItem()) {
case RGB_MODE:
selectColorType(app::Color::RgbType);
break;
case HSV_MODE:
selectColorType(app::Color::HsvType);
break;
}
2015-05-11 09:53:36 +08:00
}
void PaletteEntryEditor::onChangeModeClick()
2015-05-11 09:53:36 +08:00
{
switch (m_changeMode.selectedItem()) {
case ABS_MODE:
m_rgbSliders.setMode(ColorSliders::Absolute);
m_hsvSliders.setMode(ColorSliders::Absolute);
break;
case REL_MODE:
m_rgbSliders.setMode(ColorSliders::Relative);
m_hsvSliders.setMode(ColorSliders::Relative);
break;
}
// Update sliders, entries, etc.
updateWidgetsFromSelectedEntries();
2015-05-11 09:53:36 +08:00
}
void PaletteEntryEditor::setPaletteEntry(const app::Color& color)
{
PalettePicks entries;
getPicks(entries);
color_t new_pal_color = doc::rgba(color.getRed(),
2015-05-11 09:53:36 +08:00
color.getGreen(),
color.getBlue(), 255);
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
if (entries[c])
palette->setEntry(c, new_pal_color);
}
}
2015-05-11 09:53:36 +08:00
void PaletteEntryEditor::setAbsolutePaletteEntryChannel(ColorSliders::Channel channel, const app::Color& color)
{
PalettePicks entries;
getPicks(entries);
int picksCount = entries.picks();
uint32_t src_color;
int r, g, b, a;
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
2015-05-11 09:53:36 +08:00
if (!entries[c])
continue;
// 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);
a = rgba_geta(src_color);
2015-05-11 09:53:36 +08:00
switch (m_type) {
case app::Color::RgbType:
// Modify one entry
if (picksCount == 1) {
2015-05-11 09:53:36 +08:00
r = color.getRed();
g = color.getGreen();
b = color.getBlue();
a = color.getAlpha();
2015-05-11 09:53:36 +08:00
}
// Modify one channel a set of entries
else {
// 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;
case ColorSliders::Alpha:
a = color.getAlpha();
break;
2015-05-11 09:53:36 +08:00
}
}
break;
2015-05-11 09:53:36 +08:00
case app::Color::HsvType:
{
Hsv hsv;
// Modify one entry
if (picksCount == 1) {
2015-05-11 09:53:36 +08:00
hsv.hue(color.getHue());
hsv.saturation(double(color.getSaturation()) / 100.0);
hsv.value(double(color.getValue()) / 100.0);
a = color.getAlpha();
}
// Modify one channel a set of entries
else {
2015-05-11 09:53:36 +08:00
// Convert RGB to HSV
hsv = Hsv(Rgb(r, g, b));
// Only modify the desired HSV channel
switch (channel) {
2015-05-11 09:53:36 +08:00
case ColorSliders::Hue:
hsv.hue(color.getHue());
break;
case ColorSliders::Saturation:
hsv.saturation(double(color.getSaturation()) / 100.0);
break;
2015-05-11 09:53:36 +08:00
case ColorSliders::Value:
hsv.value(double(color.getValue()) / 100.0);
break;
case ColorSliders::Alpha:
a = color.getAlpha();
break;
}
}
2015-05-11 09:53:36 +08:00
// Convert HSV back to RGB
Rgb rgb(hsv);
r = rgb.red();
g = rgb.green();
b = rgb.blue();
}
break;
}
palette->setEntry(c, doc::rgba(r, g, b, a));
2015-05-11 09:53:36 +08:00
}
}
2015-05-11 09:53:36 +08:00
void PaletteEntryEditor::setRelativePaletteEntryChannel(ColorSliders::Channel channel, int delta)
{
PalettePicks entries;
getPicks(entries);
2015-05-11 09:53:36 +08:00
// Update modified delta
m_relDeltas[channel] = delta;
uint32_t src_color;
int r, g, b, a;
2015-05-11 09:53:36 +08:00
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
if (!entries[c])
continue;
// Get the current RGB values of the palette entry
src_color = m_fromPalette.getEntry(c);
r = rgba_getr(src_color);
g = rgba_getg(src_color);
b = rgba_getb(src_color);
a = rgba_geta(src_color);
2015-05-11 09:53:36 +08:00
switch (m_type) {
case app::Color::RgbType:
r = MID(0, r+m_relDeltas[ColorSliders::Red], 255);
g = MID(0, g+m_relDeltas[ColorSliders::Green], 255);
b = MID(0, b+m_relDeltas[ColorSliders::Blue], 255);
a = MID(0, a+m_relDeltas[ColorSliders::Alpha], 255);
2015-05-11 09:53:36 +08:00
break;
case app::Color::HsvType: {
// Convert RGB to HSV
Hsv hsv(Rgb(r, g, b));
double h = hsv.hue()+m_relDeltas[ColorSliders::Hue];
double s = 100.0*hsv.saturation()+m_relDeltas[ColorSliders::Saturation];
double v = 100.0*hsv.value()+m_relDeltas[ColorSliders::Value];
if (h < 0.0) h += 360.0;
else if (h > 360.0) h -= 360.0;
hsv.hue (MID(0.0, h, 360.0));
hsv.saturation(MID(0.0, s, 100.0) / 100.0);
hsv.value (MID(0.0, v, 100.0) / 100.0);
// Convert HSV back to RGB
Rgb rgb(hsv);
r = rgb.red();
g = rgb.green();
b = rgb.blue();
a = MID(0, a+m_relDeltas[ColorSliders::Alpha], 255);
2015-05-11 09:53:36 +08:00
break;
}
}
2015-05-11 09:53:36 +08:00
palette->setEntry(c, doc::rgba(r, g, b, a));
}
}
void PaletteEntryEditor::selectColorType(app::Color::Type type)
{
2015-05-11 09:53:36 +08:00
m_type = type;
m_rgbSliders.setVisible(type == app::Color::RgbType);
m_hsvSliders.setVisible(type == app::Color::HsvType);
2015-05-11 09:53:36 +08:00
resetRelativeInfo();
switch (type) {
case app::Color::RgbType: m_colorType.setSelectedItem(RGB_MODE); break;
case app::Color::HsvType: m_colorType.setSelectedItem(HSV_MODE); break;
}
m_vbox.layout();
m_vbox.invalidate();
}
void PaletteEntryEditor::updateCurrentSpritePalette(const char* operationName)
{
if (UIContext::instance()->activeDocument() &&
UIContext::instance()->activeDocument()->sprite()) {
try {
ContextWriter writer(UIContext::instance());
Document* document(writer.document());
Sprite* sprite(writer.sprite());
Palette* newPalette = get_current_palette(); // System current pal
frame_t frame = writer.frame();
Palette* currentSpritePalette = sprite->palette(frame); // 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) {
DocumentUndo* undo = document->undoHistory();
Cmd* cmd = new cmd::SetPalette(sprite, frame, newPalette);
// Add undo information to save the range of pal entries that will be modified.
if (m_implantChange &&
undo->lastExecutedCmd() &&
undo->lastExecutedCmd()->label() == operationName) {
// Implant the cmd in the last CmdSequence if it's
// related about color palette modifications
ASSERT(dynamic_cast<CmdSequence*>(undo->lastExecutedCmd()));
static_cast<CmdSequence*>(undo->lastExecutedCmd())->add(cmd);
cmd->execute(UIContext::instance());
}
else {
Transaction transaction(writer.context(), operationName, ModifyDocument);
transaction.execute(cmd);
transaction.commit();
}
}
}
catch (base::Exception& e) {
Console::showException(e);
}
}
PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
palette_editor->invalidate();
if (!m_redrawTimer.isRunning())
m_redrawTimer.start();
m_redrawAll = false;
m_implantChange = true;
}
void PaletteEntryEditor::updateColorBar()
{
ColorBar::instance()->invalidate();
}
void PaletteEntryEditor::updateWidgetsFromSelectedEntries()
{
PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
int index = palette_editor->getSelectedEntry();
if (index >= 0)
setColor(app::Color::fromIndex(index));
resetRelativeInfo();
2015-05-11 09:53:36 +08:00
// Redraw the window
invalidate();
}
void PaletteEntryEditor::onPalChange()
{
if (!m_selfPalChange)
updateWidgetsFromSelectedEntries();
}
2015-05-11 09:53:36 +08:00
void PaletteEntryEditor::resetRelativeInfo()
{
m_rgbSliders.resetRelativeSliders();
m_hsvSliders.resetRelativeSliders();
get_current_palette()->copyColorsTo(&m_fromPalette);
m_relDeltas.clear();
}
void PaletteEntryEditor::getPicks(PalettePicks& picks)
{
PaletteView* palView = ColorBar::instance()->getPaletteView();
palView->getSelectedEntries(picks);
if (picks.picks() == 0) {
int i = palView->getSelectedEntry();
if (i >= 0 && i < picks.size())
picks[i] = true;
}
}
Command* CommandFactory::createPaletteEditorCommand()
{
return new PaletteEditorCommand;
}
} // namespace app