From ae67cab015123788c044ad2e0821ee5d12584589 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 23 May 2017 20:41:30 -0300 Subject: [PATCH] Add dithering options for Gradient tool (#418) --- src/app/tools/ink.h | 3 + src/app/tools/ink_processing.h | 289 +++++++++++++-------------- src/app/tools/inks.h | 1 + src/app/tools/tool_loop.h | 9 + src/app/ui/context_bar.cpp | 57 ++++-- src/app/ui/context_bar.h | 19 +- src/app/ui/dithering_selector.cpp | 2 +- src/app/ui/editor/tool_loop_impl.cpp | 9 + src/render/dithering.h | 15 ++ src/render/dithering_matrix.h | 82 ++++++++ src/render/ordered_dither.h | 66 +----- 11 files changed, 319 insertions(+), 233 deletions(-) create mode 100644 src/render/dithering.h create mode 100644 src/render/dithering_matrix.h diff --git a/src/app/tools/ink.h b/src/app/tools/ink.h index 7738477a4..3d5170b89 100644 --- a/src/app/tools/ink.h +++ b/src/app/tools/ink.h @@ -64,6 +64,9 @@ namespace app { // Returns true if this ink is used to mark slices virtual bool isSlice() const { return false; } + // Returns true if this tool uses the dithering options + virtual bool withDitheringOptions() const { return false; } + // Returns true if inkHline() needs source cel coordinates // instead of sprite coordinates (i.e. relative to // ToolLoop::getCelOrigin()). diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h index 8dcbb1e42..f974b660a 100644 --- a/src/app/tools/ink_processing.h +++ b/src/app/tools/ink_processing.h @@ -16,6 +16,7 @@ #include "filters/neighboring_pixels.h" #include "gfx/hsv.h" #include "gfx/rgb.h" +#include "render/dithering.h" namespace app { namespace tools { @@ -957,138 +958,19 @@ protected: }; template -class GradientInkProcessing : public DoubleInkProcessing, ImageTraits> { +class GradientInkProcessing : public TemporalPixmanGradient, + public DoubleInkProcessing, ImageTraits> { public: - GradientInkProcessing(ToolLoop* loop) { - } - - void processPixel(int x, int y) { - // Do nothing (it's specialized for each case) - } -}; - -template<> -class GradientInkProcessing : public TemporalPixmanGradient, - public DoubleInkProcessing, RgbTraits> { -public: - typedef DoubleInkProcessing, RgbTraits> base; - - GradientInkProcessing(ToolLoop* loop) - : TemporalPixmanGradient(loop) - , m_opacity(loop->getOpacity()) { - } - - void updateInk(ToolLoop* loop, Strokes& strokes) override { - color_t c0 = loop->getPrimaryColor(); - color_t c1 = loop->getSecondaryColor(); - - renderRgbaGradient(loop, strokes, c0, c1); - } - - void hline(int x1, int y, int x2, ToolLoop* loop) override { - m_tmpAddress = (RgbTraits::address_t)m_tmpImage->getPixelAddress(x1, y); - base::hline(x1, y, x2, loop); - } - - void processPixel(int x, int y) { - // As m_tmpAddress is the rendered gradient from pixman, its RGB - // values are premultiplied, here we can divide them by the alpha - // value to get the non-premultiplied values. - doc::color_t c = *m_tmpAddress; - int a = doc::rgba_geta(c); - int r, g, b; - if (a > 0) { - r = doc::rgba_getr(c) * 255 / a; - g = doc::rgba_getg(c) * 255 / a; - b = doc::rgba_getb(c) * 255 / a; - } - else - r = g = b = 0; - - *m_dstAddress = rgba_blender_normal(*m_srcAddress, - doc::rgba(r, g, b, a), - m_opacity); - ++m_tmpAddress; - } - -private: - const int m_opacity; -}; - - -template<> -class GradientInkProcessing : public TemporalPixmanGradient, - public DoubleInkProcessing, GrayscaleTraits> { -public: - typedef DoubleInkProcessing, GrayscaleTraits> base; - - GradientInkProcessing(ToolLoop* loop) - : TemporalPixmanGradient(loop) - , m_opacity(loop->getOpacity()) { - } - - void updateInk(ToolLoop* loop, Strokes& strokes) override { - color_t c0 = loop->getPrimaryColor(); - color_t c1 = loop->getSecondaryColor(); - int v0 = int(doc::graya_getv(c0)); - int a0 = int(doc::graya_geta(c0)); - int v1 = int(doc::graya_getv(c1)); - int a1 = int(doc::graya_geta(c1)); - c0 = doc::rgba(v0, v0, v0, a0); - c1 = doc::rgba(v1, v1, v1, a1); - - renderRgbaGradient(loop, strokes, c0, c1); - } - - void hline(int x1, int y, int x2, ToolLoop* loop) override { - m_tmpAddress = (RgbTraits::address_t)m_tmpImage->getPixelAddress(x1, y); - base::hline(x1, y, x2, loop); - } - - void processPixel(int x, int y) { - // As m_tmpAddress is the rendered gradient from pixman, its RGB - // values are premultiplied, here we can divide them by the alpha - // value to get the non-premultiplied values. - doc::color_t c = *m_tmpAddress; - int a = doc::rgba_geta(c); - int v; - if (a > 0) { - // Here we could get R, G, or B because this is a grayscale gradient anyway. - v = doc::rgba_getr(c) * 255 / a; - } - else - v = 0; - - *m_dstAddress = graya_blender_normal(*m_srcAddress, - doc::graya(v, a), - m_opacity); - ++m_tmpAddress; - } - -private: - const int m_opacity; -}; - - -template<> -class GradientInkProcessing : public TemporalPixmanGradient, - public DoubleInkProcessing, IndexedTraits> { -public: - typedef DoubleInkProcessing, IndexedTraits> base; + typedef DoubleInkProcessing, ImageTraits> base; GradientInkProcessing(ToolLoop* loop) : TemporalPixmanGradient(loop) , m_opacity(loop->getOpacity()) , m_palette(get_current_palette()) , m_rgbmap(loop->getRgbMap()) - , m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) { - } - - void updateInk(ToolLoop* loop, Strokes& strokes) override { - color_t c0 = m_palette->getEntry(loop->getPrimaryColor()); - color_t c1 = m_palette->getEntry(loop->getSecondaryColor()); - - renderRgbaGradient(loop, strokes, c0, c1); + , m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) + , m_matrix(loop->getDitheringMatrix()) + , m_dither(loop->getDitheringAlgorithm()) { } void hline(int x1, int y, int x2, ToolLoop* loop) override { @@ -1096,33 +978,12 @@ public: base::hline(x1, y, x2, loop); } + void updateInk(ToolLoop* loop, Strokes& strokes) override { + // Do nothing + } + void processPixel(int x, int y) { - // As m_tmpAddress is the rendered gradient from pixman, its RGB - // values are premultiplied, here we can divide them by the alpha - // value to get the non-premultiplied values. - doc::color_t c = *m_tmpAddress; - int a = doc::rgba_geta(c); - int r, g, b; - if (a > 0) { - r = doc::rgba_getr(c) * 255 / a; - g = doc::rgba_getg(c) * 255 / a; - b = doc::rgba_getb(c) * 255 / a; - } - else - r = g = b = 0; - - doc::color_t c0 = *m_srcAddress; - if (int(c0) == m_maskIndex) - c0 = m_palette->getEntry(c0) & rgba_rgb_mask; // Alpha = 0 - else - c0 = m_palette->getEntry(c0); - c = rgba_blender_normal(c0, c, m_opacity); - - *m_dstAddress = m_rgbmap->mapColor(rgba_getr(c), - rgba_getg(c), - rgba_getb(c), - rgba_geta(c)); - ++m_tmpAddress; + // Do nothing (it's specialized for each case) } private: @@ -1130,8 +991,134 @@ private: const Palette* m_palette; const RgbMap* m_rgbmap; const int m_maskIndex; + const render::DitheringMatrix m_matrix; + render::DitheringAlgorithmBase* m_dither; }; +template<> +void GradientInkProcessing::updateInk(ToolLoop* loop, Strokes& strokes) +{ + color_t c0 = loop->getPrimaryColor(); + color_t c1 = loop->getSecondaryColor(); + + renderRgbaGradient(loop, strokes, c0, c1); +} + +template<> +void GradientInkProcessing::processPixel(int x, int y) +{ + // As m_tmpAddress is the rendered gradient from pixman, its RGB + // values are premultiplied, here we can divide them by the alpha + // value to get the non-premultiplied values. + doc::color_t c = *m_tmpAddress; + int a = doc::rgba_geta(c); + int r, g, b; + if (a > 0) { + r = doc::rgba_getr(c) * 255 / a; + g = doc::rgba_getg(c) * 255 / a; + b = doc::rgba_getb(c) * 255 / a; + } + else + r = g = b = 0; + + c = rgba_blender_normal(*m_srcAddress, + doc::rgba(r, g, b, a), + m_opacity); + + if (m_dither) { + int i = m_dither->ditherRgbPixelToIndex( + m_matrix, c, x, y, m_rgbmap, m_palette); + c = m_palette->getEntry(i); + } + + *m_dstAddress = c; + ++m_tmpAddress; +} + +template<> +void GradientInkProcessing::updateInk(ToolLoop* loop, Strokes& strokes) +{ + color_t c0 = loop->getPrimaryColor(); + color_t c1 = loop->getSecondaryColor(); + int v0 = int(doc::graya_getv(c0)); + int a0 = int(doc::graya_geta(c0)); + int v1 = int(doc::graya_getv(c1)); + int a1 = int(doc::graya_geta(c1)); + c0 = doc::rgba(v0, v0, v0, a0); + c1 = doc::rgba(v1, v1, v1, a1); + + renderRgbaGradient(loop, strokes, c0, c1); +} + +template<> +void GradientInkProcessing::processPixel(int x, int y) +{ + // As m_tmpAddress is the rendered gradient from pixman, its RGB + // values are premultiplied, here we can divide them by the alpha + // value to get the non-premultiplied values. + doc::color_t c = *m_tmpAddress; + int a = doc::rgba_geta(c); + int v; + if (a > 0) { + // Here we could get R, G, or B because this is a grayscale gradient anyway. + v = doc::rgba_getr(c) * 255 / a; + } + else + v = 0; + + *m_dstAddress = graya_blender_normal(*m_srcAddress, + doc::graya(v, a), + m_opacity); + ++m_tmpAddress; +} + +template<> +void GradientInkProcessing::updateInk(ToolLoop* loop, Strokes& strokes) +{ + color_t c0 = m_palette->getEntry(loop->getPrimaryColor()); + color_t c1 = m_palette->getEntry(loop->getSecondaryColor()); + + renderRgbaGradient(loop, strokes, c0, c1); +} + +template<> +void GradientInkProcessing::processPixel(int x, int y) +{ + // As m_tmpAddress is the rendered gradient from pixman, its RGB + // values are premultiplied, here we can divide them by the alpha + // value to get the non-premultiplied values. + doc::color_t c = *m_tmpAddress; + int a = doc::rgba_geta(c); + int r, g, b; + if (a > 0) { + r = doc::rgba_getr(c) * 255 / a; + g = doc::rgba_getg(c) * 255 / a; + b = doc::rgba_getb(c) * 255 / a; + } + else + r = g = b = 0; + + doc::color_t c0 = *m_srcAddress; + if (int(c0) == m_maskIndex) + c0 = m_palette->getEntry(c0) & rgba_rgb_mask; // Alpha = 0 + else + c0 = m_palette->getEntry(c0); + c = rgba_blender_normal(c0, c, m_opacity); + + if (m_dither) { + *m_dstAddress = m_dither->ditherRgbPixelToIndex( + m_matrix, c, x, y, m_rgbmap, m_palette); + } + else { + *m_dstAddress = m_rgbmap->mapColor(rgba_getr(c), + rgba_getg(c), + rgba_getb(c), + rgba_geta(c)); + } + + ++m_tmpAddress; +} + ////////////////////////////////////////////////////////////////////// // Xor Ink diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h index d1df5b7cb..8bc031c80 100644 --- a/src/app/tools/inks.h +++ b/src/app/tools/inks.h @@ -144,6 +144,7 @@ public: bool isPaint() const override { return true; } bool isEffect() const override { return true; } bool dependsOnStroke() const override { return true; } + bool withDitheringOptions() const override { return true; } void prepareInk(ToolLoop* loop) override { setProc(get_ink_proc(loop)); diff --git a/src/app/tools/tool_loop.h b/src/app/tools/tool_loop.h index 7dcaa4828..23b6977ee 100644 --- a/src/app/tools/tool_loop.h +++ b/src/app/tools/tool_loop.h @@ -30,6 +30,11 @@ namespace doc { class Sprite; } +namespace render { + class DitheringAlgorithmBase; + class DitheringMatrix; +} + namespace app { class Context; class Document; @@ -222,6 +227,10 @@ namespace app { virtual void updateDirtyArea() = 0; virtual void updateStatusBar(const char* text) = 0; + + // For gradients + virtual render::DitheringMatrix getDitheringMatrix() = 0; + virtual render::DitheringAlgorithmBase* getDitheringAlgorithm() = 0; }; } // namespace tools diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp index e0c5b1df5..1e1788c10 100644 --- a/src/app/ui/context_bar.cpp +++ b/src/app/ui/context_bar.cpp @@ -32,6 +32,7 @@ #include "app/ui/brush_popup.h" #include "app/ui/button_set.h" #include "app/ui/color_button.h" +#include "app/ui/dithering_selector.h" #include "app/ui/icon_button.h" #include "app/ui/skin/skin_theme.h" #include "app/ui_context.h" @@ -44,6 +45,7 @@ #include "doc/palette.h" #include "doc/remap.h" #include "obs/connection.h" +#include "render/dithering.h" #include "she/surface.h" #include "she/system.h" #include "ui/button.h" @@ -1378,6 +1380,8 @@ ContextBar::ContextBar() addChild(m_tolerance = new ToleranceField()); addChild(m_contiguous = new ContiguousField()); addChild(m_paintBucketSettings = new PaintBucketSettingsField()); + addChild(m_ditheringSelector = new DitheringSelector()); + m_ditheringSelector->setUseCustomWidget(false); // Disable custom widget because the context bar is too small addChild(m_inkType = new InkTypeField(this)); addChild(m_inkOpacityLabel = new Label("Opacity:")); @@ -1628,65 +1632,69 @@ void ContextBar::updateForTool(tools::Tool* tool) m_spraySpeed->setValue(toolPref->spray.speed()); } - bool updateShade = (!m_inkShades->isVisible() && hasInkShades); + const bool updateShade = (!m_inkShades->isVisible() && hasInkShades); m_eyedropperField->updateFromPreferences(preferences.eyedropper); m_autoSelectLayer->setSelected(preferences.editor.autoSelectLayer()); // True if we have an image as brush - bool hasImageBrush = (activeBrush()->type() == kImageBrushType); + const bool hasImageBrush = (activeBrush()->type() == kImageBrushType); // True if the brush type supports angle. - bool hasBrushWithAngle = + const bool hasBrushWithAngle = (activeBrush()->size() > 1) && (activeBrush()->type() == kSquareBrushType || activeBrush()->type() == kLineBrushType); // True if the current tool is eyedropper. - bool needZoomButtons = tool && + const bool needZoomButtons = tool && (tool->getInk(0)->isZoom() || tool->getInk(1)->isZoom() || tool->getInk(0)->isScrollMovement() || tool->getInk(1)->isScrollMovement()); // True if the current tool is eyedropper. - bool isEyedropper = tool && + const bool isEyedropper = tool && (tool->getInk(0)->isEyedropper() || tool->getInk(1)->isEyedropper()); // True if the current tool is move tool. - bool isMove = tool && + const bool isMove = tool && (tool->getInk(0)->isCelMovement() || tool->getInk(1)->isCelMovement()); // True if the current tool is floodfill - bool isFloodfill = tool && + const bool isFloodfill = tool && (tool->getPointShape(0)->isFloodFill() || tool->getPointShape(1)->isFloodFill()); // True if the current tool needs tolerance options - bool hasTolerance = tool && + const bool hasTolerance = tool && (tool->getPointShape(0)->isFloodFill() || tool->getPointShape(1)->isFloodFill()); // True if the current tool needs spray options - bool hasSprayOptions = tool && + const bool hasSprayOptions = tool && (tool->getPointShape(0)->isSpray() || tool->getPointShape(1)->isSpray()); - bool hasSelectOptions = tool && + const bool hasSelectOptions = tool && (tool->getInk(0)->isSelection() || tool->getInk(1)->isSelection()); - bool isFreehand = tool && + const bool isFreehand = tool && (tool->getController(0)->isFreehand() || tool->getController(1)->isFreehand()); - bool showOpacity = + const bool showOpacity = (supportOpacity) && ((isPaint && (hasInkWithOpacity || hasImageBrush)) || (isEffect)); + const bool withDithering = + (tool->getInk(0)->withDitheringOptions() || + tool->getInk(1)->withDitheringOptions()); + // Show/Hide fields m_zoomButtons->setVisible(needZoomButtons); m_brushType->setVisible(supportOpacity && (!isFloodfill || (isFloodfill && hasImageBrush))); @@ -1706,6 +1714,7 @@ void ContextBar::updateForTool(tools::Tool* tool) m_paintBucketSettings->setVisible(hasTolerance); m_sprayBox->setVisible(hasSprayOptions); m_selectionOptionsBox->setVisible(hasSelectOptions); + m_ditheringSelector->setVisible(withDithering); m_selectionMode->setVisible(true); m_pivot->setVisible(true); m_dropPixels->setVisible(false); @@ -1955,4 +1964,28 @@ void ContextBar::setInkType(tools::InkType type) m_inkType->setInkType(type); } +render::DitheringMatrix ContextBar::ditheringMatrix() +{ + return m_ditheringSelector->ditheringMatrix(); +} + +render::DitheringAlgorithmBase* ContextBar::ditheringAlgorithm() +{ + static base::UniquePtr s_dither; + + switch (m_ditheringSelector->ditheringAlgorithm()) { + case render::DitheringAlgorithm::None: + s_dither.reset(nullptr); + break; + case render::DitheringAlgorithm::Ordered: + s_dither.reset(new render::OrderedDither2(-1)); + break; + case render::DitheringAlgorithm::Old: + s_dither.reset(new render::OrderedDither(-1)); + break; + } + + return s_dither.get(); +} + } // namespace app diff --git a/src/app/ui/context_bar.h b/src/app/ui/context_bar.h index 4abedbebf..f0eda9441 100644 --- a/src/app/ui/context_bar.h +++ b/src/app/ui/context_bar.h @@ -26,19 +26,25 @@ namespace doc { class Remap; } -namespace ui { - class Box; - class Button; - class Label; +namespace render { + class DitheringAlgorithmBase; + class DitheringMatrix; } namespace tools { class Tool; } +namespace ui { + class Box; + class Button; + class Label; +} + namespace app { class BrushSlot; + class DitheringSelector; class ContextBar : public ui::Box , public obs::observable @@ -70,6 +76,10 @@ namespace app { void setInkType(tools::InkType type); + // For gradients + render::DitheringMatrix ditheringMatrix(); + render::DitheringAlgorithmBase* ditheringAlgorithm(); + // Signals obs::signal BrushChange; @@ -134,6 +144,7 @@ namespace app { SprayWidthField* m_sprayWidth; SpraySpeedField* m_spraySpeed; ui::Box* m_selectionOptionsBox; + DitheringSelector* m_ditheringSelector; SelectionModeField* m_selectionMode; TransparentColorField* m_transparentColor; PivotField* m_pivot; diff --git a/src/app/ui/dithering_selector.cpp b/src/app/ui/dithering_selector.cpp index a0c380f9c..f8080c9f9 100644 --- a/src/app/ui/dithering_selector.cpp +++ b/src/app/ui/dithering_selector.cpp @@ -132,7 +132,7 @@ DitheringSelector::DitheringSelector() render::BayerMatrix(2), "Old Dithering - Bayer Matrix 2x2")); setSelectedItemIndex(0); - setMinSize(getItem(0)->sizeHint()); + setSizeHint(getItem(0)->sizeHint()); } render::DitheringAlgorithm DitheringSelector::ditheringAlgorithm() diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index 4381bee44..029bdd3b2 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -47,6 +47,7 @@ #include "doc/remap.h" #include "doc/slice.h" #include "doc/sprite.h" +#include "render/dithering.h" #include "render/render.h" #include "ui/ui.h" @@ -265,6 +266,14 @@ public: StatusBar::instance()->setStatusText(0, text); } + render::DitheringMatrix getDitheringMatrix() override { + return App::instance()->contextBar()->ditheringMatrix(); + } + + render::DitheringAlgorithmBase* getDitheringAlgorithm() override { + return App::instance()->contextBar()->ditheringAlgorithm(); + } + }; ////////////////////////////////////////////////////////////////////// diff --git a/src/render/dithering.h b/src/render/dithering.h new file mode 100644 index 000000000..ce2c9935a --- /dev/null +++ b/src/render/dithering.h @@ -0,0 +1,15 @@ +// Aseprite Render Library +// Copyright (c) 2017 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef RENDER_DITHERING_H_INCLUDED +#define RENDER_DITHERING_H_INCLUDED +#pragma once + +#include "render/dithering_algorithm.h" +#include "render/dithering_matrix.h" +#include "render/ordered_dither.h" + +#endif diff --git a/src/render/dithering_matrix.h b/src/render/dithering_matrix.h new file mode 100644 index 000000000..791044e80 --- /dev/null +++ b/src/render/dithering_matrix.h @@ -0,0 +1,82 @@ +// Aseprite Render Library +// Copyright (c) 2017 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef RENDER_DITHERING_MATRIX_H_INCLUDED +#define RENDER_DITHERING_MATRIX_H_INCLUDED +#pragma once + +#include + +namespace render { + + class DitheringMatrix { + public: + DitheringMatrix() + : m_rows(1), m_cols(1) + , m_matrix(1, 1) + , m_maxValue(1) { + } + + DitheringMatrix(int rows, int cols) + : m_rows(rows), m_cols(cols) + , m_matrix(rows*cols, 0) + , m_maxValue(1) { + } + + int rows() const { return m_rows; } + int cols() const { return m_cols; } + + int maxValue() const { return m_maxValue; } + void calcMaxValue() { + m_maxValue = *std::max_element(m_matrix.begin(), + m_matrix.end()); + m_maxValue = std::max(m_maxValue, 1); + } + + int operator()(int i, int j) const { + return m_matrix[(i%m_rows)*m_cols + (j%m_cols)]; + } + + int& operator()(int i, int j) { + return m_matrix[(i%m_rows)*m_cols + (j%m_cols)]; + } + + private: + int m_rows, m_cols; + std::vector m_matrix; + int m_maxValue; + }; + + // Creates a Bayer dither matrix. + class BayerMatrix : public DitheringMatrix { + static int D2[4]; + + public: + BayerMatrix(int n) : DitheringMatrix(n, n) { + for (int i=0; i= 0 && i < n); + ASSERT(j >= 0 && j < n); + + if (n == 2) + return D2[i*2 + j]; + else + return + + 4*Dn(i%(n/2), j%(n/2), n/2) + + Dn(i/(n/2), j/(n/2), 2); + } + }; + +} // namespace render + +#endif diff --git a/src/render/ordered_dither.h b/src/render/ordered_dither.h index 1e6f67513..d700ffffd 100644 --- a/src/render/ordered_dither.h +++ b/src/render/ordered_dither.h @@ -12,6 +12,7 @@ #include "doc/image_impl.h" #include "doc/palette.h" #include "doc/rgbmap.h" +#include "render/dithering_matrix.h" #include "render/task_delegate.h" #include @@ -20,71 +21,6 @@ namespace render { - class DitheringMatrix { - public: - DitheringMatrix() - : m_rows(1), m_cols(1) - , m_matrix(1, 1) - , m_maxValue(1) { - } - - DitheringMatrix(int rows, int cols) - : m_rows(rows), m_cols(cols) - , m_matrix(rows*cols, 0) - , m_maxValue(1) { - } - - int rows() const { return m_rows; } - int cols() const { return m_cols; } - - int maxValue() const { return m_maxValue; } - void calcMaxValue() { - m_maxValue = *std::max_element(m_matrix.begin(), - m_matrix.end()); - m_maxValue = std::max(m_maxValue, 1); - } - - int operator()(int i, int j) const { - return m_matrix[(i%m_rows)*m_cols + (j%m_cols)]; - } - - int& operator()(int i, int j) { - return m_matrix[(i%m_rows)*m_cols + (j%m_cols)]; - } - - private: - int m_rows, m_cols; - std::vector m_matrix; - int m_maxValue; - }; - - // Creates a Bayer dither matrix. - class BayerMatrix : public DitheringMatrix { - static int D2[4]; - - public: - BayerMatrix(int n) : DitheringMatrix(n, n) { - for (int i=0; i= 0 && i < n); - ASSERT(j >= 0 && j < n); - - if (n == 2) - return D2[i*2 + j]; - else - return - + 4*Dn(i%(n/2), j%(n/2), n/2) - + Dn(i/(n/2), j/(n/2), 2); - } - }; - class DitheringAlgorithmBase { public: virtual ~DitheringAlgorithmBase() { }