mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-16 23:42:57 +00:00
Add a Color Mode conversion dialog with preview
This commit is contained in:
parent
23272895c4
commit
cbee0862f3
@ -639,13 +639,11 @@
|
||||
<item command="ChangePixelFormat" text="&Grayscale">
|
||||
<param name="format" value="grayscale" />
|
||||
</item>
|
||||
<item command="ChangePixelFormat" text="&Indexed (No Dithering)">
|
||||
<item command="ChangePixelFormat" text="&Indexed">
|
||||
<param name="format" value="indexed" />
|
||||
</item>
|
||||
<item command="ChangePixelFormat" text="Indexed (Ordered &Dither)">
|
||||
<param name="format" value="indexed" />
|
||||
<param name="dithering" value="ordered" />
|
||||
</item>
|
||||
<separator />
|
||||
<item command="ChangePixelFormat" text="&More Options" />
|
||||
</menu>
|
||||
<separator />
|
||||
<item command="DuplicateSprite" text="&Duplicate..." />
|
||||
|
@ -62,6 +62,9 @@ ok = &OK
|
||||
cancel = &Cancel
|
||||
delete = &Delete
|
||||
|
||||
[color_mode]
|
||||
title = Color Mode
|
||||
|
||||
[convolution_matrix]
|
||||
reload_stock = &Reload Stock
|
||||
|
||||
|
20
data/widgets/color_mode.xml
Normal file
20
data/widgets/color_mode.xml
Normal 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>
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user