Re-implement ordered dither in render library

This commit is contained in:
David Capello 2015-06-22 20:58:33 -03:00
parent ae0bf75965
commit 70ea54aa20
4 changed files with 239 additions and 122 deletions

View File

@ -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 <br />
Code to quantize RGB images with ordered dither method.
## License
This program is free software; you can redistribute it and/or modify

173
src/render/ordered_dither.h Normal file
View File

@ -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 <array>
namespace render {
// Creates a Bayer dither matrix.
template<int N>
class BayerMatrix {
static int D2[4];
std::array<int, N*N> m_matrix;
public:
int maxValue() const { return N*N; }
BayerMatrix() {
int c = 0;
for (int i=0; i<N; ++i)
for (int j=0; j<N; ++j)
m_matrix[c++] = Dn(i, j, N);
}
int operator()(int i, int j) const {
return m_matrix[(i%N)*N + (j%N)];
}
const std::array<int, N*N>& 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 N>
int BayerMatrix<N>::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<typename Matrix>
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<typename Matrix>
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<RgbTraits> srcBits(srcImage);
doc::LockImageBits<IndexedTraits> dstBits(dstImage);
auto srcIt = srcBits.begin();
auto dstIt = dstBits.begin();
int w = srcImage->width();
int h = srcImage->height();
for (int y=0; y<h; ++y) {
for (int x=0; x<w; ++x, ++srcIt, ++dstIt) {
ASSERT(srcIt != srcBits.end());
ASSERT(dstIt != dstBits.end());
*dstIt = ditherRgbPixelToIndex(matrix, *srcIt, x+u, y+v, rgbmap, palette);
}
}
}
private:
int m_transparentIndex;
};
} // namespace render
#endif

View File

@ -0,0 +1,61 @@
// 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.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gtest/gtest.h>
#include "render/ordered_dither.h"
using namespace doc;
using namespace render;
TEST(BayerMatrix, CheckD2)
{
BayerMatrix<2> matrix;
std::array<int, 2*2> expected = {
0, 2,
3, 1
};
EXPECT_EQ(expected, matrix.array());
}
TEST(BayerMatrix, CheckD4)
{
BayerMatrix<4> matrix;
std::array<int, 4*4> 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<int, 8*8> 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();
}

View File

@ -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 <algorithm>
@ -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<RgbTraits> src_bits(src_image);
LockImageBits<IndexedTraits> dst_bits(dst_image);
LockImageBits<RgbTraits>::const_iterator src_it = src_bits.begin();
LockImageBits<IndexedTraits>::iterator dst_it = dst_bits.begin();
for (y=0; y<src_image->height(); ++y) {
for (x=0; x<src_image->width(); ++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