Add widget to choose dithering algorithm + matrix

This commit is contained in:
David Capello 2017-05-22 15:43:53 -03:00
parent 829cc9ebec
commit bcdf598392
7 changed files with 281 additions and 65 deletions

View File

@ -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>

View File

@ -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

View File

@ -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();

View 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

View 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

View File

@ -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);

View File

@ -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;
}