| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- // BLUENOISE :: https://github.com/prideout/par
- // Generator for infinite 2D point sequences using Recursive Wang Tiles.
- //
- // In addition to this source code, you'll need to download one of the following
- // tilesets, the first being 2 MB while the other is 257 KB. The latter cheats
- // by referencing the point sequence from the first tile for all 8 tiles. This
- // obviously produces poor results, but in many contexts, it isn't noticeable.
- //
- // https://prideout.net/assets/bluenoise.bin
- // https://prideout.net/assets/bluenoise.trimmed.bin
- //
- // The code herein is an implementation of the algorithm described in:
- //
- // Recursive Wang Tiles for Real-Time Blue Noise
- // Johannes Kopf, Daniel Cohen-Or, Oliver Deussen, Dani Lischinski
- // ACM Transactions on Graphics 25, 3 (Proc. SIGGRAPH 2006)
- //
- // If you use this software for research purposes, please cite the above paper
- // in any resulting publication.
- //
- // EXAMPLE
- //
- // Generate point samples whose density is guided by a 512x512 grayscale image:
- //
- // int npoints;
- // float* points;
- // int maxpoints = 1e6;
- // float density = 30000;
- // par_bluenoise_context* ctx;
- // ctx = par_bluenoise_from_file("bluenoise.bin", maxpoints);
- // par_bluenoise_density_from_gray(ctx, source_pixels, 512, 512, 1);
- // points = par_bluenoise_generate(ctx, density, &npoints);
- // ... Draw points here. Each point is a three-tuple of (X Y RANK).
- // par_bluenoise_free(ctx);
- //
- // Distributed under the MIT License, see bottom of file.
- #ifndef PAR_BLUENOISE_H
- #define PAR_BLUENOISE_H
- #ifdef __cplusplus
- extern "C" {
- #endif
- // -----------------------------------------------------------------------------
- // BEGIN PUBLIC API
- // -----------------------------------------------------------------------------
- typedef unsigned char par_byte;
- // Encapsulates a tile set and an optional density function.
- typedef struct par_bluenoise_context_s par_bluenoise_context;
- // Creates a bluenoise context using the given tileset. The first argument is
- // the file path the bin file. The second argument is the maximum number of
- // points that you expect to ever be generated.
- par_bluenoise_context* par_bluenoise_from_file(char const* path, int maxpts);
- // Creates a bluenoise context using the given tileset. The first and second
- // arguments describe a memory buffer containing the contents of the bin file.
- // The third argument is the maximum number of points that you expect to ever
- // be generated.
- par_bluenoise_context* par_bluenoise_from_buffer(
- par_byte const* buffer, int nbytes, int maxpts);
- // Sets up a scissoring rectangle using the given lower-left and upper-right
- // coordinates. By default the scissor encompasses [-0.5, -0.5] - [0.5, 0.5],
- // which is the entire sampling domain for the two "generate" methods.
- void par_bluenoise_set_viewport(
- par_bluenoise_context*, float left, float bottom, float right, float top);
- // Sets up a reference window size. The only purpose of this is to ensure
- // that apparent density remains constant when the window gets resized.
- // Clients should call this *before* calling par_bluenoise_set_viewport.
- void par_bluenoise_set_window(par_bluenoise_context*, int width, int height);
- // Frees all memory associated with the given bluenoise context.
- void par_bluenoise_free(par_bluenoise_context* ctx);
- // Copies a grayscale image into the bluenoise context to guide point density.
- // Darker regions generate a higher number of points. The given bytes-per-pixel
- // value is the stride between pixels.
- void par_bluenoise_density_from_gray(par_bluenoise_context* ctx,
- const unsigned char* pixels, int width, int height, int bpp);
- // Creates a binary mask to guide point density. The given bytes-per-pixel
- // value is the stride between pixels, which must be 4 or less.
- void par_bluenoise_density_from_color(par_bluenoise_context* ctx,
- const unsigned char* pixels, int width, int height, int bpp,
- unsigned int background_color, int invert);
- // Generates samples using Recursive Wang Tiles. This is really fast!
- // The returned pointer is a list of three-tuples, where XY are in [-0.5, +0.5]
- // and Z is a rank value that can be used to create a progressive ordering.
- // The caller should not free the returned pointer.
- float* par_bluenoise_generate(
- par_bluenoise_context* ctx, float density, int* npts);
- // Generates an ordered sequence of tuples with the specified sequence length.
- // This is slower than the other "generate" method because it uses a dumb
- // backtracking method to determine a reasonable density value, and it
- // automatically sorts the output by rank. The dims argument must be 2 or more;
- // it represents the desired stride (in floats) between consecutive verts in the
- // returned data buffer.
- float* par_bluenoise_generate_exact(
- par_bluenoise_context* ctx, int npts, int dims);
- // Performs an in-place sort of 3-tuples, based on the 3rd component, then
- // replaces the 3rd component with an index.
- void par_bluenoise_sort_by_rank(float* pts, int npts);
- #ifndef PAR_PI
- #define PAR_PI (3.14159265359)
- #define PAR_MIN(a, b) (a > b ? b : a)
- #define PAR_MAX(a, b) (a > b ? a : b)
- #define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v))
- #define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; }
- #define PAR_SQR(a) ((a) * (a))
- #endif
- #ifndef PAR_MALLOC
- #define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T)))
- #define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1))
- #define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N)))
- #define PAR_FREE(BUF) free(BUF)
- #endif
- #ifdef __cplusplus
- }
- #endif
- // -----------------------------------------------------------------------------
- // END PUBLIC API
- // -----------------------------------------------------------------------------
- #ifdef PAR_BLUENOISE_IMPLEMENTATION
- #include <stdio.h>
- #include <stdlib.h>
- #include <assert.h>
- #include <math.h>
- #include <string.h>
- #define PAR_MINI(a, b) ((a < b) ? a : b)
- #define PAR_MAXI(a, b) ((a > b) ? a : b)
- typedef struct {
- float x;
- float y;
- } par_vec2;
- typedef struct {
- float x;
- float y;
- float rank;
- } par_vec3;
- typedef struct {
- int n, e, s, w;
- int nsubtiles, nsubdivs, npoints, nsubpts;
- int** subdivs;
- par_vec2* points;
- par_vec2* subpts;
- } par_tile;
- struct par_bluenoise_context_s {
- par_vec3* points;
- par_tile* tiles;
- float global_density;
- float left, bottom, right, top;
- int ntiles, nsubtiles, nsubdivs;
- int npoints;
- int maxpoints;
- int density_width;
- int density_height;
- unsigned char* density;
- float mag;
- int window_width;
- int window_height;
- int abridged;
- };
- static float sample_density(par_bluenoise_context* ctx, float x, float y)
- {
- unsigned char* density = ctx->density;
- if (!density) {
- return 1;
- }
- int width = ctx->density_width;
- int height = ctx->density_height;
- y = 1 - y;
- x -= 0.5;
- y -= 0.5;
- float tx = x * PAR_MAXI(width, height);
- float ty = y * PAR_MAXI(width, height);
- x += 0.5;
- y += 0.5;
- tx += width / 2;
- ty += height / 2;
- int ix = PAR_CLAMP((int) tx, 0, width - 2);
- int iy = PAR_CLAMP((int) ty, 0, height - 2);
- return density[iy * width + ix] / 255.0f;
- }
- static void recurse_tile(
- par_bluenoise_context* ctx, par_tile* tile, float x, float y, int level)
- {
- float left = ctx->left, right = ctx->right;
- float top = ctx->top, bottom = ctx->bottom;
- float mag = ctx->mag;
- float tileSize = 1.f / powf(ctx->nsubtiles, level);
- if (x + tileSize < left || x > right || y + tileSize < bottom || y > top) {
- return;
- }
- float depth = powf(ctx->nsubtiles, 2 * level);
- float threshold = mag / depth * ctx->global_density - tile->npoints;
- int ntests = PAR_MINI(tile->nsubpts, threshold);
- float factor = 1.f / mag * depth / ctx->global_density;
- for (int i = 0; i < ntests; i++) {
- float px = x + tile->subpts[i].x * tileSize;
- float py = y + tile->subpts[i].y * tileSize;
- if (px < left || px > right || py < bottom || py > top) {
- continue;
- }
- if (sample_density(ctx, px, py) < (i + tile->npoints) * factor) {
- continue;
- }
- ctx->points[ctx->npoints].x = px - 0.5;
- ctx->points[ctx->npoints].y = py - 0.5;
- ctx->points[ctx->npoints].rank = (level + 1) + i * factor;
- ctx->npoints++;
- if (ctx->npoints >= ctx->maxpoints) {
- return;
- }
- }
- const float scale = tileSize / ctx->nsubtiles;
- if (threshold <= tile->nsubpts) {
- return;
- }
- level++;
- for (int ty = 0; ty < ctx->nsubtiles; ty++) {
- for (int tx = 0; tx < ctx->nsubtiles; tx++) {
- int tileIndex = tile->subdivs[0][ty * ctx->nsubtiles + tx];
- par_tile* subtile = &ctx->tiles[tileIndex];
- recurse_tile(ctx, subtile, x + tx * scale, y + ty * scale, level);
- }
- }
- }
- void par_bluenoise_set_window(par_bluenoise_context* ctx, int width, int height)
- {
- ctx->window_width = width;
- ctx->window_height = height;
- }
- void par_bluenoise_set_viewport(par_bluenoise_context* ctx, float left,
- float bottom, float right, float top)
- {
- // Transform [-.5, +.5] to [0, 1]
- left = ctx->left = left + 0.5;
- right = ctx->right = right + 0.5;
- bottom = ctx->bottom = bottom + 0.5;
- top = ctx->top = top + 0.5;
- // Determine magnification factor BEFORE clamping.
- float scale = 1000 * (top - bottom) / ctx->window_height;
- ctx->mag = powf(scale, -2);
- // The density function is only sampled in [0, +1].
- ctx->left = PAR_CLAMP(left, 0, 1);
- ctx->right = PAR_CLAMP(right, 0, 1);
- ctx->bottom = PAR_CLAMP(bottom, 0, 1);
- ctx->top = PAR_CLAMP(top, 0, 1);
- }
- float* par_bluenoise_generate(
- par_bluenoise_context* ctx, float density, int* npts)
- {
- ctx->global_density = density;
- ctx->npoints = 0;
- float left = ctx->left;
- float right = ctx->right;
- float bottom = ctx->bottom;
- float top = ctx->top;
- float mag = ctx->mag;
- int ntests = PAR_MINI(ctx->tiles[0].npoints, mag * ctx->global_density);
- float factor = 1.f / mag / ctx->global_density;
- for (int i = 0; i < ntests; i++) {
- float px = ctx->tiles[0].points[i].x;
- float py = ctx->tiles[0].points[i].y;
- if (px < left || px > right || py < bottom || py > top) {
- continue;
- }
- if (sample_density(ctx, px, py) < (i + 1) * factor) {
- continue;
- }
- ctx->points[ctx->npoints].x = px - 0.5;
- ctx->points[ctx->npoints].y = py - 0.5;
- ctx->points[ctx->npoints].rank = i * factor;
- ctx->npoints++;
- if (ctx->npoints >= ctx->maxpoints) {
- break;
- }
- }
- recurse_tile(ctx, &ctx->tiles[0], 0, 0, 0);
- *npts = ctx->npoints;
- return &ctx->points->x;
- }
- #define freadi() \
- *((int*) ptr); \
- ptr += sizeof(int)
- #define freadf() \
- *((float*) ptr); \
- ptr += sizeof(float)
- static par_bluenoise_context* par_bluenoise_create(
- char const* filepath, int nbytes, int maxpts)
- {
- par_bluenoise_context* ctx = PAR_MALLOC(par_bluenoise_context, 1);
- ctx->maxpoints = maxpts;
- ctx->points = PAR_MALLOC(par_vec3, maxpts);
- ctx->density = 0;
- ctx->abridged = 0;
- par_bluenoise_set_window(ctx, 1024, 768);
- par_bluenoise_set_viewport(ctx, -.5, -.5, .5, .5);
- char* buf = 0;
- if (nbytes == 0) {
- FILE* fin = fopen(filepath, "rb");
- assert(fin);
- fseek(fin, 0, SEEK_END);
- nbytes = (int) ftell(fin);
- fseek(fin, 0, SEEK_SET);
- buf = PAR_MALLOC(char, nbytes);
- int consumed = (int) fread(buf, nbytes, 1, fin);
- assert(consumed == 1);
- fclose(fin);
- }
- char const* ptr = buf ? buf : filepath;
- int ntiles = ctx->ntiles = freadi();
- int nsubtiles = ctx->nsubtiles = freadi();
- int nsubdivs = ctx->nsubdivs = freadi();
- par_tile* tiles = ctx->tiles = PAR_MALLOC(par_tile, ntiles);
- for (int i = 0; i < ntiles; i++) {
- tiles[i].n = freadi();
- tiles[i].e = freadi();
- tiles[i].s = freadi();
- tiles[i].w = freadi();
- tiles[i].subdivs = PAR_MALLOC(int*, nsubdivs);
- for (int j = 0; j < nsubdivs; j++) {
- int* subdiv = PAR_MALLOC(int, PAR_SQR(nsubtiles));
- for (int k = 0; k < PAR_SQR(nsubtiles); k++) {
- subdiv[k] = freadi();
- }
- tiles[i].subdivs[j] = subdiv;
- }
- tiles[i].npoints = freadi();
- tiles[i].points = PAR_MALLOC(par_vec2, tiles[i].npoints);
- for (int j = 0; j < tiles[i].npoints; j++) {
- tiles[i].points[j].x = freadf();
- tiles[i].points[j].y = freadf();
- }
- tiles[i].nsubpts = freadi();
- tiles[i].subpts = PAR_MALLOC(par_vec2, tiles[i].nsubpts);
- for (int j = 0; j < tiles[i].nsubpts; j++) {
- tiles[i].subpts[j].x = freadf();
- tiles[i].subpts[j].y = freadf();
- }
- // The following hack allows for an optimization whereby
- // the first tile's point set is re-used for every other tile.
- // This goes against the entire purpose of Recursive Wang Tiles,
- // but in many applications the qualatitive loss is not
- // observable, and the footprint savings are huge (10x).
- if (tiles[i].npoints == 0) {
- ctx->abridged = 1;
- tiles[i].npoints = tiles[0].npoints;
- tiles[i].points = tiles[0].points;
- tiles[i].nsubpts = tiles[0].nsubpts;
- tiles[i].subpts = tiles[0].subpts;
- }
- }
- free(buf);
- return ctx;
- }
- par_bluenoise_context* par_bluenoise_from_file(char const* path, int maxpts)
- {
- return par_bluenoise_create(path, 0, maxpts);
- }
- par_bluenoise_context* par_bluenoise_from_buffer(
- par_byte const* buffer, int nbytes, int maxpts)
- {
- return par_bluenoise_create((char const*) buffer, nbytes, maxpts);
- }
- void par_bluenoise_density_from_gray(par_bluenoise_context* ctx,
- const unsigned char* pixels, int width, int height, int bpp)
- {
- ctx->density_width = width;
- ctx->density_height = height;
- ctx->density = PAR_MALLOC(unsigned char, width * height);
- unsigned char* dst = ctx->density;
- for (int j = 0; j < height; j++) {
- for (int i = 0; i < width; i++) {
- *dst++ = 255 - (*pixels);
- pixels += bpp;
- }
- }
- }
- void par_bluenoise_density_from_color(par_bluenoise_context* ctx,
- const unsigned char* pixels, int width, int height, int bpp,
- unsigned int background_color, int invert)
- {
- unsigned int bkgd = background_color;
- ctx->density_width = width;
- ctx->density_height = height;
- ctx->density = PAR_MALLOC(unsigned char, width * height);
- unsigned char* dst = ctx->density;
- unsigned int mask = 0x000000ffu;
- if (bpp > 1) {
- mask |= 0x0000ff00u;
- }
- if (bpp > 2) {
- mask |= 0x00ff0000u;
- }
- if (bpp > 3) {
- mask |= 0xff000000u;
- }
- assert(bpp <= 4);
- for (int j = 0; j < height; j++) {
- for (int i = 0; i < width; i++) {
- unsigned int val = (*((unsigned int*) pixels)) & mask;
- val = invert ? (val == bkgd) : (val != bkgd);
- *dst++ = val ? 255 : 0;
- pixels += bpp;
- }
- }
- }
- void par_bluenoise_free(par_bluenoise_context* ctx)
- {
- free(ctx->points);
- for (int t = 0; t < ctx->ntiles; t++) {
- for (int s = 0; s < ctx->nsubdivs; s++) {
- free(ctx->tiles[t].subdivs[s]);
- }
- free(ctx->tiles[t].subdivs);
- if (t == 0 || !ctx->abridged) {
- free(ctx->tiles[t].points);
- free(ctx->tiles[t].subpts);
- }
- }
- free(ctx->tiles);
- free(ctx->density);
- }
- int cmp(const void* a, const void* b)
- {
- const par_vec3* v1 = (const par_vec3*) a;
- const par_vec3* v2 = (const par_vec3*) b;
- if (v1->rank < v2->rank) {
- return -1;
- }
- if (v1->rank > v2->rank) {
- return 1;
- }
- return 0;
- }
- void par_bluenoise_sort_by_rank(float* floats, int npts)
- {
- par_vec3* vecs = (par_vec3*) floats;
- qsort(vecs, npts, sizeof(vecs[0]), cmp);
- for (int i = 0; i < npts; i++) {
- vecs[i].rank = i;
- }
- }
- float* par_bluenoise_generate_exact(
- par_bluenoise_context* ctx, int npts, int stride)
- {
- assert(stride >= 2);
- int maxpoints = npts * 2;
- if (ctx->maxpoints < maxpoints) {
- free(ctx->points);
- ctx->maxpoints = maxpoints;
- ctx->points = PAR_MALLOC(par_vec3, maxpoints);
- }
- int ngenerated = 0;
- int nprevious = 0;
- int ndesired = npts;
- float density = 2048;
- while (ngenerated < ndesired) {
- par_bluenoise_generate(ctx, density, &ngenerated);
- // Might be paranoid, but break if something fishy is going on:
- if (ngenerated == nprevious) {
- return 0;
- }
- // Perform crazy heuristic to approach a nice density:
- if (ndesired / ngenerated >= 2) {
- density *= 2;
- } else {
- density += density / 10;
- }
- nprevious = ngenerated;
- }
- par_bluenoise_sort_by_rank(&ctx->points->x, ngenerated);
- if (stride != 3) {
- int nbytes = sizeof(float) * stride * ndesired;
- float* pts = PAR_MALLOC(float, stride * ndesired);
- float* dst = pts;
- const float* src = &ctx->points->x;
- for (int i = 0; i < ndesired; i++, src++) {
- *dst++ = *src++;
- *dst++ = *src++;
- if (stride > 3) {
- *dst++ = *src;
- dst += stride - 3;
- }
- }
- memcpy(ctx->points, pts, nbytes);
- free(pts);
- }
- return &ctx->points->x;
- }
- #undef PAR_MINI
- #undef PAR_MAXI
- #endif // PAR_BLUENOISE_IMPLEMENTATION
- #endif // PAR_BLUENOISE_H
- // par_bluenoise is distributed under the MIT license:
- //
- // Copyright (c) 2019 Philip Rideout
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- // SOFTWARE.
|