mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-06 03:39:51 +00:00
Re-implement ordered dither in render library
This commit is contained in:
parent
ae0bf75965
commit
70ea54aa20
@ -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
173
src/render/ordered_dither.h
Normal 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
|
61
src/render/ordered_dither_tests.cpp
Normal file
61
src/render/ordered_dither_tests.cpp
Normal 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();
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user