Quit settles a pending gradient, a pixel move and open dialogs; Dither › Scanlines (CRT) on all cores (13.17)
This commit is contained in:
1 parent
765e071c0c
commit
5bfbc6cd5d
12 files changed
+913
-32
No files matched your search
+151
-12
@@ -1,9 +1,30 @@
|
||||
#include "DitherPixels.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
// Linux: GCD's dispatch_apply, run on QtConcurrent by Document/Dither.cpp. Blocks become functions with a context.
|
||||
void dither_bands(size_t bands, void (*band)(void *context, size_t index), void *context);
|
||||
|
||||
static inline float clamp01(float v) { return v < 0 ? 0 : v > 1 ? 1 : v; }
|
||||
|
||||
typedef void (*band_body)(void *context, size_t start, size_t end);
|
||||
typedef struct { size_t count, size; band_body body; void *context; } band_job;
|
||||
|
||||
static void run_band(void *context, size_t band) {
|
||||
const band_job *job = context;
|
||||
size_t count = job->count, size = job->size;
|
||||
size_t start = band * size, end = start + size < count ? start + size : count;
|
||||
if (start < end) job->body(job->context, start, end);
|
||||
}
|
||||
|
||||
// Runs `body` over `count` items split into a few runs per core, each a (start, end) range, all at once.
|
||||
static void in_bands(size_t count, band_body body, void *context) {
|
||||
size_t bands = count < 64 ? 1 : 32, size = (count + bands - 1) / bands;
|
||||
band_job job = { count, size, body, context };
|
||||
dither_bands(bands, run_band, &job);
|
||||
}
|
||||
|
||||
// Density darkens (positive) or lightens as a gamma, so black and white stay put; contrast pivots on mid gray.
|
||||
static inline float adjust_tone(float v, float gamma, float contrast) {
|
||||
v = powf(clamp01(v), gamma);
|
||||
@@ -114,19 +135,17 @@ static inline void write_pixel(uint8_t *px, float r, float g, float b) {
|
||||
px[2] = (uint8_t)lroundf(clamp01(b) * a * 255.0f);
|
||||
}
|
||||
|
||||
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;
|
||||
int planes = p->originalColors ? 3 : 1;
|
||||
float *tone = malloc(count * sizeof(float) * (size_t)planes);
|
||||
uint8_t *alpha = malloc(count);
|
||||
// The image's own colors, unadjusted: halftone dots and glyphs take them in Original mode.
|
||||
float *source = p->originalColors ? malloc(count * sizeof(float) * 3) : NULL;
|
||||
if (!tone || !alpha || (p->originalColors && !source)) { free(tone); free(alpha); free(source); return 0; }
|
||||
typedef struct {
|
||||
uint8_t *rgba; size_t stride, width, count; uint8_t *alpha; float *tone, *source; const DitherParams *p;
|
||||
float gamma, contrast;
|
||||
} tone_job;
|
||||
|
||||
float gamma = exp2f(p->density * 1.5f);
|
||||
float contrast = p->contrast >= 0 ? 1.0f / (1.0f - 0.95f * p->contrast) : 1.0f + p->contrast;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
static void tone_band(void *context, size_t first, size_t last) {
|
||||
const tone_job *job = context;
|
||||
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, *source = job->source; const DitherParams *p = job->p;
|
||||
float gamma = job->gamma, contrast = job->contrast;
|
||||
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;
|
||||
@@ -147,6 +166,90 @@ int dither_apply(uint8_t *rgba, size_t width, size_t height, size_t stride, cons
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
int planes = p->originalColors ? 3 : 1;
|
||||
float *tone = malloc(count * sizeof(float) * (size_t)planes);
|
||||
uint8_t *alpha = malloc(count);
|
||||
// The image's own colors, unadjusted: halftone dots and glyphs take them in Original mode.
|
||||
float *source = p->originalColors ? malloc(count * sizeof(float) * 3) : NULL;
|
||||
if (!tone || !alpha || (p->originalColors && !source)) { free(tone); free(alpha); free(source); 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;
|
||||
tone_job toning = { rgba, stride, width, count, alpha, tone, source, p, gamma, contrast };
|
||||
in_bands(height, tone_band, &toning);
|
||||
|
||||
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 };
|
||||
@@ -183,6 +286,18 @@ 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.
|
||||
@@ -277,3 +392,27 @@ void dither_dots(uint8_t *rgba, size_t width, size_t height, size_t stride, int
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct { uint8_t *rgba; const uint8_t *glow; size_t width, stride; float amount; } glow_job;
|
||||
|
||||
static void glow_band(void *context, size_t first, size_t last) {
|
||||
const glow_job *job = context;
|
||||
uint8_t *rgba = job->rgba; const uint8_t *glow = job->glow; size_t width = job->width, stride = job->stride;
|
||||
float amount = job->amount;
|
||||
for (size_t y = first; y < last; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
const uint8_t *light = glow + y * stride;
|
||||
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;
|
||||
row[x + c] = (uint8_t)lroundf(v > a ? a : v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dither_glow(uint8_t *rgba, const uint8_t *glow, size_t width, size_t height, size_t stride, float amount) {
|
||||
glow_job glowing = { rgba, glow, width, stride, amount };
|
||||
in_bands(height, glow_band, &glowing);
|
||||
}
|
||||
Reference in new issue
Block a user