From 70ea54aa20f5833134182902b3702c2b52e34795 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 22 Jun 2015 20:58:33 -0300 Subject: [PATCH] Re-implement ordered dither in render library --- README.md | 5 - src/render/ordered_dither.h | 173 ++++++++++++++++++++++++++++ src/render/ordered_dither_tests.cpp | 61 ++++++++++ src/render/quantization.cpp | 122 +------------------- 4 files changed, 239 insertions(+), 122 deletions(-) create mode 100644 src/render/ordered_dither.h create mode 100644 src/render/ordered_dither_tests.cpp diff --git a/README.md b/README.md index 2bfbc7740..f5525bdcb 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,6 @@ of the following projects created by third-parties: * [tinyxml](http://www.sourceforge.net/projects/tinyxml) - [zlib license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/ZLIB.txt) * [zlib](http://www.gzip.org/zlib/) - [ZLIB license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/ZLIB.txt) -Other parts of code by: - -* Gary Oberbrunner
- Code to quantize RGB images with ordered dither method. - ## License This program is free software; you can redistribute it and/or modify diff --git a/src/render/ordered_dither.h b/src/render/ordered_dither.h new file mode 100644 index 000000000..6c07f3c09 --- /dev/null +++ b/src/render/ordered_dither.h @@ -0,0 +1,173 @@ +// Aseprite Render Library +// Copyright (c) 2001-2015 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef RENDER_ORDERED_DITHER_H_INCLUDED +#define RENDER_ORDERED_DITHER_H_INCLUDED +#pragma once + +#include "doc/color.h" +#include "doc/image_impl.h" +#include "doc/palette.h" +#include "doc/rgbmap.h" + +#include + +namespace render { + + // Creates a Bayer dither matrix. + template + class BayerMatrix { + static int D2[4]; + + std::array m_matrix; + + public: + int maxValue() const { return N*N; } + + BayerMatrix() { + int c = 0; + for (int i=0; i& array() const { + return m_matrix; + } + + private: + int Dn(int i, int j, int n) const { + ASSERT(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); + } + }; + + // Base 2x2 dither matrix, called D(2): + template + int BayerMatrix::D2[4] = { 0, 2, + 3, 1 }; + + class OrderedDither { + static int colorDistance(int r1, int g1, int b1, + int r2, int g2, int b2) { + // The factor for RGB components came from doc::rba_luma() + return int((r1-r2) * (r1-r2) * 21 + // 2126 + (g1-g2) * (g1-g2) * 71 + // 7152 + (b1-b2) * (b1-b2) * 7); // 722 + } + + public: + OrderedDither(int transparentIndex = -1) : m_transparentIndex(transparentIndex) { + } + + template + doc::color_t ditherRgbPixelToIndex( + const Matrix& matrix, + doc::color_t color, + int x, int y, + const doc::RgbMap* rgbmap, + const doc::Palette* palette) { + // Alpha=0, output transparent color + if (!doc::rgba_geta(color)) + return m_transparentIndex; + + // Get the nearest color in the palette with the given RGB + // values. + int r = doc::rgba_getr(color); + int g = doc::rgba_getg(color); + int b = doc::rgba_getb(color); + doc::color_t nearest1idx = + (rgbmap ? rgbmap->mapColor(r, g, b): + palette->findBestfit(r, g, b, m_transparentIndex)); + + doc::color_t nearest1rgb = palette->getEntry(nearest1idx); + int r1 = doc::rgba_getr(nearest1rgb); + int g1 = doc::rgba_getg(nearest1rgb); + int b1 = doc::rgba_getb(nearest1rgb); + + // Between the original color ('color' parameter) and 'nearest' + // index, we have an error (r1-r, g1-g, b1-b). Here we try to + // find the other nearest color with the same error but with + // different sign. + int r2 = r - (r1-r); + int g2 = g - (g1-g); + int b2 = b - (b1-b); + r2 = MID(0, r2, 255); + g2 = MID(0, g2, 255); + b2 = MID(0, b2, 255); + doc::color_t nearest2idx = + (rgbmap ? rgbmap->mapColor(r2, g2, b2): + palette->findBestfit(r2, g2, b2, m_transparentIndex)); + + // If both possible RGB colors use the same index, we cannot + // make any dither with these two colors. + if (nearest1idx == nearest2idx) + return nearest1idx; + + doc::color_t nearest2rgb = palette->getEntry(nearest2idx); + r2 = doc::rgba_getr(nearest2rgb); + g2 = doc::rgba_getg(nearest2rgb); + b2 = doc::rgba_getb(nearest2rgb); + + // Here we calculate the distance between the original 'color' + // and 'nearest1rgb'. The maximum possible distance is given by + // the distance between 'nearest1rgb' and 'nearest2rgb'. + int d = colorDistance(r1, g1, b1, r, g, b); + int D = colorDistance(r1, g1, b1, r2, g2, b2); + if (D == 0) + return nearest1idx; + + // We convert the d/D factor to the matrix range to compare it + // with the threshold. If d > threshold, it means that we're + // closer to 'nearest2rgb' than to 'nearest1rgb'. + d = matrix.maxValue() * d / D; + int threshold = matrix(x, y); + + return (d > threshold ? nearest2idx: + nearest1idx); + } + + template + void ditherRgbImageToIndexed(const Matrix& matrix, + const doc::Image* srcImage, + doc::Image* dstImage, + int u, int v, + const doc::RgbMap* rgbmap, + const doc::Palette* palette) { + const doc::LockImageBits srcBits(srcImage); + doc::LockImageBits dstBits(dstImage); + auto srcIt = srcBits.begin(); + auto dstIt = dstBits.begin(); + int w = srcImage->width(); + int h = srcImage->height(); + + for (int y=0; y + +#include "render/ordered_dither.h" + +using namespace doc; +using namespace render; + +TEST(BayerMatrix, CheckD2) +{ + BayerMatrix<2> matrix; + std::array expected = { + 0, 2, + 3, 1 + }; + EXPECT_EQ(expected, matrix.array()); +} + +TEST(BayerMatrix, CheckD4) +{ + BayerMatrix<4> matrix; + std::array expected = { + 0, 8, 2, 10, + 12, 4, 14, 6, + 3, 11, 1, 9, + 15, 7, 13, 5 + }; + EXPECT_EQ(expected, matrix.array()); +} + +TEST(BayerMatrix, CheckD8) +{ + BayerMatrix<8> matrix; + std::array expected = { + 0, 32, 8, 40, 2, 34, 10, 42, + 48, 16, 56, 24, 50, 18, 58, 26, + 12, 44, 4, 36, 14, 46, 6, 38, + 60, 28, 52, 20, 62, 30, 54, 22, + + 3, 35, 11, 43, 1, 33, 9, 41, + 51, 19, 59, 27, 49, 17, 57, 25, + 15, 47, 7, 39, 13, 45, 5, 37, + 63, 31, 55, 23, 61, 29, 53, 21 + }; + EXPECT_EQ(expected, matrix.array()); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/render/quantization.cpp b/src/render/quantization.cpp index 01bdee78e..dbab3e3ba 100644 --- a/src/render/quantization.cpp +++ b/src/render/quantization.cpp @@ -19,6 +19,7 @@ #include "doc/sprite.h" #include "gfx/hsv.h" #include "gfx/rgb.h" +#include "render/ordered_dither.h" #include "render/render.h" #include @@ -31,14 +32,6 @@ namespace render { using namespace doc; using namespace gfx; -// Converts a RGB image to indexed with ordered dithering method. -static Image* ordered_dithering( - const Image* src_image, - Image* dst_image, - int offsetx, int offsety, - const RgbMap* rgbmap, - const Palette* palette); - Palette* create_palette_from_rgb( const Sprite* sprite, frame_t fromFrame, @@ -85,7 +78,10 @@ Image* convert_pixel_format( if (image->pixelFormat() == IMAGE_RGB && pixelFormat == IMAGE_INDEXED && ditheringMethod == DitheringMethod::ORDERED) { - return ordered_dithering(image, new_image, 0, 0, rgbmap, palette); + BayerMatrix<8> matrix; + OrderedDither dither; + dither.ditherRgbImageToIndexed(matrix, image, new_image, 0, 0, rgbmap, palette); + return new_image; } color_t c; @@ -300,114 +296,6 @@ Image* convert_pixel_format( return new_image; } -/* Based on Gary Oberbrunner: */ -/*---------------------------------------------------------------------- - * Color image quantizer, from Paul Heckbert's paper in - * Computer Graphics, vol.16 #3, July 1982 (Siggraph proceedings), - * pp. 297-304. - * By Gary Oberbrunner, copyright c. 1988. - *---------------------------------------------------------------------- - */ - -/* Bayer-method ordered dither. The array line[] contains the - * intensity values for the line being processed. As you can see, the - * ordered dither is much simpler than the error dispersion dither. - * It is also many times faster, but it is not as accurate and - * produces cross-hatch * patterns on the output. - */ - -static int pattern[8][8] = { - { 0, 32, 8, 40, 2, 34, 10, 42 }, /* 8x8 Bayer ordered dithering */ - { 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */ - { 12, 44, 4, 36, 14, 46, 6, 38 }, /* is scaled to the 0..63 range */ - { 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */ - { 3, 35, 11, 43, 1, 33, 9, 41 }, /* to determine the action. */ - { 51, 19, 59, 27, 49, 17, 57, 25 }, - { 15, 47, 7, 39, 13, 45, 5, 37 }, - { 63, 31, 55, 23, 61, 29, 53, 21 } -}; - -#define DIST(r1,g1,b1,r2,g2,b2) (3 * ((r1)-(r2)) * ((r1)-(r2)) + \ - 4 * ((g1)-(g2)) * ((g1)-(g2)) + \ - 2 * ((b1)-(b2)) * ((b1)-(b2))) - -static Image* ordered_dithering( - const Image* src_image, - Image* dst_image, - int offsetx, int offsety, - const RgbMap* rgbmap, - const Palette* palette) -{ - int oppr, oppg, oppb, oppnrcm; - int dither_const; - int nr, ng, nb; - int r, g, b, a; - int nearestcm; - int x, y; - color_t c; - - const LockImageBits src_bits(src_image); - LockImageBits dst_bits(dst_image); - LockImageBits::const_iterator src_it = src_bits.begin(); - LockImageBits::iterator dst_it = dst_bits.begin(); - - for (y=0; yheight(); ++y) { - for (x=0; xwidth(); ++x, ++src_it, ++dst_it) { - ASSERT(src_it != src_bits.end()); - ASSERT(dst_it != dst_bits.end()); - - c = *src_it; - - r = rgba_getr(c); - g = rgba_getg(c); - b = rgba_getb(c); - a = rgba_geta(c); - - if (a != 0) { - nearestcm = rgbmap->mapColor(r, g, b); - /* rgb values for nearest color */ - nr = rgba_getr(palette->getEntry(nearestcm)); - ng = rgba_getg(palette->getEntry(nearestcm)); - nb = rgba_getb(palette->getEntry(nearestcm)); - /* Color as far from rgb as nrngnb but in the other direction */ - oppr = MID(0, 2*r - nr, 255); - oppg = MID(0, 2*g - ng, 255); - oppb = MID(0, 2*b - nb, 255); - /* Nearest match for opposite color: */ - oppnrcm = rgbmap->mapColor(oppr, oppg, oppb); - /* If they're not the same, dither between them. */ - /* Dither constant is measured by where the true - color lies between the two nearest approximations. - Since the most nearly opposite color is not necessarily - on the line from the nearest through the true color, - some triangulation error can be introduced. In the worst - case the r-nr distance can actually be less than the nr-oppr - distance. */ - if (oppnrcm != nearestcm) { - oppr = rgba_getr(palette->getEntry(oppnrcm)); - oppg = rgba_getg(palette->getEntry(oppnrcm)); - oppb = rgba_getb(palette->getEntry(oppnrcm)); - - dither_const = DIST(nr, ng, nb, oppr, oppg, oppb); - if (dither_const != 0) { - dither_const = 64 * DIST(r, g, b, nr, ng, nb) / dither_const; - dither_const = MIN(63, dither_const); - - if (pattern[(x+offsetx) & 7][(y+offsety) & 7] < dither_const) - nearestcm = oppnrcm; - } - } - } - else - nearestcm = 0; - - *dst_it = nearestcm; - } - } - - return dst_image; -} - ////////////////////////////////////////////////////////////////////// // Creation of optimized palette for RGB images // by David Capello