14.19: Filter › Scanlines, its own filter, on Swift's cdde327 kernel
This commit is contained in:
26 files changed
+905
-479
No files matched your search
+271
-81
@@ -1,6 +1,7 @@
|
||||
#include "DitherPixels.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
// Linux: GCD's dispatch_apply, run on QtConcurrent by Dither.cpp.
|
||||
@@ -168,74 +169,6 @@ static void tone_band(void *context, size_t first, size_t last) {
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t *rgba; size_t stride, width, height, count; int planes; const uint8_t *alpha; const float *tone;
|
||||
const DitherParams *p; size_t spacing; float middle, dots; const float *screen, *phosphor; atomic_int failed;
|
||||
} scan_job;
|
||||
|
||||
static void scan_band(void *context, size_t firstLine, size_t lastLine) {
|
||||
scan_job *job = context;
|
||||
uint8_t *rgba = job->rgba; size_t stride = job->stride, width = job->width, height = job->height, count = job->count;
|
||||
int planes = job->planes; const uint8_t *alpha = job->alpha; const float *tone = job->tone; const DitherParams *p = job->p;
|
||||
size_t spacing = job->spacing; float middle = job->middle, dots = job->dots;
|
||||
const float *screen = job->screen, *phosphor = job->phosphor;
|
||||
float *scan = malloc(width * sizeof(float) * (size_t)planes);
|
||||
if (!scan) { atomic_store(&job->failed, 1); return; }
|
||||
for (size_t line = firstLine; line < lastLine; ++line) {
|
||||
size_t top = line * spacing;
|
||||
size_t bottom = top + spacing < height ? top + spacing : height;
|
||||
// Wobble: each line is pushed sideways, a slow wave down the screen with a quicker one over it, as a
|
||||
// CRT's picture wavers when its sync drifts.
|
||||
float wave = sinf((float)line * 0.45f) * 0.7f + sinf((float)line * 1.7f + 1.3f) * 0.3f;
|
||||
long shift = lroundf(p->wobble * wave);
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
float sum[3] = { 0, 0, 0 }; int n = 0;
|
||||
long sx = (long)x - shift;
|
||||
if (sx >= 0 && sx < (long)width)
|
||||
for (size_t y = top; y < bottom; ++y) {
|
||||
size_t at = y * width + (size_t)sx;
|
||||
if (!alpha[at]) continue;
|
||||
for (int c = 0; c < planes; ++c) sum[c] += tone[(size_t)c * count + at];
|
||||
++n;
|
||||
}
|
||||
for (int c = 0; c < planes; ++c) scan[(size_t)c * width + x] = n ? sum[c] / (float)n : 0;
|
||||
}
|
||||
for (size_t y = top; y < bottom; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
float offset = fabsf((float)(y - top) + 0.5f - middle);
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
if (!alpha[y * width + x]) continue;
|
||||
// Dots: the line breaks into beads, one every line spacing, each lit in the color at its middle.
|
||||
float along = fmodf((float)x + 0.5f, (float)spacing) - middle;
|
||||
long centered = lroundf((float)x - along * dots);
|
||||
size_t at = centered < 0 ? 0 : (size_t)centered >= width ? width - 1 : (size_t)centered;
|
||||
float r, g, b, t;
|
||||
if (p->originalColors) {
|
||||
r = scan[at]; g = scan[width + at]; b = scan[2 * width + at];
|
||||
t = 0.2126f * r + 0.7152f * g + 0.0722f * b;
|
||||
} else {
|
||||
t = scan[at];
|
||||
r = screen[0] + (phosphor[0] - screen[0]) * t;
|
||||
g = screen[1] + (phosphor[1] - screen[1]) * t;
|
||||
b = screen[2] + (phosphor[2] - screen[2]) * t;
|
||||
}
|
||||
// The beam is driven brighter than the picture, making up for the dark screen between lines.
|
||||
r *= 1.35f; g *= 1.35f; b *= 1.35f;
|
||||
// Half the beam's height: a thin line in the shadows, most of the way across in the highlights,
|
||||
// always leaving dark screen between lines.
|
||||
float beam = middle * (0.2f + 0.5f * sqrtf(clamp01(t)));
|
||||
float across = along * dots, distance = sqrtf(offset * offset + across * across);
|
||||
float cover = clamp01(beam - distance + 0.5f);
|
||||
// Between the lines, the screen: black in Original, else the dark color.
|
||||
float br = p->originalColors ? 0 : screen[0], bg = p->originalColors ? 0 : screen[1];
|
||||
float bb = p->originalColors ? 0 : screen[2];
|
||||
write_pixel(row + x * 4, br + (r - br) * cover, bg + (g - bg) * cover, bb + (b - bb) * cover);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(scan);
|
||||
}
|
||||
|
||||
int dither_apply(uint8_t *rgba, size_t width, size_t height, size_t stride, const DitherParams *p) {
|
||||
size_t count = width * height;
|
||||
if (!count) return 1;
|
||||
@@ -286,18 +219,6 @@ int dither_apply(uint8_t *rgba, size_t width, size_t height, size_t stride, cons
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (style == DITHER_SCANLINES) {
|
||||
// A CRT: each line scans the image, its tone along the line the average of the rows it covers. The beam glows
|
||||
// brighter and blooms thicker where the picture is light, and the screen between lines stays dark.
|
||||
size_t spacing = (size_t)(p->cell < 2 ? 2 : p->cell);
|
||||
float middle = (float)spacing / 2, dots = clamp01(p->dots);
|
||||
// Each line is drawn on its own, so the lines are shared out across the cores.
|
||||
size_t lines = (height + spacing - 1) / spacing;
|
||||
const float *screen = dark, *phosphor = light;
|
||||
scan_job scanning = { rgba, stride, width, height, count, planes, alpha, tone, p, spacing, middle, dots, screen, phosphor, 0 };
|
||||
in_bands(lines, scan_band, &scanning);
|
||||
int failed = atomic_load(&scanning.failed);
|
||||
if (failed) { free(tone); free(alpha); free(source); return 0; }
|
||||
} else {
|
||||
// Marks (halftone shapes, patterns, glyphs) cover as much of each spot as the tone calls for. On light, they
|
||||
// stand for darkness and are drawn in the dark color; light on dark, the reverse.
|
||||
@@ -405,7 +326,10 @@ static void glow_band(void *context, size_t first, size_t last) {
|
||||
for (size_t x = 0; x < width * 4; x += 4) {
|
||||
float a = row[x + 3];
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
float v = (float)row[x + c] + (float)light[x + c] * amount * a / 255.0f;
|
||||
// The light eases in as the pixel nears full brightness rather than clipping there, so where the
|
||||
// picture is bright the gaps between lines glow without filling up to the lines.
|
||||
float v = row[x + c], added = (float)light[x + c] * amount * a / 255.0f, room = (a - v) * 0.7f;
|
||||
if (room > 0) v += room * (1 - expf(-added / room));
|
||||
row[x + c] = (uint8_t)lroundf(v > a ? a : v);
|
||||
}
|
||||
}
|
||||
@@ -451,3 +375,269 @@ void dither_quantize16(const uint16_t *wide, uint8_t *rgba, size_t width, size_t
|
||||
quantize_job quantizing = { wide, rgba, width, stride };
|
||||
in_bands(height, quantize_band, &quantizing);
|
||||
}
|
||||
|
||||
typedef struct { float *lift; size_t width; long radius; atomic_int failed; } blur_job;
|
||||
|
||||
static void blur_band(void *context, size_t firstLine, size_t lastLine) {
|
||||
blur_job *job = context;
|
||||
float *lift = job->lift; size_t width = job->width; long radius = job->radius;
|
||||
float *copy = malloc(width * sizeof(float));
|
||||
if (!copy) { atomic_store(&job->failed, 1); return; }
|
||||
for (size_t line = firstLine; line < lastLine; ++line) {
|
||||
float *row = lift + line * width;
|
||||
for (int pass = 0; pass < 3; ++pass) {
|
||||
memcpy(copy, row, width * sizeof(float));
|
||||
float sum = 0;
|
||||
for (long x = -radius; x <= radius; ++x) sum += copy[x < 0 ? 0 : x >= (long)width ? width - 1 : (size_t)x];
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
row[x] = sum / (float)(2 * radius + 1);
|
||||
long out = (long)x - radius, in = (long)x + radius + 1;
|
||||
sum += copy[in >= (long)width ? width - 1 : (size_t)in] - copy[out < 0 ? 0 : (size_t)out];
|
||||
}
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
}
|
||||
|
||||
typedef struct { const float *lift; float *spread; size_t width, lines; } spread_job;
|
||||
|
||||
static void spread_band(void *context, size_t firstLine, size_t lastLine) {
|
||||
const spread_job *job = context;
|
||||
const float *lift = job->lift; float *spread = job->spread; size_t width = job->width, lines = job->lines;
|
||||
for (size_t line = firstLine; line < lastLine; ++line) {
|
||||
const float *above = lift + (line ? line - 1 : 0) * width, *here = lift + line * width;
|
||||
const float *below = lift + (line + 1 < lines ? line + 1 : line) * width;
|
||||
for (size_t x = 0; x < width; ++x) spread[line * width + x] = (above[x] + 2 * here[x] + below[x]) / 4;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t *rgba; size_t width, height, stride, lines, spacing, plane; const float *scan, *tone, *lift; const uint8_t *alpha;
|
||||
int original, rising; float middle, dots, threshold, displace, thickness, blackLevel; const float *screen, *phosphor;
|
||||
atomic_int failed;
|
||||
} column_job;
|
||||
|
||||
static void column_band(void *context, size_t first, size_t last) {
|
||||
column_job *job = context;
|
||||
uint8_t *rgba = job->rgba; size_t width = job->width, height = job->height, stride = job->stride, lines = job->lines;
|
||||
size_t spacing = job->spacing, plane = job->plane; const float *scan = job->scan, *tone = job->tone, *lift = job->lift;
|
||||
const uint8_t *alpha = job->alpha; int original = job->original, rising = job->rising;
|
||||
float middle = job->middle, dots = job->dots, threshold = job->threshold, displace = job->displace;
|
||||
float thickness = job->thickness, blackLevel = job->blackLevel; const float *screen = job->screen, *phosphor = job->phosphor;
|
||||
float *cover = malloc(height * sizeof(float) * 4);
|
||||
if (!cover) { atomic_store(&job->failed, 1); return; }
|
||||
float *color = cover + height;
|
||||
for (size_t x = first; x < last; ++x) {
|
||||
memset(cover, 0, height * sizeof(float));
|
||||
float along = fmodf((float)x + 0.5f, (float)spacing) - middle;
|
||||
// The edge of everything drawn so far in this column; lines further back show only beyond it.
|
||||
float horizon = rising ? INFINITY : -INFINITY;
|
||||
for (size_t step = 0; step < lines; ++step) {
|
||||
size_t line = rising ? lines - 1 - step : step;
|
||||
// Dots: darker than the Dots level, the line breaks into beads, one every line spacing, each lit in the
|
||||
// tone at its middle; just above it, into dashes that close up into the solid line.
|
||||
float beading = dots > 0 ? clamp01((dots - tone[line * width + x]) / 0.2f) : 0, across = along * beading;
|
||||
long centered = lroundf((float)x - across);
|
||||
size_t at = centered < 0 ? 0 : (size_t)centered >= width ? width - 1 : (size_t)centered;
|
||||
size_t i = line * width + at;
|
||||
float t = tone[i];
|
||||
float lit = threshold > 0 ? clamp01((t - threshold) / 0.04f) : 1;
|
||||
if (lit <= 0) continue;
|
||||
// Drawn between this column's height and the last's, so a steep climb never breaks the line.
|
||||
float base = (float)line * (float)spacing + middle;
|
||||
float here = base - displace * lift[i], before = at > 0 ? base - displace * lift[i - 1] : here;
|
||||
float lo = fminf(here, before), hi = fmaxf(here, before);
|
||||
// Black Level: the line's least brightness, so it still shows where the picture is black.
|
||||
float level = blackLevel + (1 - blackLevel) * t, c[3];
|
||||
for (int k = 0; k < 3; ++k)
|
||||
c[k] = original ? blackLevel + (1 - blackLevel) * scan[(size_t)k * plane + i] : screen[k] + (phosphor[k] - screen[k]) * level;
|
||||
// Half the line's height: thinner where the picture is dim.
|
||||
float beam = middle * thickness * (0.29f + 0.71f * sqrtf(clamp01(level)));
|
||||
long top = (long)floorf(rising ? lo - beam - 1 : fmaxf(lo - beam - 1, horizon - 1));
|
||||
long bottom = (long)ceilf(rising ? fminf(hi + beam + 1, horizon + 1) : hi + beam + 1);
|
||||
if (top < 0) top = 0;
|
||||
if (bottom > (long)height) bottom = (long)height;
|
||||
for (long y = top; y < bottom; ++y) {
|
||||
float yy = (float)y + 0.5f;
|
||||
float off = yy < lo ? lo - yy : yy > hi ? yy - hi : 0;
|
||||
float distance = sqrtf(off * off + across * across);
|
||||
float hidden = rising ? clamp01(horizon - yy + 0.5f) : clamp01(yy - horizon + 0.5f);
|
||||
float shown = clamp01(beam - distance + 0.5f) * lit * hidden;
|
||||
if (shown > cover[y]) {
|
||||
cover[y] = shown;
|
||||
color[y * 3] = c[0]; color[y * 3 + 1] = c[1]; color[y * 3 + 2] = c[2];
|
||||
}
|
||||
}
|
||||
if (lit > 0.5f) horizon = rising ? fminf(horizon, lo - beam) : fmaxf(horizon, hi + beam);
|
||||
}
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
if (!alpha[y * width + x]) continue;
|
||||
// The beam is driven brighter than the picture, making up for the dark screen between lines.
|
||||
float shown = cover[y];
|
||||
write_pixel(rgba + y * stride + x * 4, screen[0] + (color[y * 3] * 1.35f - screen[0]) * shown,
|
||||
screen[1] + (color[y * 3 + 1] * 1.35f - screen[1]) * shown,
|
||||
screen[2] + (color[y * 3 + 2] * 1.35f - screen[2]) * shown);
|
||||
}
|
||||
}
|
||||
free(cover);
|
||||
}
|
||||
|
||||
// Draws the lines over the scanned tones, a column at a time. Displaced, they're a landscape seen from the front, after
|
||||
// the Rutt-Etra video synthesizer: each line is lifted (or, displaced the other way, lowered) by the picture's
|
||||
// brightness smoothed into hills, and hides whatever lies behind it, so nearer lines wrap over the shapes rather than
|
||||
// crossing the ones beyond. Nearest line first: the bottom one when lines rise, the top one when they fall.
|
||||
static int draw_lines(uint8_t *rgba, size_t width, size_t height, size_t stride, const ScanlinesParams *p,
|
||||
const float *scan, const uint8_t *alpha, size_t lines, size_t spacing) {
|
||||
int original = p->originalColors;
|
||||
size_t plane = lines * width;
|
||||
float *tone = malloc(plane * sizeof(float)), *lift = malloc(plane * sizeof(float)), *spread = malloc(plane * sizeof(float));
|
||||
if (!tone || !lift || !spread) { free(tone); free(lift); free(spread); return 0; }
|
||||
for (size_t at = 0; at < plane; ++at)
|
||||
tone[at] = original ? 0.2126f * scan[at] + 0.7152f * scan[plane + at] + 0.0722f * scan[2 * plane + at] : scan[at];
|
||||
// The height: brightness blurred along each line (three box passes, close to a Gaussian) and then across its
|
||||
// neighbors, so a face rises as one rounded hill instead of a staircase of the pixels under it.
|
||||
float smoothness = clamp01(p->smoothness);
|
||||
long radius = lroundf(smoothness * (float)spacing * 2);
|
||||
memcpy(lift, tone, plane * sizeof(float));
|
||||
if (radius > 0 && p->displace != 0) {
|
||||
blur_job blurring = { lift, width, radius, 0 };
|
||||
in_bands(lines, blur_band, &blurring);
|
||||
int failed = atomic_load(&blurring.failed);
|
||||
if (failed) { free(tone); free(lift); free(spread); return 0; }
|
||||
long across = lroundf(smoothness * 2);
|
||||
for (long step = 0; step < across; ++step) {
|
||||
spread_job spreading = { lift, spread, width, lines };
|
||||
in_bands(lines, spread_band, &spreading);
|
||||
memcpy(lift, spread, plane * sizeof(float));
|
||||
}
|
||||
}
|
||||
free(spread);
|
||||
|
||||
float middle = (float)spacing / 2, dots = clamp01(p->dots), threshold = clamp01(p->threshold), displace = p->displace;
|
||||
float thickness = clamp01(p->thickness), blackLevel = clamp01(p->blackLevel);
|
||||
int rising = displace >= 0;
|
||||
float dark[3] = { p->dark[0] / 255.0f, p->dark[1] / 255.0f, p->dark[2] / 255.0f };
|
||||
float light[3] = { p->light[0] / 255.0f, p->light[1] / 255.0f, p->light[2] / 255.0f };
|
||||
if (original) dark[0] = dark[1] = dark[2] = 0;
|
||||
const float *screen = dark, *phosphor = light;
|
||||
column_job drawing = { rgba, width, height, stride, lines, spacing, plane, scan, tone, lift, alpha, original, rising, middle,
|
||||
dots, threshold, displace, thickness, blackLevel, screen, phosphor, 0 };
|
||||
in_bands(width, column_band, &drawing);
|
||||
int failed = atomic_load(&drawing.failed);
|
||||
free(tone); free(lift);
|
||||
return !failed;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *rgba; size_t stride, width, count; uint8_t *alpha; float *tone; float gamma, contrast; int original;
|
||||
} scanlines_tone_job;
|
||||
|
||||
static void scanlines_tone_band(void *context, size_t first, size_t last) {
|
||||
const scanlines_tone_job *job = context;
|
||||
const uint8_t *rgba = job->rgba; size_t stride = job->stride, width = job->width, count = job->count;
|
||||
uint8_t *alpha = job->alpha; float *tone = job->tone; float gamma = job->gamma, contrast = job->contrast;
|
||||
int original = job->original;
|
||||
for (size_t y = first; y < last; ++y) {
|
||||
const uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
const uint8_t *px = row + x * 4;
|
||||
size_t at = y * width + x;
|
||||
alpha[at] = px[3];
|
||||
float r = 0, g = 0, b = 0;
|
||||
if (px[3]) { float scale = 1.0f / (float)px[3]; r = px[0] * scale; g = px[1] * scale; b = px[2] * scale; }
|
||||
if (original) {
|
||||
tone[at] = adjust_tone(r, gamma, contrast);
|
||||
tone[count + at] = adjust_tone(g, gamma, contrast);
|
||||
tone[2 * count + at] = adjust_tone(b, gamma, contrast);
|
||||
} else {
|
||||
tone[at] = adjust_tone(0.2126f * r + 0.7152f * g + 0.0722f * b, gamma, contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
size_t width, height, count, spacing, lines; int planes; const uint8_t *alpha; const float *tone; float *scan; float wobble;
|
||||
} line_scan_job;
|
||||
|
||||
static void line_scan_band(void *context, size_t firstLine, size_t lastLine) {
|
||||
const line_scan_job *job = context;
|
||||
size_t width = job->width, height = job->height, count = job->count, spacing = job->spacing, lines = job->lines;
|
||||
int planes = job->planes; const uint8_t *alpha = job->alpha; const float *tone = job->tone; float *scan = job->scan;
|
||||
float wobble = job->wobble;
|
||||
for (size_t line = firstLine; line < lastLine; ++line) {
|
||||
size_t top = line * spacing, bottom = top + spacing < height ? top + spacing : height;
|
||||
// A slow wave down the screen with a quicker one over it, as a CRT's picture wavers when its sync drifts.
|
||||
float wave = sinf((float)line * 0.45f) * 0.7f + sinf((float)line * 1.7f + 1.3f) * 0.3f;
|
||||
long shift = lroundf(wobble * wave);
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
float sum[3] = { 0, 0, 0 }; int n = 0;
|
||||
long sx = (long)x - shift;
|
||||
if (sx >= 0 && sx < (long)width)
|
||||
for (size_t y = top; y < bottom; ++y) {
|
||||
size_t at = y * width + (size_t)sx;
|
||||
if (!alpha[at]) continue;
|
||||
for (int c = 0; c < planes; ++c) sum[c] += tone[(size_t)c * count + at];
|
||||
++n;
|
||||
}
|
||||
for (int c = 0; c < planes; ++c) scan[((size_t)c * lines + line) * width + x] = n ? sum[c] / (float)n : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct { uint8_t *rgba; size_t stride, width; long split; atomic_int failed; } split_job;
|
||||
|
||||
static void split_band(void *context, size_t first, size_t last) {
|
||||
split_job *job = context;
|
||||
uint8_t *rgba = job->rgba; size_t stride = job->stride, width = job->width; long split = job->split;
|
||||
uint8_t *copy = malloc(width * 4);
|
||||
if (!copy) { atomic_store(&job->failed, 1); return; }
|
||||
for (size_t y = first; y < last; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
memcpy(copy, row, width * 4);
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
long from = (long)x - split, to = (long)x + split;
|
||||
uint8_t a = row[x * 4 + 3];
|
||||
uint8_t r = from >= 0 ? copy[from * 4] : 0, b = to < (long)width ? copy[to * 4 + 2] : 0;
|
||||
row[x * 4] = r > a ? a : r;
|
||||
row[x * 4 + 2] = b > a ? a : b;
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
}
|
||||
|
||||
int scanlines_apply(uint8_t *rgba, size_t width, size_t height, size_t stride, const ScanlinesParams *p) {
|
||||
size_t count = width * height;
|
||||
if (!count) return 1;
|
||||
size_t spacing = (size_t)(p->spacing < 2 ? 2 : p->spacing);
|
||||
size_t lines = (height + spacing - 1) / spacing;
|
||||
int planes = p->originalColors ? 3 : 1;
|
||||
float *tone = malloc(count * sizeof(float) * (size_t)planes);
|
||||
uint8_t *alpha = malloc(count);
|
||||
// Each line's tone along it: the average of the rows it covers, sampled where its wobble moves it from.
|
||||
float *scan = malloc(lines * width * sizeof(float) * (size_t)planes);
|
||||
if (!tone || !alpha || !scan) { free(tone); free(alpha); free(scan); return 0; }
|
||||
|
||||
float gamma = exp2f(p->density * 1.5f);
|
||||
float contrast = p->contrast >= 0 ? 1.0f / (1.0f - 0.95f * p->contrast) : 1.0f + p->contrast;
|
||||
int original = p->originalColors;
|
||||
scanlines_tone_job toning = { rgba, stride, width, count, alpha, tone, gamma, contrast, original };
|
||||
in_bands(height, scanlines_tone_band, &toning);
|
||||
float wobble = p->wobble;
|
||||
line_scan_job scanning = { width, height, count, spacing, lines, planes, alpha, tone, scan, wobble };
|
||||
in_bands(lines, line_scan_band, &scanning);
|
||||
|
||||
int drawn = draw_lines(rgba, width, height, stride, p, scan, alpha, lines, spacing);
|
||||
free(tone); free(alpha); free(scan);
|
||||
if (!drawn) return 0;
|
||||
|
||||
// Color split: red moved one way and blue the other, for colored fringes on the lines' edges.
|
||||
long split = lroundf(p->split);
|
||||
if (split > 0) {
|
||||
split_job splitting = { rgba, stride, width, split, 0 };
|
||||
in_bands(height, split_band, &splitting);
|
||||
int failed = atomic_load(&splitting.failed);
|
||||
if (failed) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
+37
-7
@@ -8,7 +8,7 @@ enum {
|
||||
DITHER_ATKINSON, DITHER_FLOYD_STEINBERG,
|
||||
DITHER_BAYER_2, DITHER_BAYER_4, DITHER_BAYER_8,
|
||||
DITHER_DOTS, DITHER_LINES, DITHER_DIAMONDS,
|
||||
DITHER_PATTERNS, DITHER_GLYPHS, DITHER_SCANLINES
|
||||
DITHER_PATTERNS, DITHER_GLYPHS
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -20,8 +20,7 @@ typedef struct {
|
||||
// −1…1: darker (more ink) or lighter, and flatter or punchier, before dithering.
|
||||
float density;
|
||||
float contrast;
|
||||
// Halftone and glyph cells, in pixels (scanlines: the distance between lines), and the halftone screen's angle in
|
||||
// radians.
|
||||
// Halftone and glyph cells, in pixels, and the halftone screen's angle in radians.
|
||||
int cell;
|
||||
float angle;
|
||||
// Halftone dots, patterns and glyphs mark the light tones on the dark color instead of the dark on the light.
|
||||
@@ -38,18 +37,49 @@ typedef struct {
|
||||
const uint8_t *glyphs;
|
||||
const float *glyphCoverage;
|
||||
int glyphCount;
|
||||
// Scanlines: how far each line breaks into round dots (0–1), and how many pixels its wobble pushes it sideways.
|
||||
float dots;
|
||||
float wobble;
|
||||
} DitherParams;
|
||||
|
||||
// Filter › Scanlines: the picture as a CRT draws it, in lines of light on a dark screen.
|
||||
typedef struct {
|
||||
// Pixels from one line to the next.
|
||||
int spacing;
|
||||
// How much of the gap a full-bright line fills, 0–1; dimmer parts draw it thinner.
|
||||
float thickness;
|
||||
// 0–1: tones darker than this break the lines into round dots, blending through dashes into the solid line above it.
|
||||
float dots;
|
||||
// Pixels a line wavers sideways, in a wave down the screen.
|
||||
float wobble;
|
||||
// Pixels a line rises where the picture under it is bright (falls, if negative), so lines swell into its shapes.
|
||||
float displace;
|
||||
// 0–1: tones darker than this draw no line at all.
|
||||
float threshold;
|
||||
// Pixels the red and blue are moved apart, either way, for colored fringes.
|
||||
float split;
|
||||
// −1…1: darker or lighter, and flatter or punchier, before the lines are drawn.
|
||||
float density;
|
||||
float contrast;
|
||||
// 0–1: how bright the lines are where the picture is black.
|
||||
float blackLevel;
|
||||
// 0–1: how far the brightness is smoothed, over a few line spacings, before it displaces the lines.
|
||||
float smoothness;
|
||||
// 0: lines of `light` on `dark` (straight sRGB), brighter where the picture is. 1: lines in the picture's colors.
|
||||
int originalColors;
|
||||
uint8_t dark[3];
|
||||
uint8_t light[3];
|
||||
} ScanlinesParams;
|
||||
|
||||
// Draws premultiplied RGBA pixels (4 bytes per pixel, `stride` bytes per row) as scanlines, in place. Alpha is kept and
|
||||
// fully transparent pixels are left alone. Returns 0 if working memory couldn't be had.
|
||||
int scanlines_apply(uint8_t *rgba, size_t width, size_t height, size_t stride, const ScanlinesParams *params);
|
||||
|
||||
// Dithers premultiplied RGBA pixels (4 bytes per pixel, `stride` bytes per row) in place. Alpha is kept and fully
|
||||
// transparent pixels are left alone. Returns 0 if working memory couldn't be had.
|
||||
int dither_apply(uint8_t *rgba, size_t width, size_t height, size_t stride, const DitherParams *params);
|
||||
// Turns each `block` × `block` square of premultiplied RGBA pixels into a round dot in its own color on `gap` (straight
|
||||
// sRGB), like the lit pixels of a dot-matrix screen. The dot's edge is smoothed and alpha is kept.
|
||||
void dither_dots(uint8_t *rgba, size_t width, size_t height, size_t stride, int block, const uint8_t *gap);
|
||||
// Adds `glow` (premultiplied RGBA, same layout) over the pixels at `amount`, never past their own alpha.
|
||||
// Adds `glow` (premultiplied RGBA, same layout) over the pixels at `amount`, easing off as they near full brightness
|
||||
// and never past their own alpha.
|
||||
void dither_glow(uint8_t *rgba, const uint8_t *glow, size_t width, size_t height, size_t stride, float amount);
|
||||
// Rounds 16-bit premultiplied RGBA (`width` × `height`, tightly packed) to 8 bits into `rgba` (`stride` bytes per
|
||||
// row), adding noise of about one 8-bit step first so smooth gradients don't come out in steps. The noise is fixed per
|
||||
|
||||
Reference in new issue
Block a user