Skip to content

Commit

Permalink
Add widget to choose dithering algorithm + matrix
Browse files Browse the repository at this point in the history
  • Loading branch information
dacap committed May 22, 2017
1 parent 829cc9e commit bcdf598
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 67 deletions.
1 change: 1 addition & 0 deletions data/widgets/color_mode.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 53 additions & 47 deletions src/app/commands/cmd_change_pixel_format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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");
Expand All @@ -57,27 +56,13 @@ class ConversionItem : public ListItem {
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 {
Expand Down Expand Up @@ -181,6 +166,7 @@ class ColorModeWindow : public app::gen::ColorMode {
, m_image(nullptr)
, m_imageBuffer(new doc::ImageBuffer)
, m_selectedItem(nullptr)
, m_ditheringSelector(nullptr)
{
doc::PixelFormat from = m_editor->sprite()->pixelFormat();

Expand All @@ -196,8 +182,12 @@ class ColorModeWindow : public app::gen::ColorMode {
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));
Expand Down Expand Up @@ -225,8 +215,13 @@ class ColorModeWindow : public app::gen::ColorMode {
}

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 {
Expand All @@ -236,13 +231,14 @@ class ColorModeWindow : public app::gen::ColorMode {
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() {
Expand Down Expand Up @@ -283,13 +279,19 @@ class ColorModeWindow : public app::gen::ColorMode {
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)
Expand All @@ -315,6 +317,7 @@ class ColorModeWindow : public app::gen::ColorMode {
doc::ImageBufferPtr m_imageBuffer;
base::UniquePtr<ConvertThread> m_bgThread;
ConversionItem* m_selectedItem;
DitheringSelector* m_ditheringSelector;
};

} // anonymous namespace
Expand Down Expand Up @@ -465,33 +468,36 @@ void ChangePixelFormatCommand::onExecute(Context* context)

m_format = window.pixelFormat();
m_ditheringAlgorithm = window.ditheringAlgorithm();
m_ditheringMatrix = window.ditheringMatrix();
flatten = window.flattenEnabled();
}

// No conversion needed
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());

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

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();
Expand Down
154 changes: 154 additions & 0 deletions src/app/ui/dithering_selector.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 28 additions & 0 deletions src/app/ui/dithering_selector.h
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit bcdf598

Please sign in to comment.