Add a Color Mode conversion dialog with preview

This commit is contained in:
David Capello 2017-05-16 17:18:55 -03:00
parent 23272895c4
commit cbee0862f3
8 changed files with 318 additions and 25 deletions

View File

@ -639,13 +639,11 @@
<item command="ChangePixelFormat" text="&amp;Grayscale">
<param name="format" value="grayscale" />
</item>
<item command="ChangePixelFormat" text="&amp;Indexed (No Dithering)">
<item command="ChangePixelFormat" text="&amp;Indexed">
<param name="format" value="indexed" />
</item>
<item command="ChangePixelFormat" text="Indexed (Ordered &amp;Dither)">
<param name="format" value="indexed" />
<param name="dithering" value="ordered" />
</item>
<separator />
<item command="ChangePixelFormat" text="&amp;More Options" />
</menu>
<separator />
<item command="DuplicateSprite" text="&amp;Duplicate..." />

View File

@ -62,6 +62,9 @@ ok = &OK
cancel = &Cancel
delete = &Delete
[color_mode]
title = Color Mode
[convolution_matrix]
reload_stock = &Reload Stock

View File

@ -0,0 +1,20 @@
<!-- Aseprite -->
<!-- Copyright (C) 2017 by David Capello -->
<gui>
<window id="color_mode" text="@.title">
<vbox>
<view id="color_mode_view" expansive="true">
<listbox id="color_mode" />
</view>
<separator horizontal="true" />
<hbox>
<boxfiller />
<hbox homogeneous="true">
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
<button text="@general.cancel" closewindow="true" />
</hbox>
</hbox>
</vbox>
</window>
</gui>

View File

@ -13,19 +13,250 @@
#include "app/commands/params.h"
#include "app/context_access.h"
#include "app/document_api.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/transaction.h"
#include "app/ui/editor/editor.h"
#include "app/ui/skin/skin_theme.h"
#include "base/bind.h"
#include "base/thread.h"
#include "doc/image.h"
#include "doc/sprite.h"
#include "render/dithering_algorithm.h"
#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"
namespace app {
class ChangePixelFormatCommand : public Command {
doc::PixelFormat m_format;
render::DitheringAlgorithm m_dithering;
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;
render::DitheringAlgorithm m_dithering;
};
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;
};
} // anonymous namespace
class ChangePixelFormatCommand : public Command {
public:
ChangePixelFormatCommand();
Command* clone() const override { return new ChangePixelFormatCommand(*this); }
@ -35,6 +266,11 @@ protected:
bool onEnabled(Context* context) override;
bool onChecked(Context* context) override;
void onExecute(Context* context) override;
private:
bool m_useUI;
doc::PixelFormat m_format;
render::DitheringAlgorithm m_dithering;
};
ChangePixelFormatCommand::ChangePixelFormatCommand()
@ -42,16 +278,21 @@ ChangePixelFormatCommand::ChangePixelFormatCommand()
"Change Pixel Format",
CmdUIOnlyFlag)
{
m_useUI = true;
m_format = IMAGE_RGB;
m_dithering = render::DitheringAlgorithm::None;
}
void ChangePixelFormatCommand::onLoadParams(const Params& params)
{
m_useUI = false;
std::string format = params.get("format");
if (format == "rgb") m_format = IMAGE_RGB;
else if (format == "grayscale") m_format = IMAGE_GRAYSCALE;
else if (format == "indexed") m_format = IMAGE_INDEXED;
else
m_useUI = true;
std::string dithering = params.get("dithering");
if (dithering == "ordered")
@ -65,33 +306,55 @@ bool ChangePixelFormatCommand::onEnabled(Context* context)
ContextWriter writer(context);
Sprite* sprite(writer.sprite());
if (sprite != NULL &&
sprite->pixelFormat() == IMAGE_INDEXED &&
if (!sprite)
return false;
if (m_useUI)
return true;
if (sprite->pixelFormat() == IMAGE_INDEXED &&
m_format == IMAGE_INDEXED &&
m_dithering == render::DitheringAlgorithm::Ordered)
return false;
return sprite != NULL;
return true;
}
bool ChangePixelFormatCommand::onChecked(Context* context)
{
if (m_useUI)
return false;
const ContextReader reader(context);
const Sprite* sprite = reader.sprite();
if (sprite != NULL &&
if (sprite &&
sprite->pixelFormat() == IMAGE_INDEXED &&
m_format == IMAGE_INDEXED &&
m_dithering == render::DitheringAlgorithm::Ordered)
return false;
return
sprite != NULL &&
sprite->pixelFormat() == m_format;
(sprite &&
sprite->pixelFormat() == m_format);
}
void ChangePixelFormatCommand::onExecute(Context* context)
{
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();
}
{
ContextWriter writer(context);
Transaction transaction(writer.context(), "Color Mode Change");

View File

@ -1,5 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2001-2015 David Capello
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -151,7 +151,8 @@ namespace render {
doc::Image* dstImage,
int u, int v,
const doc::RgbMap* rgbmap,
const doc::Palette* palette) {
const doc::Palette* palette,
bool* stopFlag = nullptr) {
const doc::LockImageBits<doc::RgbTraits> srcBits(srcImage);
doc::LockImageBits<doc::IndexedTraits> dstBits(dstImage);
auto srcIt = srcBits.begin();
@ -165,6 +166,8 @@ namespace render {
ASSERT(dstIt != dstBits.end());
*dstIt = ditherRgbPixelToIndex(matrix, *srcIt, x+u, y+v, rgbmap, palette);
}
if (stopFlag && *stopFlag)
break;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2001-2016 David Capello
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -85,7 +85,8 @@ Image* convert_pixel_format(
const RgbMap* rgbmap,
const Palette* palette,
bool is_background,
color_t new_mask_color)
color_t new_mask_color,
bool* stopFlag)
{
if (!new_image)
new_image = Image::create(pixelFormat, image->width(), image->height());
@ -97,7 +98,9 @@ Image* convert_pixel_format(
ditheringAlgorithm == DitheringAlgorithm::Ordered) {
BayerMatrix<8> matrix;
OrderedDither dither;
dither.ditherRgbImageToIndexed(matrix, image, new_image, 0, 0, rgbmap, palette);
dither.ditherRgbImageToIndexed(matrix, image, new_image,
0, 0, rgbmap, palette,
stopFlag);
return new_image;
}

View File

@ -62,7 +62,8 @@ namespace render {
const doc::RgbMap* rgbmap,
const doc::Palette* palette,
bool is_background,
doc::color_t new_mask_color);
doc::color_t new_mask_color,
bool* stopFlag = nullptr);
} // namespace render

View File

@ -41,7 +41,7 @@ public:
inline typename DstTraits::pixel_t
operator()(const typename DstTraits::pixel_t& dst,
const typename SrcTraits::pixel_t& src,
int opacity)
const int opacity)
{
if (src != m_mask_color)
return (*m_blendFunc)(dst, src, opacity);
@ -63,7 +63,7 @@ public:
inline RgbTraits::pixel_t
operator()(const RgbTraits::pixel_t& dst,
const GrayscaleTraits::pixel_t& src,
int opacity)
const int opacity)
{
if (src != m_mask_color) {
int v = graya_getv(src);
@ -91,7 +91,7 @@ public:
inline RgbTraits::pixel_t
operator()(const RgbTraits::pixel_t& dst,
const IndexedTraits::pixel_t& src,
int opacity)
const int opacity)
{
if (m_blendMode == BlendMode::SRC) {
return m_pal->getEntry(src);
@ -119,7 +119,7 @@ public:
inline IndexedTraits::pixel_t
operator()(const IndexedTraits::pixel_t& dst,
const IndexedTraits::pixel_t& src,
int opacity)
const int opacity)
{
if (m_blendMode == BlendMode::SRC) {
return src;
@ -758,7 +758,9 @@ void Render::renderSprite(
m_previewImage->width(),
m_previewImage->height()),
area,
compositeImage,
getImageComposition(
dstImage->pixelFormat(),
m_previewImage->pixelFormat(), sprite->root()),
255,
m_previewBlendMode);
}