2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
2017-05-16 02:21:02 +08:00
|
|
|
// Copyright (C) 2001-2017 David Capello
|
2015-02-12 23:16:25 +08:00
|
|
|
//
|
2016-08-27 04:02:58 +08:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2012-01-06 06:45:03 +08:00
|
|
|
#include "config.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "app/app.h"
|
|
|
|
#include "app/commands/command.h"
|
|
|
|
#include "app/commands/params.h"
|
|
|
|
#include "app/context_access.h"
|
|
|
|
#include "app/document_api.h"
|
2017-05-17 04:18:55 +08:00
|
|
|
#include "app/modules/editors.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/modules/gui.h"
|
|
|
|
#include "app/modules/palettes.h"
|
2015-01-19 09:05:33 +08:00
|
|
|
#include "app/transaction.h"
|
2017-05-17 04:18:55 +08:00
|
|
|
#include "app/ui/editor/editor.h"
|
|
|
|
#include "app/ui/skin/skin_theme.h"
|
|
|
|
#include "base/bind.h"
|
|
|
|
#include "base/thread.h"
|
2014-10-21 09:21:31 +08:00
|
|
|
#include "doc/image.h"
|
|
|
|
#include "doc/sprite.h"
|
2017-05-16 03:25:09 +08:00
|
|
|
#include "render/dithering_algorithm.h"
|
2017-05-17 04:18:55 +08:00
|
|
|
#include "render/quantization.h"
|
|
|
|
#include "render/render.h"
|
|
|
|
#include "ui/listitem.h"
|
|
|
|
#include "ui/paint_event.h"
|
|
|
|
#include "ui/size_hint_event.h"
|
|
|
|
|
|
|
|
#include "color_mode.xml.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app {
|
2015-02-12 23:16:25 +08:00
|
|
|
|
2017-05-17 04:18:55 +08:00
|
|
|
using namespace ui;
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
class ConversionItem : public ListItem {
|
|
|
|
public:
|
|
|
|
ConversionItem(const doc::PixelFormat pixelFormat,
|
|
|
|
const render::DitheringAlgorithm dithering = render::DitheringAlgorithm::None)
|
|
|
|
: m_pixelFormat(pixelFormat)
|
|
|
|
, m_dithering(dithering) {
|
|
|
|
switch (pixelFormat) {
|
|
|
|
case IMAGE_RGB:
|
|
|
|
setText("-> RGB");
|
|
|
|
break;
|
|
|
|
case IMAGE_GRAYSCALE:
|
|
|
|
setText("-> Grayscale");
|
|
|
|
break;
|
|
|
|
case IMAGE_INDEXED:
|
|
|
|
switch (m_dithering) {
|
|
|
|
case render::DitheringAlgorithm::None:
|
|
|
|
setText("-> Indexed");
|
|
|
|
break;
|
|
|
|
case render::DitheringAlgorithm::Ordered:
|
|
|
|
setText("-> Indexed w/Ordered Dithering");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
doc::PixelFormat pixelFormat() const { return m_pixelFormat; }
|
|
|
|
render::DitheringAlgorithm ditheringAlgorithm() const { return m_dithering; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
doc::PixelFormat m_pixelFormat;
|
2017-05-16 03:25:09 +08:00
|
|
|
render::DitheringAlgorithm m_dithering;
|
2017-05-17 04:18:55 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
class ConvertThread {
|
|
|
|
public:
|
|
|
|
ConvertThread(const doc::ImageRef& dstImage,
|
|
|
|
const doc::Sprite* sprite,
|
|
|
|
const doc::frame_t frame,
|
|
|
|
const doc::PixelFormat pixelFormat,
|
|
|
|
const render::DitheringAlgorithm ditheringAlgorithm)
|
|
|
|
: m_image(dstImage)
|
|
|
|
, m_running(true)
|
|
|
|
, m_stopFlag(false)
|
|
|
|
, m_thread(
|
|
|
|
[this,
|
|
|
|
sprite, frame,
|
|
|
|
pixelFormat,
|
|
|
|
ditheringAlgorithm]() {
|
|
|
|
run(sprite, frame,
|
|
|
|
pixelFormat,
|
|
|
|
ditheringAlgorithm);
|
|
|
|
})
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void stop() {
|
|
|
|
m_stopFlag = true;
|
|
|
|
m_thread.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isRunning() const {
|
|
|
|
return m_running;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void run(const Sprite* sprite,
|
|
|
|
const doc::frame_t frame,
|
|
|
|
const doc::PixelFormat pixelFormat,
|
|
|
|
const render::DitheringAlgorithm ditheringAlgorithm) {
|
|
|
|
doc::ImageRef tmp(Image::create(sprite->pixelFormat(),
|
|
|
|
sprite->width(),
|
|
|
|
sprite->height()));
|
|
|
|
|
|
|
|
render::Render render;
|
|
|
|
render.renderSprite(
|
|
|
|
tmp.get(), sprite, frame);
|
|
|
|
|
|
|
|
render::convert_pixel_format(
|
|
|
|
tmp.get(),
|
|
|
|
m_image.get(),
|
|
|
|
pixelFormat,
|
|
|
|
ditheringAlgorithm,
|
|
|
|
sprite->rgbMap(frame),
|
|
|
|
sprite->palette(frame),
|
|
|
|
(sprite->backgroundLayer() != nullptr),
|
|
|
|
0,
|
|
|
|
&m_stopFlag);
|
|
|
|
|
|
|
|
m_running = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
doc::ImageRef m_image;
|
|
|
|
bool m_running;
|
|
|
|
bool m_stopFlag;
|
|
|
|
base::thread m_thread;
|
|
|
|
};
|
|
|
|
|
|
|
|
class ColorModeWindow : public app::gen::ColorMode {
|
|
|
|
public:
|
|
|
|
ColorModeWindow(Editor* editor)
|
|
|
|
: m_timer(100)
|
|
|
|
, m_editor(editor)
|
|
|
|
, m_image(nullptr)
|
|
|
|
, m_imageBuffer(new doc::ImageBuffer)
|
|
|
|
{
|
|
|
|
doc::PixelFormat from = m_editor->sprite()->pixelFormat();
|
|
|
|
|
|
|
|
// Add the color mode in the window title
|
|
|
|
switch (from) {
|
|
|
|
case IMAGE_RGB: setText(text() + ": RGB"); break;
|
|
|
|
case IMAGE_GRAYSCALE: setText(text() + ": Grayscale"); break;
|
|
|
|
case IMAGE_INDEXED: setText(text() + ": Indexed"); break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add conversion items
|
|
|
|
if (from != IMAGE_RGB)
|
|
|
|
colorMode()->addChild(new ConversionItem(IMAGE_RGB));
|
|
|
|
if (from != IMAGE_INDEXED) {
|
|
|
|
colorMode()->addChild(new ConversionItem(IMAGE_INDEXED));
|
|
|
|
colorMode()->addChild(new ConversionItem(IMAGE_INDEXED, render::DitheringAlgorithm::Ordered));
|
|
|
|
}
|
|
|
|
if (from != IMAGE_GRAYSCALE)
|
|
|
|
colorMode()->addChild(new ConversionItem(IMAGE_GRAYSCALE));
|
|
|
|
|
|
|
|
colorModeView()->setMinSize(
|
|
|
|
colorModeView()->sizeHint() +
|
|
|
|
colorMode()->sizeHint());
|
|
|
|
|
|
|
|
colorMode()->Change.connect(base::Bind<void>(&ColorModeWindow::onChangeColorMode, this));
|
|
|
|
m_timer.Tick.connect(base::Bind<void>(&ColorModeWindow::onMonitorProgress, this));
|
|
|
|
|
|
|
|
// Select first option
|
|
|
|
colorMode()->selectIndex(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
~ColorModeWindow() {
|
|
|
|
m_editor->renderEngine().removePreviewImage();
|
|
|
|
m_editor->invalidate();
|
|
|
|
}
|
|
|
|
|
|
|
|
doc::PixelFormat pixelFormat() const {
|
|
|
|
return
|
|
|
|
static_cast<ConversionItem*>(colorMode()->getSelectedChild())
|
|
|
|
->pixelFormat();
|
|
|
|
}
|
|
|
|
|
|
|
|
render::DitheringAlgorithm ditheringAlgorithm() const {
|
|
|
|
return
|
|
|
|
static_cast<ConversionItem*>(colorMode()->getSelectedChild())
|
|
|
|
->ditheringAlgorithm();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void onChangeColorMode() {
|
|
|
|
m_timer.stop();
|
|
|
|
|
|
|
|
if (m_bgThread) {
|
|
|
|
m_bgThread->stop();
|
|
|
|
m_bgThread.reset(nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_editor->renderEngine().removePreviewImage();
|
|
|
|
|
|
|
|
ConversionItem* item =
|
|
|
|
static_cast<ConversionItem*>(colorMode()->getSelectedChild());
|
|
|
|
|
|
|
|
m_image.reset(
|
|
|
|
Image::create(item->pixelFormat(),
|
|
|
|
m_editor->sprite()->width(),
|
|
|
|
m_editor->sprite()->height(),
|
|
|
|
m_imageBuffer));
|
|
|
|
|
|
|
|
m_editor->renderEngine().setPreviewImage(
|
|
|
|
nullptr,
|
|
|
|
m_editor->frame(),
|
|
|
|
m_image.get(),
|
|
|
|
gfx::Point(0, 0),
|
|
|
|
doc::BlendMode::NORMAL);
|
|
|
|
|
|
|
|
m_image->clear(0);
|
|
|
|
m_editor->invalidate();
|
|
|
|
|
|
|
|
m_bgThread.reset(
|
|
|
|
new ConvertThread(
|
|
|
|
m_image,
|
|
|
|
m_editor->sprite(),
|
|
|
|
m_editor->frame(),
|
|
|
|
item->pixelFormat(),
|
|
|
|
item->ditheringAlgorithm()));
|
|
|
|
|
|
|
|
m_timer.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void onMonitorProgress() {
|
|
|
|
ASSERT(m_bgThread);
|
|
|
|
if (!m_bgThread)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!m_bgThread->isRunning()) {
|
|
|
|
m_timer.stop();
|
|
|
|
m_bgThread->stop();
|
|
|
|
m_bgThread.reset(nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_editor->invalidate();
|
|
|
|
}
|
|
|
|
|
|
|
|
Timer m_timer;
|
|
|
|
Editor* m_editor;
|
|
|
|
doc::ImageRef m_image;
|
|
|
|
doc::ImageBufferPtr m_imageBuffer;
|
|
|
|
base::UniquePtr<ConvertThread> m_bgThread;
|
|
|
|
};
|
2017-05-16 03:25:09 +08:00
|
|
|
|
2017-05-17 04:18:55 +08:00
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
class ChangePixelFormatCommand : public Command {
|
2012-01-06 06:45:03 +08:00
|
|
|
public:
|
2012-02-13 10:21:06 +08:00
|
|
|
ChangePixelFormatCommand();
|
2014-08-15 10:07:47 +08:00
|
|
|
Command* clone() const override { return new ChangePixelFormatCommand(*this); }
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
protected:
|
2015-03-12 02:40:22 +08:00
|
|
|
void onLoadParams(const Params& params) override;
|
|
|
|
bool onEnabled(Context* context) override;
|
|
|
|
bool onChecked(Context* context) override;
|
|
|
|
void onExecute(Context* context) override;
|
2017-05-17 04:18:55 +08:00
|
|
|
|
|
|
|
private:
|
|
|
|
bool m_useUI;
|
|
|
|
doc::PixelFormat m_format;
|
|
|
|
render::DitheringAlgorithm m_dithering;
|
2012-01-06 06:45:03 +08:00
|
|
|
};
|
|
|
|
|
2012-02-13 10:21:06 +08:00
|
|
|
ChangePixelFormatCommand::ChangePixelFormatCommand()
|
|
|
|
: Command("ChangePixelFormat",
|
|
|
|
"Change Pixel Format",
|
2012-01-06 06:45:03 +08:00
|
|
|
CmdUIOnlyFlag)
|
|
|
|
{
|
2017-05-17 04:18:55 +08:00
|
|
|
m_useUI = true;
|
2012-02-13 10:21:06 +08:00
|
|
|
m_format = IMAGE_RGB;
|
2017-05-16 03:25:09 +08:00
|
|
|
m_dithering = render::DitheringAlgorithm::None;
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2015-03-12 02:40:22 +08:00
|
|
|
void ChangePixelFormatCommand::onLoadParams(const Params& params)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2017-05-17 04:18:55 +08:00
|
|
|
m_useUI = false;
|
|
|
|
|
2015-03-12 02:40:22 +08:00
|
|
|
std::string format = params.get("format");
|
2012-02-13 10:21:06 +08:00
|
|
|
if (format == "rgb") m_format = IMAGE_RGB;
|
|
|
|
else if (format == "grayscale") m_format = IMAGE_GRAYSCALE;
|
|
|
|
else if (format == "indexed") m_format = IMAGE_INDEXED;
|
2017-05-17 04:18:55 +08:00
|
|
|
else
|
|
|
|
m_useUI = true;
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-03-12 02:40:22 +08:00
|
|
|
std::string dithering = params.get("dithering");
|
2012-01-06 06:45:03 +08:00
|
|
|
if (dithering == "ordered")
|
2017-05-16 03:25:09 +08:00
|
|
|
m_dithering = render::DitheringAlgorithm::Ordered;
|
2012-01-06 06:45:03 +08:00
|
|
|
else
|
2017-05-16 03:25:09 +08:00
|
|
|
m_dithering = render::DitheringAlgorithm::None;
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2012-02-13 10:21:06 +08:00
|
|
|
bool ChangePixelFormatCommand::onEnabled(Context* context)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
ContextWriter writer(context);
|
|
|
|
Sprite* sprite(writer.sprite());
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2017-05-17 04:18:55 +08:00
|
|
|
if (!sprite)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (m_useUI)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (sprite->pixelFormat() == IMAGE_INDEXED &&
|
2012-02-13 10:21:06 +08:00
|
|
|
m_format == IMAGE_INDEXED &&
|
2017-05-16 03:25:09 +08:00
|
|
|
m_dithering == render::DitheringAlgorithm::Ordered)
|
2012-01-06 06:45:03 +08:00
|
|
|
return false;
|
|
|
|
|
2017-05-17 04:18:55 +08:00
|
|
|
return true;
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2012-02-13 10:21:06 +08:00
|
|
|
bool ChangePixelFormatCommand::onChecked(Context* context)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2017-05-17 04:18:55 +08:00
|
|
|
if (m_useUI)
|
|
|
|
return false;
|
|
|
|
|
2013-03-12 07:29:45 +08:00
|
|
|
const ContextReader reader(context);
|
|
|
|
const Sprite* sprite = reader.sprite();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2017-05-17 04:18:55 +08:00
|
|
|
if (sprite &&
|
2014-07-30 12:28:15 +08:00
|
|
|
sprite->pixelFormat() == IMAGE_INDEXED &&
|
2012-02-13 10:21:06 +08:00
|
|
|
m_format == IMAGE_INDEXED &&
|
2017-05-16 03:25:09 +08:00
|
|
|
m_dithering == render::DitheringAlgorithm::Ordered)
|
2012-01-06 06:45:03 +08:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return
|
2017-05-17 04:18:55 +08:00
|
|
|
(sprite &&
|
|
|
|
sprite->pixelFormat() == m_format);
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2012-02-13 10:21:06 +08:00
|
|
|
void ChangePixelFormatCommand::onExecute(Context* context)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2017-05-17 04:18:55 +08:00
|
|
|
if (m_useUI) {
|
|
|
|
ColorModeWindow window(current_editor);
|
|
|
|
|
|
|
|
window.remapWindow();
|
|
|
|
window.centerWindow();
|
|
|
|
|
|
|
|
window.openWindowInForeground();
|
|
|
|
if (window.closer() != window.ok())
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_format = window.pixelFormat();
|
|
|
|
m_dithering = window.ditheringAlgorithm();
|
|
|
|
}
|
|
|
|
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
ContextWriter writer(context);
|
2015-01-19 09:05:33 +08:00
|
|
|
Transaction transaction(writer.context(), "Color Mode Change");
|
2013-03-12 07:29:45 +08:00
|
|
|
Document* document(writer.document());
|
|
|
|
Sprite* sprite(writer.sprite());
|
|
|
|
|
2015-01-19 09:05:33 +08:00
|
|
|
document->getApi(transaction).setPixelFormat(sprite, m_format, m_dithering);
|
|
|
|
transaction.commit();
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2017-05-16 02:21:02 +08:00
|
|
|
|
|
|
|
if (context->isUIAvailable())
|
|
|
|
app_refresh_screen();
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2012-02-13 10:21:06 +08:00
|
|
|
Command* CommandFactory::createChangePixelFormatCommand()
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2012-02-13 10:21:06 +08:00
|
|
|
return new ChangePixelFormatCommand;
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2013-08-06 08:20:19 +08:00
|
|
|
|
|
|
|
} // namespace app
|