diff --git a/data/extensions/bayer-matrices/bayer2x2.bmp b/data/extensions/bayer-matrices/bayer2x2.bmp new file mode 100644 index 000000000..b87d96323 Binary files /dev/null and b/data/extensions/bayer-matrices/bayer2x2.bmp differ diff --git a/data/extensions/bayer-matrices/bayer4x4.bmp b/data/extensions/bayer-matrices/bayer4x4.bmp new file mode 100644 index 000000000..7056adfdc Binary files /dev/null and b/data/extensions/bayer-matrices/bayer4x4.bmp differ diff --git a/data/extensions/bayer-matrices/bayer8x8.bmp b/data/extensions/bayer-matrices/bayer8x8.bmp new file mode 100644 index 000000000..45ea27d85 Binary files /dev/null and b/data/extensions/bayer-matrices/bayer8x8.bmp differ diff --git a/data/extensions/bayer-matrices/package.json b/data/extensions/bayer-matrices/package.json new file mode 100644 index 000000000..ed09a72d9 --- /dev/null +++ b/data/extensions/bayer-matrices/package.json @@ -0,0 +1,26 @@ +{ + "name": "bayer-matrices", + "displayName": "Bayer Matrices for Dithering", + "description": "Dithering matrices created by Bryce E. Bayer", + "version": "1.0", + "publisher": "aseprite", + "contributes": { + "ditheringMatrices": [ + { + "id": "bayer8x8", + "name": "Bayer Matrix 8x8", + "path": "./bayer8x8.bmp" + }, + { + "id": "bayer4x4", + "name": "Bayer Matrix 4x4", + "path": "./bayer4x4.bmp" + }, + { + "id": "bayer2x2", + "name": "Bayer Matrix 2x2", + "path": "./bayer2x2.bmp" + } + ] + } +} diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index bf599c6bd..50e9628bb 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -382,6 +382,7 @@ add_library(app-lib ini_file.cpp job.cpp launcher.cpp + load_matrix.cpp log.cpp loop_tag.cpp modules.cpp diff --git a/src/app/commands/cmd_change_pixel_format.cpp b/src/app/commands/cmd_change_pixel_format.cpp index 4de0a6325..2009d4525 100644 --- a/src/app/commands/cmd_change_pixel_format.cpp +++ b/src/app/commands/cmd_change_pixel_format.cpp @@ -14,7 +14,8 @@ #include "app/commands/command.h" #include "app/commands/params.h" #include "app/context_access.h" -#include "app/file/file.h" +#include "app/extensions.h" +#include "app/load_matrix.h" #include "app/modules/editors.h" #include "app/modules/gui.h" #include "app/modules/palettes.h" @@ -376,45 +377,21 @@ void ChangePixelFormatCommand::onLoadParams(const Params& params) m_ditheringAlgorithm = render::DitheringAlgorithm::None; std::string matrix = params.get("dithering-matrix"); - // TODO object slicing here (from BayerMatrix -> DitheringMatrix if (!matrix.empty()) { - if (matrix == "bayer2x2") - m_ditheringMatrix = render::BayerMatrix(2); - else if (matrix == "bayer4x4") - m_ditheringMatrix = render::BayerMatrix(4); - else if (matrix == "bayer8x8") - m_ditheringMatrix = render::BayerMatrix(8); - else { - // Load a matrix from a file - base::UniquePtr doc(load_document(nullptr, matrix)); - if (doc) { - // Flatten layers - doc::Sprite* spr = doc->sprite(); - app::Context ctx; - cmd::FlattenLayers(spr).execute(&ctx); - - const doc::Layer* lay = spr->root()->firstLayer(); - const doc::Image* img = (lay && lay->cel(0) ? - lay->cel(0)->image(): nullptr); - if (img) { - const int w = spr->width(); - const int h = spr->height(); - m_ditheringMatrix = render::DitheringMatrix(h, w); - for (int i=0; igetPixel(j, i); - m_ditheringMatrix.calcMaxValue(); - } - else { - m_ditheringMatrix = render::DitheringMatrix(); - } - } - else - throw std::runtime_error("Invalid matrix name"); + // Try to get the matrix from the extensions + const render::DitheringMatrix* knownMatrix = + App::instance()->extensions().ditheringMatrix(matrix); + if (knownMatrix) { + m_ditheringMatrix = *knownMatrix; + } + // Then, if the matrix doesn't exist we try to load it from a file + else if (!load_dithering_matrix_from_sprite(matrix, m_ditheringMatrix)) { + throw std::runtime_error("Invalid matrix name"); } } // Default dithering matrix is BayerMatrix(8) else { + // TODO object slicing here (from BayerMatrix -> DitheringMatrix) m_ditheringMatrix = render::BayerMatrix(8); } } diff --git a/src/app/extensions.cpp b/src/app/extensions.cpp index e9b603046..799c2f7b8 100644 --- a/src/app/extensions.cpp +++ b/src/app/extensions.cpp @@ -11,12 +11,14 @@ #include "app/extensions.h" #include "app/ini_file.h" +#include "app/load_matrix.h" #include "app/pref/preferences.h" #include "app/resource_finder.h" #include "base/exception.h" #include "base/file_handle.h" #include "base/fs.h" #include "base/unique_ptr.h" +#include "render/dithering_matrix.h" #include "archive.h" #include "archive_entry.h" @@ -153,6 +155,21 @@ private: } // anonymous namespace +const render::DitheringMatrix& Extension::DitheringMatrixInfo::matrix() const +{ + if (!m_matrix) { + m_matrix = new render::DitheringMatrix; + load_dithering_matrix_from_sprite(m_path, *m_matrix); + } + return *m_matrix; +} + +void Extension::DitheringMatrixInfo::destroyMatrix() +{ + if (m_matrix) + delete m_matrix; +} + Extension::Extension(const std::string& path, const std::string& name, const std::string& displayName, @@ -167,6 +184,13 @@ Extension::Extension(const std::string& path, { } +Extension::~Extension() +{ + // Delete all matrices + for (auto& it : m_ditheringMatrices) + it.second.destroyMatrix(); +} + void Extension::addTheme(const std::string& id, const std::string& path) { m_themes[id] = path; @@ -177,6 +201,14 @@ void Extension::addPalette(const std::string& id, const std::string& path) m_palettes[id] = path; } +void Extension::addDitheringMatrix(const std::string& id, + const std::string& path, + const std::string& name) +{ + DitheringMatrixInfo info(path, name); + m_ditheringMatrices[id] = info; +} + bool Extension::canBeDisabled() const { return (m_isEnabled && @@ -347,6 +379,32 @@ ExtensionItems Extensions::palettes() const return palettes; } +const render::DitheringMatrix* Extensions::ditheringMatrix(const std::string& matrixId) +{ + for (auto ext : m_extensions) { + if (!ext->isEnabled()) // Ignore disabled themes + continue; + + auto it = ext->m_ditheringMatrices.find(matrixId); + if (it != ext->m_ditheringMatrices.end()) + return &it->second.matrix(); + } + return nullptr; +} + +std::vector Extensions::ditheringMatrices() +{ + std::vector result; + for (auto ext : m_extensions) { + if (!ext->isEnabled()) // Ignore disabled themes + continue; + + for (auto it : ext->m_ditheringMatrices) + result.push_back(it.second); + } + return result; +} + void Extensions::enableExtension(Extension* extension, const bool state) { extension->enable(state); @@ -493,6 +551,25 @@ Extension* Extensions::loadExtension(const std::string& path, extension->addPalette(palId, palPath); } } + + // Dithering matrices + auto ditheringMatrices = contributes["ditheringMatrices"]; + if (ditheringMatrices.is_array()) { + for (const auto& ditheringMatrix : ditheringMatrices.get_array()) { + std::string matId = ditheringMatrix.at("id").get_string(); + std::string matPath = ditheringMatrix.at("path").get_string(); + std::string matName = ditheringMatrix.at("name").get_string(); + + // The path must be always relative to the extension + matPath = base::join_path(path, matPath); + + LOG("EXT: New dithering matrix '%s' in '%s'\n", + matId.c_str(), + matPath.c_str()); + + extension->addDitheringMatrix(matId, matPath, matName); + } + } } if (extension) @@ -502,8 +579,9 @@ Extension* Extensions::loadExtension(const std::string& path, void Extensions::generateExtensionSignals(Extension* extension) { - if (!extension->themes().empty()) ThemesChange(extension); - if (!extension->palettes().empty()) PalettesChange(extension); + if (extension->hasThemes()) ThemesChange(extension); + if (extension->hasPalettes()) PalettesChange(extension); + if (extension->hasDitheringMatrices()) DitheringMatricesChange(extension); } } // namespace app diff --git a/src/app/extensions.h b/src/app/extensions.h index 610c32fa4..5ff043e61 100644 --- a/src/app/extensions.h +++ b/src/app/extensions.h @@ -8,12 +8,17 @@ #define APP_EXTENSIONS_H_INCLUDED #pragma once +#include "base/unique_ptr.h" #include "obs/signal.h" #include #include #include +namespace render { + class DitheringMatrix; +} + namespace app { // Key=theme/palette/etc. id @@ -25,11 +30,29 @@ namespace app { class Extension { friend class Extensions; public: + class DitheringMatrixInfo { + public: + DitheringMatrixInfo() : m_matrix(nullptr) { } + DitheringMatrixInfo(const std::string& path, + const std::string& name) + : m_path(path), m_name(name), m_matrix(nullptr) { } + + const std::string& name() const { return m_name; } + const render::DitheringMatrix& matrix() const; + void destroyMatrix(); + + private: + std::string m_path; + std::string m_name; + mutable render::DitheringMatrix* m_matrix; + }; + Extension(const std::string& path, const std::string& name, const std::string& displayName, const bool isEnabled, const bool isBuiltinExtension); + ~Extension(); const std::string& path() const { return m_path; } const std::string& name() const { return m_name; } @@ -40,12 +63,19 @@ namespace app { void addTheme(const std::string& id, const std::string& path); void addPalette(const std::string& id, const std::string& path); + void addDitheringMatrix(const std::string& id, + const std::string& path, + const std::string& name); bool isEnabled() const { return m_isEnabled; } bool isInstalled() const { return m_isInstalled; } bool canBeDisabled() const; bool canBeUninstalled() const; + bool hasThemes() const { return !m_themes.empty(); } + bool hasPalettes() const { return !m_palettes.empty(); } + bool hasDitheringMatrices() const { return !m_ditheringMatrices.empty(); } + private: void enable(const bool state); void uninstall(); @@ -55,6 +85,7 @@ namespace app { ExtensionItems m_themes; ExtensionItems m_palettes; + std::map m_ditheringMatrices; std::string m_path; std::string m_name; std::string m_displayName; @@ -81,10 +112,13 @@ namespace app { std::string themePath(const std::string& themeId); std::string palettePath(const std::string& palId); ExtensionItems palettes() const; + const render::DitheringMatrix* ditheringMatrix(const std::string& matrixId); + std::vector ditheringMatrices(); obs::signal NewExtension; obs::signal ThemesChange; obs::signal PalettesChange; + obs::signal DitheringMatricesChange; private: Extension* loadExtension(const std::string& path, diff --git a/src/app/load_matrix.cpp b/src/app/load_matrix.cpp new file mode 100644 index 000000000..09b11e9c2 --- /dev/null +++ b/src/app/load_matrix.cpp @@ -0,0 +1,52 @@ +// 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/load_matrix.h" + +#include "app/context.h" +#include "app/document.h" +#include "app/file/file.h" +#include "doc/layer.h" +#include "doc/sprite.h" +#include "render/dithering_matrix.h" + +namespace app { + +bool load_dithering_matrix_from_sprite( + const std::string& filename, + render::DitheringMatrix& matrix) +{ + base::UniquePtr doc(load_document(nullptr, filename)); + if (!doc) + return false; + + doc::Sprite* spr = doc->sprite(); + const doc::Layer* lay = (spr && spr->root() ? spr->root()->firstLayer(): + nullptr); + const doc::Image* img = (lay && lay->cel(0) ? lay->cel(0)->image(): + nullptr); + if (img) { + const int w = spr->width(); + const int h = spr->height(); + matrix = render::DitheringMatrix(h, w); + for (int i=0; igetPixel(j, i); + + matrix.calcMaxValue(); + } + else { + matrix = render::DitheringMatrix(); + } + + return true; +} + +} // namespace app diff --git a/src/app/load_matrix.h b/src/app/load_matrix.h new file mode 100644 index 000000000..45b6900ff --- /dev/null +++ b/src/app/load_matrix.h @@ -0,0 +1,23 @@ +// Aseprite +// Copyright (C) 2017 David Capello +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_LOAD_MATRIX_H_INCLUDED +#define APP_LOAD_MATRIX_H_INCLUDED +#pragma once + +namespace render { + class DitheringMatrix; +}; + +namespace app { + + bool load_dithering_matrix_from_sprite( + const std::string& filename, + render::DitheringMatrix& matrix); + +} // namespace app + +#endif diff --git a/src/app/ui/dithering_selector.cpp b/src/app/ui/dithering_selector.cpp index 0fa0c04ea..dbb30c8d3 100644 --- a/src/app/ui/dithering_selector.cpp +++ b/src/app/ui/dithering_selector.cpp @@ -10,6 +10,8 @@ #include "app/ui/dithering_selector.h" +#include "app/app.h" +#include "app/extensions.h" #include "app/modules/palettes.h" #include "app/ui/skin/skin_theme.h" #include "base/bind.h" @@ -157,35 +159,53 @@ private: } // anonymous namespace DitheringSelector::DitheringSelector(Type type) + : m_type(type) { - setUseCustomWidget(true); + Extensions& extensions = App::instance()->extensions(); - switch (type) { + // If an extension with "ditheringMatrices" is disable/enable, we + // regenerate this DitheringSelector + m_extChanges = + extensions.DitheringMatricesChange.connect( + base::Bind(&DitheringSelector::regenerate, this)); + + setUseCustomWidget(true); + regenerate(); +} + +void DitheringSelector::regenerate() +{ + removeAllItems(); + + Extensions& extensions = App::instance()->extensions(); + auto ditheringMatrices = extensions.ditheringMatrices(); + + switch (m_type) { case SelectBoth: 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::Old, - render::BayerMatrix(8), "Old Dithering - Bayer Matrix 8x8")); - addItem(new DitherItem(render::DitheringAlgorithm::Old, - render::BayerMatrix(4), "Old Dithering - Bayer Matrix 4x4")); - addItem(new DitherItem(render::DitheringAlgorithm::Old, - render::BayerMatrix(2), "Old Dithering - Bayer Matrix 2x2")); + for (const auto& it : ditheringMatrices) { + addItem( + new DitherItem( + render::DitheringAlgorithm::Ordered, + it.matrix(), + "Ordered Dithering+" + it.name())); + } + for (const auto& it : ditheringMatrices) { + addItem( + new DitherItem( + render::DitheringAlgorithm::Old, + it.matrix(), + "Old Dithering+" + it.name())); + } break; case SelectMatrix: addItem(new DitherItem(render::DitheringMatrix(), "No Dithering")); - addItem(new DitherItem(render::BayerMatrix(8), "Bayer Matrix 8x8")); - addItem(new DitherItem(render::BayerMatrix(4), "Bayer Matrix 4x4")); - addItem(new DitherItem(render::BayerMatrix(2), "Bayer Matrix 2x2")); + for (auto& it : ditheringMatrices) + addItem(new DitherItem(it.matrix(), it.name())); break; } - setSelectedItemIndex(0); setSizeHint(getItem(0)->sizeHint()); } diff --git a/src/app/ui/dithering_selector.h b/src/app/ui/dithering_selector.h index 2d7a01d01..686502351 100644 --- a/src/app/ui/dithering_selector.h +++ b/src/app/ui/dithering_selector.h @@ -8,6 +8,7 @@ #define APP_UI_DITHERING_SELECTOR_H_INCLUDED #pragma once +#include "obs/connection.h" #include "render/dithering_algorithm.h" #include "render/ordered_dither.h" #include "ui/box.h" @@ -26,6 +27,12 @@ namespace app { render::DitheringAlgorithm ditheringAlgorithm(); render::DitheringMatrix ditheringMatrix(); + + private: + void regenerate(); + + Type m_type; + obs::scoped_connection m_extChanges; }; } // namespace app