mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-17 08:43:11 +00:00
Add widget to choose dithering algorithm + matrix
This commit is contained in:
parent
829cc9ebec
commit
bcdf598392
@ -7,6 +7,7 @@
|
||||
<listbox id="color_mode" />
|
||||
</view>
|
||||
<slider min="0" max="100" id="progress" minwidth="100" />
|
||||
<hbox id="dithering_placeholder" />
|
||||
<check text="@.flatten" id="flatten" />
|
||||
<separator horizontal="true" />
|
||||
<hbox>
|
||||
|
@ -427,6 +427,7 @@ add_library(app-lib
|
||||
ui/color_wheel.cpp
|
||||
ui/configure_timeline_popup.cpp
|
||||
ui/context_bar.cpp
|
||||
ui/dithering_selector.cpp
|
||||
ui/document_view.cpp
|
||||
ui/drop_down_button.cpp
|
||||
ui/editor/brush_preview.cpp
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "app/modules/palettes.h"
|
||||
#include "app/render_task_job.h"
|
||||
#include "app/transaction.h"
|
||||
#include "app/ui/dithering_selector.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "base/bind.h"
|
||||
@ -45,10 +46,8 @@ namespace {
|
||||
|
||||
class ConversionItem : public ListItem {
|
||||
public:
|
||||
ConversionItem(const doc::PixelFormat pixelFormat,
|
||||
const render::DitheringAlgorithm dithering = render::DitheringAlgorithm::None)
|
||||
: m_pixelFormat(pixelFormat)
|
||||
, m_dithering(dithering) {
|
||||
ConversionItem(const doc::PixelFormat pixelFormat)
|
||||
: m_pixelFormat(pixelFormat) {
|
||||
switch (pixelFormat) {
|
||||
case IMAGE_RGB:
|
||||
setText("-> RGB");
|
||||
@ -57,27 +56,13 @@ public:
|
||||
setText("-> Grayscale");
|
||||
break;
|
||||
case IMAGE_INDEXED:
|
||||
switch (m_dithering) {
|
||||
case render::DitheringAlgorithm::None:
|
||||
setText("-> Indexed");
|
||||
break;
|
||||
case render::DitheringAlgorithm::OldOrdered:
|
||||
setText("-> Indexed w/Old Ordered Dithering");
|
||||
break;
|
||||
case render::DitheringAlgorithm::Ordered:
|
||||
setText("-> Indexed w/Ordered Dithering");
|
||||
break;
|
||||
}
|
||||
setText("-> Indexed");
|
||||
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 render::TaskDelegate {
|
||||
@ -181,6 +166,7 @@ public:
|
||||
, m_image(nullptr)
|
||||
, m_imageBuffer(new doc::ImageBuffer)
|
||||
, m_selectedItem(nullptr)
|
||||
, m_ditheringSelector(nullptr)
|
||||
{
|
||||
doc::PixelFormat from = m_editor->sprite()->pixelFormat();
|
||||
|
||||
@ -196,8 +182,12 @@ public:
|
||||
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));
|
||||
colorMode()->addChild(new ConversionItem(IMAGE_INDEXED, render::DitheringAlgorithm::OldOrdered));
|
||||
|
||||
m_ditheringSelector = new DitheringSelector;
|
||||
m_ditheringSelector->setExpansive(true);
|
||||
m_ditheringSelector->Change.connect(
|
||||
base::Bind<void>(&ColorModeWindow::onDithering, this));
|
||||
ditheringPlaceholder()->addChild(m_ditheringSelector);
|
||||
}
|
||||
if (from != IMAGE_GRAYSCALE)
|
||||
colorMode()->addChild(new ConversionItem(IMAGE_GRAYSCALE));
|
||||
@ -225,8 +215,13 @@ public:
|
||||
}
|
||||
|
||||
render::DitheringAlgorithm ditheringAlgorithm() const {
|
||||
ASSERT(m_selectedItem);
|
||||
return m_selectedItem->ditheringAlgorithm();
|
||||
return (m_ditheringSelector ? m_ditheringSelector->ditheringAlgorithm():
|
||||
render::DitheringAlgorithm::None);
|
||||
}
|
||||
|
||||
render::DitheringMatrix ditheringMatrix() const {
|
||||
return (m_ditheringSelector ? m_ditheringSelector->ditheringMatrix():
|
||||
render::BayerMatrix(8));
|
||||
}
|
||||
|
||||
bool flattenEnabled() const {
|
||||
@ -236,13 +231,14 @@ public:
|
||||
private:
|
||||
|
||||
void stop() {
|
||||
m_editor->renderEngine().removePreviewImage();
|
||||
m_editor->invalidate();
|
||||
|
||||
m_timer.stop();
|
||||
if (m_bgThread) {
|
||||
m_bgThread->stop();
|
||||
m_bgThread.reset(nullptr);
|
||||
}
|
||||
m_editor->renderEngine().removePreviewImage();
|
||||
m_editor->invalidate();
|
||||
}
|
||||
|
||||
void onChangeColorMode() {
|
||||
@ -283,13 +279,19 @@ private:
|
||||
m_editor->sprite(),
|
||||
m_editor->frame(),
|
||||
item->pixelFormat(),
|
||||
item->ditheringAlgorithm(),
|
||||
render::BayerMatrix(8), // TODO this must be configurable
|
||||
ditheringAlgorithm(),
|
||||
ditheringMatrix(),
|
||||
visibleBounds.origin()));
|
||||
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
void onDithering() {
|
||||
stop();
|
||||
m_selectedItem = nullptr;
|
||||
onChangeColorMode();
|
||||
}
|
||||
|
||||
void onMonitorProgress() {
|
||||
ASSERT(m_bgThread);
|
||||
if (!m_bgThread)
|
||||
@ -315,6 +317,7 @@ private:
|
||||
doc::ImageBufferPtr m_imageBuffer;
|
||||
base::UniquePtr<ConvertThread> m_bgThread;
|
||||
ConversionItem* m_selectedItem;
|
||||
DitheringSelector* m_ditheringSelector;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
@ -465,6 +468,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
|
||||
m_format = window.pixelFormat();
|
||||
m_ditheringAlgorithm = window.ditheringAlgorithm();
|
||||
m_ditheringMatrix = window.ditheringMatrix();
|
||||
flatten = window.flattenEnabled();
|
||||
}
|
||||
|
||||
@ -472,26 +476,28 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
if (context->activeDocument()->sprite()->pixelFormat() == m_format)
|
||||
return;
|
||||
|
||||
RenderTaskJob job(
|
||||
"Converting Color Mode",
|
||||
[this, &job, context, flatten]{
|
||||
ContextWriter writer(context);
|
||||
Transaction transaction(writer.context(), "Color Mode Change");
|
||||
Sprite* sprite(writer.sprite());
|
||||
{
|
||||
RenderTaskJob job(
|
||||
"Converting Color Mode",
|
||||
[this, &job, context, flatten]{
|
||||
ContextWriter writer(context);
|
||||
Transaction transaction(writer.context(), "Color Mode Change");
|
||||
Sprite* sprite(writer.sprite());
|
||||
|
||||
if (flatten)
|
||||
transaction.execute(new cmd::FlattenLayers(sprite));
|
||||
if (flatten)
|
||||
transaction.execute(new cmd::FlattenLayers(sprite));
|
||||
|
||||
transaction.execute(
|
||||
new cmd::SetPixelFormat(
|
||||
sprite, m_format,
|
||||
m_ditheringAlgorithm,
|
||||
m_ditheringMatrix, &job));
|
||||
if (!job.isCanceled())
|
||||
transaction.commit();
|
||||
});
|
||||
job.startJob();
|
||||
job.waitJob();
|
||||
transaction.execute(
|
||||
new cmd::SetPixelFormat(
|
||||
sprite, m_format,
|
||||
m_ditheringAlgorithm,
|
||||
m_ditheringMatrix, &job));
|
||||
if (!job.isCanceled())
|
||||
transaction.commit();
|
||||
});
|
||||
job.startJob();
|
||||
job.waitJob();
|
||||
}
|
||||
|
||||
if (context->isUIAvailable())
|
||||
app_refresh_screen();
|
||||
|
154
src/app/ui/dithering_selector.cpp
Normal file
154
src/app/ui/dithering_selector.cpp
Normal file
@ -0,0 +1,154 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/ui/dithering_selector.h"
|
||||
|
||||
#include "app/modules/palettes.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "base/bind.h"
|
||||
#include "doc/conversion_she.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "render/quantization.h"
|
||||
#include "she/surface.h"
|
||||
#include "she/system.h"
|
||||
#include "ui/graphics.h"
|
||||
#include "ui/listitem.h"
|
||||
#include "ui/paint_event.h"
|
||||
#include "ui/size_hint_event.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace {
|
||||
|
||||
class DitherItem : public ListItem {
|
||||
public:
|
||||
DitherItem(render::DitheringAlgorithm algo,
|
||||
const render::DitheringMatrix& matrix,
|
||||
const std::string& text)
|
||||
: ListItem(text)
|
||||
, m_algo(algo)
|
||||
, m_matrix(matrix) {
|
||||
generatePreview();
|
||||
}
|
||||
|
||||
render::DitheringAlgorithm algo() const { return m_algo; }
|
||||
render::DitheringMatrix matrix() const { return m_matrix; }
|
||||
|
||||
private:
|
||||
void generatePreview() {
|
||||
const doc::Palette* palette = get_current_palette();
|
||||
|
||||
const int w = 128, h = 16;
|
||||
doc::ImageRef image1(doc::Image::create(doc::IMAGE_RGB, w, h));
|
||||
doc::clear_image(image1.get(), 0);
|
||||
for (int y=0; y<h; ++y)
|
||||
for (int x=0; x<w; ++x) {
|
||||
int v = 255 * x / (w-1);
|
||||
image1->putPixel(x, y, doc::rgba(v, v, v, 255));
|
||||
}
|
||||
|
||||
doc::ImageRef image2(doc::Image::create(doc::IMAGE_INDEXED, w, h));
|
||||
doc::clear_image(image2.get(), 0);
|
||||
render::convert_pixel_format(
|
||||
image1.get(), image2.get(), IMAGE_INDEXED,
|
||||
m_algo, m_matrix, nullptr, palette, true, -1, nullptr);
|
||||
|
||||
m_preview = she::instance()->createRgbaSurface(w, h);
|
||||
doc::convert_image_to_surface(image2.get(), palette, m_preview,
|
||||
0, 0, 0, 0, w, h);
|
||||
}
|
||||
|
||||
void onSizeHint(SizeHintEvent& ev) override {
|
||||
gfx::Size sz = textSize();
|
||||
|
||||
sz.w = MAX(sz.w, m_preview->width()) + 4*guiscale();
|
||||
sz.h += 6*guiscale() + m_preview->height();
|
||||
|
||||
ev.setSizeHint(sz);
|
||||
}
|
||||
|
||||
void onPaint(PaintEvent& ev) override {
|
||||
Graphics* g = ev.graphics();
|
||||
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(this->theme());
|
||||
|
||||
gfx::Color fg, bg;
|
||||
if (isSelected()) {
|
||||
fg = theme->colors.listitemSelectedText();
|
||||
bg = theme->colors.listitemSelectedFace();
|
||||
}
|
||||
else {
|
||||
fg = theme->colors.listitemNormalText();
|
||||
bg = theme->colors.listitemNormalFace();
|
||||
}
|
||||
|
||||
gfx::Rect rc = clientBounds();
|
||||
g->fillRect(bg, rc);
|
||||
|
||||
gfx::Size textsz = textSize();
|
||||
g->drawText(text(), fg, bg,
|
||||
gfx::Point(rc.x+2*guiscale(),
|
||||
rc.y+2*guiscale()));
|
||||
g->drawRgbaSurface(
|
||||
m_preview,
|
||||
rc.x+2*guiscale(),
|
||||
rc.y+4*guiscale()+textsz.h);
|
||||
}
|
||||
|
||||
render::DitheringAlgorithm m_algo;
|
||||
render::DitheringMatrix m_matrix;
|
||||
she::Surface* m_preview;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
DitheringSelector::DitheringSelector()
|
||||
{
|
||||
addItem(new DitherItem(render::DitheringAlgorithm::None,
|
||||
render::DitheringMatrix(), "No Dithering"));
|
||||
addItem(new DitherItem(render::DitheringAlgorithm::Ordered,
|
||||
render::BayerMatrix(8), "Ordered Dithering - Bayer Matrix 8x8"));
|
||||
addItem(new DitherItem(render::DitheringAlgorithm::Ordered,
|
||||
render::BayerMatrix(4), "Ordered Dithering - Bayer Matrix 4x4"));
|
||||
addItem(new DitherItem(render::DitheringAlgorithm::Ordered,
|
||||
render::BayerMatrix(2), "Ordered Dithering - Bayer Matrix 2x2"));
|
||||
addItem(new DitherItem(render::DitheringAlgorithm::OldOrdered,
|
||||
render::BayerMatrix(8), "Old Dithering - Bayer Matrix 8x8"));
|
||||
addItem(new DitherItem(render::DitheringAlgorithm::OldOrdered,
|
||||
render::BayerMatrix(4), "Old Dithering - Bayer Matrix 4x4"));
|
||||
addItem(new DitherItem(render::DitheringAlgorithm::OldOrdered,
|
||||
render::BayerMatrix(2), "Old Dithering - Bayer Matrix 2x2"));
|
||||
|
||||
setSelectedItemIndex(0);
|
||||
setMinSize(getItem(0)->sizeHint());
|
||||
}
|
||||
|
||||
render::DitheringAlgorithm DitheringSelector::ditheringAlgorithm()
|
||||
{
|
||||
auto item = static_cast<DitherItem*>(getSelectedItem());
|
||||
if (item)
|
||||
return item->algo();
|
||||
else
|
||||
return render::DitheringAlgorithm::None;
|
||||
}
|
||||
|
||||
render::DitheringMatrix DitheringSelector::ditheringMatrix()
|
||||
{
|
||||
auto item = static_cast<DitherItem*>(getSelectedItem());
|
||||
if (item)
|
||||
return item->matrix();
|
||||
else
|
||||
return render::DitheringMatrix();
|
||||
}
|
||||
|
||||
} // namespace app
|
28
src/app/ui/dithering_selector.h
Normal file
28
src/app/ui/dithering_selector.h
Normal file
@ -0,0 +1,28 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_UI_DITHERING_SELECTOR_H_INCLUDED
|
||||
#define APP_UI_DITHERING_SELECTOR_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "render/dithering_algorithm.h"
|
||||
#include "render/ordered_dither.h"
|
||||
#include "ui/box.h"
|
||||
#include "ui/combobox.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
class DitheringSelector : public ui::ComboBox {
|
||||
public:
|
||||
DitheringSelector();
|
||||
|
||||
render::DitheringAlgorithm ditheringAlgorithm();
|
||||
render::DitheringMatrix ditheringMatrix();
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -169,8 +169,10 @@ Image* convert_pixel_format(
|
||||
|
||||
if (a == 0)
|
||||
*dst_it = new_mask_color;
|
||||
else
|
||||
else if (rgbmap)
|
||||
*dst_it = rgbmap->mapColor(r, g, b, a);
|
||||
else
|
||||
*dst_it = palette->findBestfit(r, g, b, a, new_mask_color);
|
||||
}
|
||||
ASSERT(dst_it == dst_end);
|
||||
break;
|
||||
@ -226,8 +228,10 @@ Image* convert_pixel_format(
|
||||
|
||||
if (a == 0)
|
||||
*dst_it = new_mask_color;
|
||||
else
|
||||
else if (rgbmap)
|
||||
*dst_it = rgbmap->mapColor(c, c, c, a);
|
||||
else
|
||||
*dst_it = palette->findBestfit(c, c, c, a, new_mask_color);
|
||||
}
|
||||
ASSERT(dst_it == dst_end);
|
||||
break;
|
||||
@ -312,7 +316,11 @@ Image* convert_pixel_format(
|
||||
g = rgba_getg(c);
|
||||
b = rgba_getb(c);
|
||||
a = rgba_geta(c);
|
||||
*dst_it = rgbmap->mapColor(r, g, b, a);
|
||||
|
||||
if (rgbmap)
|
||||
*dst_it = rgbmap->mapColor(r, g, b, a);
|
||||
else
|
||||
*dst_it = palette->findBestfit(r, g, b, a, new_mask_color);
|
||||
}
|
||||
}
|
||||
ASSERT(dst_it == dst_end);
|
||||
|
@ -8,9 +8,23 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "ui/combobox.h"
|
||||
|
||||
#include "gfx/size.h"
|
||||
#include "she/font.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/button.h"
|
||||
#include "ui/entry.h"
|
||||
#include "ui/listbox.h"
|
||||
#include "ui/listitem.h"
|
||||
#include "ui/manager.h"
|
||||
#include "ui/message.h"
|
||||
#include "ui/resize_event.h"
|
||||
#include "ui/scale.h"
|
||||
#include "ui/size_hint_event.h"
|
||||
#include "ui/system.h"
|
||||
#include "ui/theme.h"
|
||||
#include "ui/view.h"
|
||||
#include "ui/window.h"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@ -41,15 +55,12 @@ private:
|
||||
class ComboBoxListBox : public ListBox {
|
||||
public:
|
||||
ComboBoxListBox(ComboBox* comboBox)
|
||||
: m_comboBox(comboBox)
|
||||
{
|
||||
for (ComboBox::ListItems::iterator
|
||||
it = comboBox->begin(), end = comboBox->end(); it != end; ++it)
|
||||
: m_comboBox(comboBox) {
|
||||
for (auto it=comboBox->begin(), end=comboBox->end(); it!=end; ++it)
|
||||
addChild(*it);
|
||||
}
|
||||
|
||||
void clean()
|
||||
{
|
||||
void clean() {
|
||||
// Remove all added items so ~Widget() don't delete them.
|
||||
removeAllChildren();
|
||||
selectChild(nullptr);
|
||||
@ -574,12 +585,18 @@ void ComboBox::openListBox()
|
||||
m_window->noBorderNoChildSpacing();
|
||||
|
||||
Widget* viewport = view->viewport();
|
||||
int size = getItemCount();
|
||||
viewport->setMinSize
|
||||
(gfx::Size(
|
||||
m_button->bounds().x2() - m_entry->bounds().x - view->border().width(),
|
||||
+(2*guiscale()+m_listbox->textHeight())*MID(1, size, 16)+
|
||||
+viewport->border().height()));
|
||||
{
|
||||
gfx::Rect entryBounds = m_entry->bounds();
|
||||
gfx::Size size;
|
||||
size.w = m_button->bounds().x2() - entryBounds.x - view->border().width();
|
||||
size.h = viewport->border().height();
|
||||
for (ListItem* item : m_items)
|
||||
size.h += item->sizeHint().h;
|
||||
|
||||
int max = MAX(entryBounds.y, ui::display_h() - entryBounds.y2()) - 8*guiscale();
|
||||
size.h = MID(textHeight(), size.h, max);
|
||||
viewport->setMinSize(size);
|
||||
}
|
||||
|
||||
m_window->addChild(view);
|
||||
view->attachToView(m_listbox);
|
||||
@ -631,13 +648,14 @@ void ComboBox::switchListBox()
|
||||
|
||||
gfx::Rect ComboBox::getListBoxPos() const
|
||||
{
|
||||
gfx::Rect rc(gfx::Point(m_entry->bounds().x,
|
||||
m_entry->bounds().y2()),
|
||||
gfx::Rect entryBounds = m_entry->bounds();
|
||||
gfx::Rect rc(gfx::Point(entryBounds.x,
|
||||
entryBounds.y2()),
|
||||
gfx::Point(m_button->bounds().x2(),
|
||||
m_entry->bounds().y2()+m_window->bounds().h));
|
||||
entryBounds.y2() + m_window->bounds().h));
|
||||
|
||||
if (rc.y2() > ui::display_h())
|
||||
rc.offset(0, -(rc.h + m_entry->bounds().h));
|
||||
rc.offset(0, -(rc.h + entryBounds.h));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user