/* Blue noise generation using the void-and-cluster method as described in
*
* The void - and - cluster method for dither array generation
* Ulichney , Robert A ( 1993 )
*
* http : //cv.ulichney.com/papers/1993-void-cluster.pdf
*
* Note that running with openmp ( - DUSE_OPENMP ) will trigger additional
* randomness due to computing reductions in parallel , and is not recommended
* unless generating very large dither arrays .
*/
#include <assert.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <stdio.h>
/* Booleans and utility functions */
#ifndef TRUE
# define TRUE 1
#endif
#ifndef FALSE
# define FALSE 0
#endif
typedef int bool_t;
int
imin (int x, int y)
{
return x < y ? x : y;
}
/* Memory allocation */
void *
malloc_abc (unsigned int a, unsigned int b, unsigned int c)
{
if (a >= INT32_MAX / b)
return NULL;
else if (a * b >= INT32_MAX / c)
return NULL;
else
return malloc (a * b * c);
}
/* Random number generation */
typedef uint32_t xorwow_state_t[5 ];
uint32_t
xorwow_next (xorwow_state_t *state)
{
uint32_t s = (*state)[0 ],
t = (*state)[3 ];
(*state)[3 ] = (*state)[2 ];
(*state)[2 ] = (*state)[1 ];
(*state)[1 ] = s;
t ^= t >> 2 ;
t ^= t << 1 ;
t ^= s ^ (s << 4 );
(*state)[0 ] = t;
(*state)[4 ] += 362437 ;
return t + (*state)[4 ];
}
float
xorwow_float (xorwow_state_t *s)
{
return (xorwow_next (s) >> 9 ) / (float )((1 << 23 ) - 1 );
}
/* Floating point matrices
*
* Used to cache the cluster sizes .
*/
typedef struct matrix_t {
int width;
int height;
float *buffer;
} matrix_t;
bool_t
matrix_init (matrix_t *matrix, int width, int height)
{
float *buffer;
if (!matrix)
return FALSE ;
buffer = malloc_abc (width, height, sizeof (float ));
if (!buffer)
return FALSE ;
matrix->buffer = buffer;
matrix->width = width;
matrix->height = height;
return TRUE ;
}
bool_t
matrix_copy (matrix_t *dst, matrix_t const *src)
{
float *srcbuf = src->buffer,
*srcend = src->buffer + src->width * src->height,
*dstbuf = dst->buffer;
if (dst->width != src->width || dst->height != src->height)
return FALSE ;
while (srcbuf < srcend)
*dstbuf++ = *srcbuf++;
return TRUE ;
}
float *
matrix_get (matrix_t *matrix, int x, int y)
{
return &matrix->buffer[y * matrix->width + x];
}
void
matrix_destroy (matrix_t *matrix)
{
free (matrix->buffer);
}
/* Binary patterns */
typedef struct pattern_t {
int width;
int height;
bool_t *buffer;
} pattern_t;
bool_t
pattern_init (pattern_t *pattern, int width, int height)
{
bool_t *buffer;
if (!pattern)
return FALSE ;
buffer = malloc_abc (width, height, sizeof (bool_t));
if (!buffer)
return FALSE ;
pattern->buffer = buffer;
pattern->width = width;
pattern->height = height;
return TRUE ;
}
bool_t
pattern_copy (pattern_t *dst, pattern_t const *src)
{
bool_t *srcbuf = src->buffer,
*srcend = src->buffer + src->width * src->height,
*dstbuf = dst->buffer;
if (dst->width != src->width || dst->height != src->height)
return FALSE ;
while (srcbuf < srcend)
*dstbuf++ = *srcbuf++;
return TRUE ;
}
bool_t *
pattern_get (pattern_t *pattern, int x, int y)
{
return &pattern->buffer[y * pattern->width + x];
}
void
pattern_fill_white_noise (pattern_t *pattern, float fraction,
xorwow_state_t *s)
{
bool_t *buffer = pattern->buffer;
bool_t *end = buffer + (pattern->width * pattern->height);
while (buffer < end)
*buffer++ = xorwow_float (s) < fraction;
}
void
pattern_destroy (pattern_t *pattern)
{
free (pattern->buffer);
}
/* Dither arrays */
typedef struct array_t {
int width;
int height;
uint32_t *buffer;
} array_t;
bool_t
array_init (array_t *array, int width, int height)
{
uint32_t *buffer;
if (!array)
return FALSE ;
buffer = malloc_abc (width, height, sizeof (uint32_t));
if (!buffer)
return FALSE ;
array->buffer = buffer;
array->width = width;
array->height = height;
return TRUE ;
}
uint32_t *
array_get (array_t *array, int x, int y)
{
return &array->buffer[y * array->width + x];
}
bool_t
array_save_ppm (array_t *array, const char *filename)
{
FILE *f = fopen(filename, "wb" );
int i = 0 ;
int bpp = 2 ;
uint8_t buffer[1024 ];
if (!f)
return FALSE ;
if (array->width * array->height - 1 < 256 )
bpp = 1 ;
fprintf(f, "P5 %d %d %d\n" , array->width, array->height,
array->width * array->height - 1 );
while (i < array->width * array->height)
{
int j = 0 ;
for (; j < 1024 / bpp && j < array->width * array->height; ++j)
{
uint32_t v = array->buffer[i + j];
if (bpp == 2 )
{
buffer[2 * j] = v & 0 xff;
buffer[2 * j + 1 ] = (v & 0 xff00) >> 8 ;
} else {
buffer[j] = v;
}
}
fwrite((void *)buffer, bpp, j, f);
i += j;
}
if (fclose(f) != 0 )
return FALSE ;
return TRUE ;
}
bool_t
array_save (array_t *array, const char *filename)
{
int x, y;
FILE *f = fopen(filename, "wb" );
if (!f)
return FALSE ;
fprintf (f,
"/* WARNING: This file is generated by make-blue-noise.c\n"
" * Please edit that file instead of this one.\n"
" */\n"
"\n"
"#ifndef BLUE_NOISE_%dX%d_H\n"
"#define BLUE_NOISE_%dX%d_H\n"
"\n"
"#include <stdint.h>\n"
"\n" , array->width, array->height, array->width, array->height);
fprintf (f, "static const uint16_t dither_blue_noise_%dx%d[%d] = {\n" ,
array->width, array->height, array->width * array->height);
for (y = 0 ; y < array->height; ++y)
{
fprintf (f, " " );
for (x = 0 ; x < array->width; ++x)
{
if (x != 0 )
fprintf (f, ", " );
fprintf (f, "%d" , *array_get (array, x, y));
}
fprintf (f, ",\n" );
}
fprintf (f, "};\n" );
fprintf (f, "\n#endif /* BLUE_NOISE_%dX%d_H */\n",
array->width, array->height);
if (fclose(f) != 0 )
return FALSE ;
return TRUE ;
}
void
array_destroy (array_t *array)
{
free (array->buffer);
}
/* Dither array generation */
bool_t
compute_cluster_sizes (pattern_t *pattern, matrix_t *matrix)
{
int width = pattern->width,
height = pattern->height;
if (matrix->width != width || matrix->height != height)
return FALSE ;
int px, py, qx, qy, dx, dy;
float tsqsi = 2 .f * 1 .5 f * 1 .5 f;
#ifdef USE_OPENMP
#pragma omp parallel for default (none) \
private (py, px, qy, qx, dx, dy) \
shared (height, width, pattern, matrix, tsqsi)
#endif
for (py = 0 ; py < height; ++py)
{
for (px = 0 ; px < width; ++px)
{
bool_t pixel = *pattern_get (pattern, px, py);
float dist = 0 .f;
for (qx = 0 ; qx < width; ++qx)
{
dx = imin (abs (qx - px), width - abs (qx - px));
dx = dx * dx;
for (qy = 0 ; qy < height; ++qy)
{
dy = imin (abs (qy - py), height - abs (qy - py));
dy = dy * dy;
dist += (pixel == *pattern_get (pattern, qx, qy))
* expf (- (dx + dy) / tsqsi);
}
}
*matrix_get (matrix, px, py) = dist;
}
}
return TRUE ;
}
bool_t
swap_pixel (pattern_t *pattern, matrix_t *matrix, int x, int y)
{
int width = pattern->width,
height = pattern->height;
bool_t new ;
float f,
dist = 0 .f,
tsqsi = 2 .f * 1 .5 f * 1 .5 f;
int px, py, dx, dy;
bool_t b;
new = !*pattern_get (pattern, x, y);
*pattern_get (pattern, x, y) = new ;
if (matrix->width != width || matrix->height != height)
return FALSE ;
#ifdef USE_OPENMP
#pragma omp parallel for reduction (+:dist) default (none) \
private (px, py, dx, dy, b, f) \
shared (x, y, width, height, pattern, matrix, new , tsqsi)
#endif
for (py = 0 ; py < height; ++py)
{
dy = imin (abs (py - y), height - abs (py - y));
dy = dy * dy;
for (px = 0 ; px < width; ++px)
{
dx = imin (abs (px - x), width - abs (px - x));
dx = dx * dx;
b = (*pattern_get (pattern, px, py) == new );
f = expf (- (dx + dy) / tsqsi);
*matrix_get (matrix, px, py) += (2 * b - 1 ) * f;
dist += b * f;
}
}
*matrix_get (matrix, x, y) = dist;
return TRUE ;
}
void
largest_cluster (pattern_t *pattern, matrix_t *matrix,
bool_t pixel, int *xmax, int *ymax)
{
int width = pattern->width,
height = pattern->height;
int x, y;
float vmax = -INFINITY;
#ifdef USE_OPENMP
#pragma omp parallel default (none) \
private (x, y) \
shared (height, width, pattern, matrix, pixel, xmax, ymax, vmax)
#endif
{
int xbest = -1 ,
ybest = -1 ;
#ifdef USE_OPENMP
float vbest = -INFINITY;
#pragma omp for reduction (max: vmax) collapse (2 )
#endif
for (y = 0 ; y < height; ++y)
{
for (x = 0 ; x < width; ++x)
{
if (*pattern_get (pattern, x, y) != pixel)
continue ;
if (*matrix_get (matrix, x, y) > vmax)
{
vmax = *matrix_get (matrix, x, y);
#ifdef USE_OPENMP
vbest = vmax;
#endif
xbest = x;
ybest = y;
}
}
}
#ifdef USE_OPENMP
#pragma omp barrier
#pragma omp critical
{
if (vmax == vbest)
{
*xmax = xbest;
*ymax = ybest;
}
}
#else
*xmax = xbest;
*ymax = ybest;
#endif
}
assert (vmax > -INFINITY);
}
void
generate_initial_binary_pattern (pattern_t *pattern, matrix_t *matrix)
{
int xcluster = 0 ,
ycluster = 0 ,
xvoid = 0 ,
yvoid = 0 ;
for (;;)
{
largest_cluster (pattern, matrix, TRUE , &xcluster, &ycluster);
assert (*pattern_get (pattern, xcluster, ycluster) == TRUE );
swap_pixel (pattern, matrix, xcluster, ycluster);
largest_cluster (pattern, matrix, FALSE , &xvoid, &yvoid);
assert (*pattern_get (pattern, xvoid, yvoid) == FALSE );
swap_pixel (pattern, matrix, xvoid, yvoid);
if (xcluster == xvoid && ycluster == yvoid)
return ;
}
}
bool_t
generate_dither_array (array_t *array,
pattern_t const *prototype, matrix_t const *matrix,
pattern_t *temp_pattern, matrix_t *temp_matrix)
{
int width = prototype->width,
height = prototype->height;
int x, y, rank;
int initial_rank = 0 ;
if (array->width != width || array->height != height)
return FALSE ;
// Make copies of the prototype and associated sizes matrix since we will
// trash them
if (!pattern_copy (temp_pattern, prototype))
return FALSE ;
if (!matrix_copy (temp_matrix, matrix))
return FALSE ;
// Compute initial rank
for (y = 0 ; y < height; ++y)
{
for (x = 0 ; x < width; ++x)
{
if (*pattern_get (temp_pattern, x, y))
initial_rank += 1 ;
*array_get (array, x, y) = 0 ;
}
}
// Phase 1
for (rank = initial_rank; rank > 0 ; --rank)
{
largest_cluster (temp_pattern, temp_matrix, TRUE , &x, &y);
swap_pixel (temp_pattern, temp_matrix, x, y);
*array_get (array, x, y) = rank - 1 ;
}
// Make copies again for phases 2 & 3
if (!pattern_copy (temp_pattern, prototype))
return FALSE ;
if (!matrix_copy (temp_matrix, matrix))
return FALSE ;
// Phase 2 & 3
for (rank = initial_rank; rank < width * height; ++rank)
{
largest_cluster (temp_pattern, temp_matrix, FALSE , &x, &y);
swap_pixel (temp_pattern, temp_matrix, x, y);
*array_get (array, x, y) = rank;
}
return TRUE ;
}
bool_t
generate (int size, xorwow_state_t *s,
char const *c_filename, char const *ppm_filename)
{
bool_t ok = TRUE ;
pattern_t prototype, temp_pattern;
array_t array;
matrix_t matrix, temp_matrix;
printf ("Generating %dx%d blue noise...\n" , size, size);
if (!pattern_init (&prototype, size, size))
return FALSE ;
if (!pattern_init (&temp_pattern, size, size))
{
pattern_destroy (&prototype);
return FALSE ;
}
if (!matrix_init (&matrix, size, size))
{
pattern_destroy (&temp_pattern);
pattern_destroy (&prototype);
return FALSE ;
}
if (!matrix_init (&temp_matrix, size, size))
{
matrix_destroy (&matrix);
pattern_destroy (&temp_pattern);
pattern_destroy (&prototype);
return FALSE ;
}
if (!array_init (&array, size, size))
{
matrix_destroy (&temp_matrix);
matrix_destroy (&matrix);
pattern_destroy (&temp_pattern);
pattern_destroy (&prototype);
return FALSE ;
}
printf("Filling initial binary pattern with white noise...\n" );
pattern_fill_white_noise (&prototype, .1 , s);
printf("Initializing cluster sizes...\n" );
if (!compute_cluster_sizes (&prototype, &matrix))
{
fprintf (stderr, "Error while computing cluster sizes\n" );
ok = FALSE ;
goto out;
}
printf("Generating initial binary pattern...\n" );
generate_initial_binary_pattern (&prototype, &matrix);
printf("Generating dither array...\n" );
if (!generate_dither_array (&array, &prototype, &matrix,
&temp_pattern, &temp_matrix))
{
fprintf (stderr, "Error while generating dither array\n" );
ok = FALSE ;
goto out;
}
printf("Saving dither array...\n" );
if (!array_save (&array, c_filename))
{
fprintf (stderr, "Error saving dither array\n" );
ok = FALSE ;
goto out;
}
#if SAVE_PPM
if (!array_save_ppm (&array, ppm_filename))
{
fprintf (stderr, "Error saving dither array PPM\n" );
ok = FALSE ;
goto out;
}
#else
(void )ppm_filename;
#endif
printf("All done!\n" );
out:
array_destroy (&array);
matrix_destroy (&temp_matrix);
matrix_destroy (&matrix);
pattern_destroy (&temp_pattern);
pattern_destroy (&prototype);
return ok;
}
int
main (void )
{
xorwow_state_t s = {1185956906 , 12385940 , 983948 , 349208051 , 901842 };
if (!generate (64 , &s, "blue-noise-64x64.h" , "blue-noise-64x64.ppm" ))
return -1 ;
return 0 ;
}
Messung V0.5 in Prozent C=98 H=81 G=89
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-09)
¤
*© Formatika GbR, Deutschland