Camera Raw filter's model and pixel kernels (12.8a)
This commit is contained in:
1 parent
4291f4249a
commit
b544e12b1d
33 files changed
+4438
-27
No files matched your search
+888
-14
@@ -1,5 +1,8 @@
|
||||
#include "AdjustPixels.h"
|
||||
#include "LensPixels.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void adjust_gradient_map(uint8_t *rgba, size_t width, size_t height, size_t stride, const uint8_t *table) {
|
||||
for (size_t y = 0; y < height; y++) {
|
||||
@@ -41,6 +44,21 @@ static inline float lattice(int64_t ix, int64_t iy, uint32_t seed) {
|
||||
return (float)(h & 0xFFFFU) / 65535.0f + (float)(h >> 16) / 65535.0f - 1.0f;
|
||||
}
|
||||
|
||||
// Smooth seeded noise whose features follow `scale` document pixels. Keeping both the broad and
|
||||
// detailed patterns relative to the requested grain size makes Size remain visible at any Roughness.
|
||||
static inline float grain_field(double u, double v, double scale, uint32_t seed) {
|
||||
double cellX = floor(u / scale), cellY = floor(v / scale);
|
||||
float tx = (float)(u / scale - cellX), ty = (float)(v / scale - cellY);
|
||||
tx = tx * tx * (3.0f - 2.0f * tx);
|
||||
ty = ty * ty * (3.0f - 2.0f * ty);
|
||||
int64_t ix = (int64_t)cellX, iy = (int64_t)cellY;
|
||||
float n00 = lattice(ix, iy, seed), n10 = lattice(ix + 1, iy, seed);
|
||||
float n01 = lattice(ix, iy + 1, seed), n11 = lattice(ix + 1, iy + 1, seed);
|
||||
float top = n00 + (n10 - n00) * tx, bottom = n01 + (n11 - n01) * tx;
|
||||
// Blending neighboring lattice values narrows the spread; restore approximately its original range.
|
||||
return (top + (bottom - top) * ty) * 1.6f;
|
||||
}
|
||||
|
||||
static inline float clamp255(float value) { return value < 0 ? 0 : value > 255 ? 255 : value; }
|
||||
|
||||
void adjust_grain(uint8_t *rgba, size_t width, size_t height, size_t stride, double amount, double size,
|
||||
@@ -50,27 +68,18 @@ void adjust_grain(uint8_t *rgba, size_t width, size_t height, size_t stride, dou
|
||||
float strength = (float)(amount > 100 ? 1.0 : amount / 100.0) * 0.35f * 255.0f;
|
||||
float rough = (float)(roughness < 0 ? 0.0 : roughness > 100 ? 1.0 : roughness / 100.0);
|
||||
uint32_t fineSeed = mix32(seed ^ 0xA511E9B3U);
|
||||
// Roughness adds smaller, less regular particles, as in Photoshop, but their size remains
|
||||
// proportional to the Size control instead of collapsing to fixed one-pixel noise.
|
||||
double detailSize = fmax(0.5, size * 0.35);
|
||||
for (size_t y = 0; y < height; y++) {
|
||||
double v = originY + ((double)y + 0.5) * unitsPerPixel;
|
||||
double cellY = floor(v / size);
|
||||
float ty = (float)(v / size - cellY);
|
||||
ty = ty * ty * (3.0f - 2.0f * ty);
|
||||
int64_t iy = (int64_t)cellY, fineY = (int64_t)floor(v);
|
||||
uint8_t *p = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; x++, p += 4) {
|
||||
unsigned a = p[3];
|
||||
if (a == 0) continue;
|
||||
double u = originX + ((double)x + 0.5) * unitsPerPixel;
|
||||
double cellX = floor(u / size);
|
||||
float tx = (float)(u / size - cellX);
|
||||
tx = tx * tx * (3.0f - 2.0f * tx);
|
||||
int64_t ix = (int64_t)cellX;
|
||||
float n00 = lattice(ix, iy, seed), n10 = lattice(ix + 1, iy, seed);
|
||||
float n01 = lattice(ix, iy + 1, seed), n11 = lattice(ix + 1, iy + 1, seed);
|
||||
float top = n00 + (n10 - n00) * tx, bottom = n01 + (n11 - n01) * tx;
|
||||
// Blending neighbors narrows the spread; scaling restores about the lattice's own.
|
||||
float smooth = (top + (bottom - top) * ty) * 1.6f;
|
||||
float fine = lattice((int64_t)floor(u), fineY, fineSeed);
|
||||
float smooth = grain_field(u, v, size, seed);
|
||||
float fine = grain_field(u, v, detailSize, fineSeed);
|
||||
float noise = smooth + (fine - smooth) * rough;
|
||||
float unpremultiply = a == 255 ? 1.0f : 255.0f / (float)a;
|
||||
float r = p[0] * unpremultiply, g = p[1] * unpremultiply, b = p[2] * unpremultiply;
|
||||
@@ -185,3 +194,868 @@ void adjust_color_balance(uint8_t *rgba, size_t width, size_t height, size_t str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static double camera_clamp(double value) {
|
||||
if (value < 0) return 0;
|
||||
if (value > 1) return 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
static double srgb_to_linear(double encoded) {
|
||||
if (encoded <= 0.04045) return encoded / 12.92;
|
||||
return pow((encoded + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
static double linear_to_srgb(double linear) {
|
||||
if (linear <= 0) return 0;
|
||||
if (linear >= 1) return 1;
|
||||
if (linear <= 0.0031308) return linear * 12.92;
|
||||
return 1.055 * pow(linear, 1.0 / 2.4) - 0.055;
|
||||
}
|
||||
|
||||
static double rec709(double r, double g, double b) {
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
}
|
||||
|
||||
// Moves r, g, b so their Rec. 709 luminance becomes `target`, keeping the hue. Pure black cannot
|
||||
// be scaled, so a lift paints neutral light of that luminance.
|
||||
static void scale_luminance(double *r, double *g, double *b, double target) {
|
||||
target = camera_clamp(target);
|
||||
double y = rec709(*r, *g, *b);
|
||||
if (fabs(target - y) < 1e-8) return;
|
||||
if (y < 1e-8) {
|
||||
if (target > y) *r = *g = *b = target;
|
||||
return;
|
||||
}
|
||||
double scale = target / y;
|
||||
*r = camera_clamp(*r * scale);
|
||||
*g = camera_clamp(*g * scale);
|
||||
*b = camera_clamp(*b * scale);
|
||||
}
|
||||
|
||||
static double tone_highlights(double y, double amount) {
|
||||
double t = camera_clamp((y - 0.5) / 0.5);
|
||||
double weight = t * t;
|
||||
if (amount >= 0) return camera_clamp(y + amount * weight * (1.0 - y));
|
||||
return camera_clamp(y + amount * weight * (y - 0.5));
|
||||
}
|
||||
|
||||
static double tone_shadows(double y, double amount) {
|
||||
double t = camera_clamp((0.5 - y) / 0.5);
|
||||
double weight = t * t;
|
||||
if (amount >= 0) return camera_clamp(y + amount * weight * (0.5 - y));
|
||||
return camera_clamp(y + amount * weight * y);
|
||||
}
|
||||
|
||||
// The top quarter is the white point: +1 maps 0.875 to 1, −1 pulls everything above 0.75 down to 0.75.
|
||||
static double tone_whites(double y, double amount) {
|
||||
if (y <= 0.75) return y;
|
||||
return camera_clamp(0.75 + (y - 0.75) * (1.0 + amount));
|
||||
}
|
||||
|
||||
// The bottom quarter is the black point. Negative amounts crush toward 0; positive ones lift toward 0.25.
|
||||
static double tone_blacks(double y, double amount) {
|
||||
if (y >= 0.25) return y;
|
||||
return camera_clamp(0.25 + (y - 0.25) * (1.0 - amount));
|
||||
}
|
||||
|
||||
static void vibrance_and_saturation(double *r, double *g, double *b, double vibrance, double saturation) {
|
||||
double lum = rec709(*r, *g, *b);
|
||||
double maxc = fmax(*r, fmax(*g, *b));
|
||||
double minc = fmin(*r, fmin(*g, *b));
|
||||
double chroma = maxc - minc;
|
||||
double sat = maxc <= 1e-8 ? 0 : chroma / maxc;
|
||||
double hue = 0;
|
||||
if (chroma > 1e-8) {
|
||||
if (*r >= *g && *r >= *b) hue = 60.0 * fmod((*g - *b) / chroma, 6.0);
|
||||
else if (*g >= *r && *g >= *b) hue = 60.0 * ((*b - *r) / chroma + 2.0);
|
||||
else hue = 60.0 * ((*r - *g) / chroma + 4.0);
|
||||
if (hue < 0) hue += 360.0;
|
||||
}
|
||||
double skin = 0;
|
||||
if (hue >= 10.0 && hue <= 50.0) {
|
||||
skin = hue <= 30.0 ? (hue - 10.0) / 20.0 : (50.0 - hue) / 20.0;
|
||||
skin *= camera_clamp((sat - 0.15) / 0.35);
|
||||
}
|
||||
double amount = vibrance * (1.0 - sat);
|
||||
if (vibrance > 0) amount *= 1.0 - 0.7 * skin;
|
||||
double factor = 1.0 + amount;
|
||||
*r = camera_clamp(lum + (*r - lum) * factor);
|
||||
*g = camera_clamp(lum + (*g - lum) * factor);
|
||||
*b = camera_clamp(lum + (*b - lum) * factor);
|
||||
lum = rec709(*r, *g, *b);
|
||||
factor = 1.0 + saturation;
|
||||
*r = camera_clamp(lum + (*r - lum) * factor);
|
||||
*g = camera_clamp(lum + (*g - lum) * factor);
|
||||
*b = camera_clamp(lum + (*b - lum) * factor);
|
||||
}
|
||||
|
||||
static void write_premultiplied(uint8_t *p, double r, double g, double b, double alpha) {
|
||||
p[0] = (uint8_t)fmin(alpha, fmax(0.0, round(r * alpha)));
|
||||
p[1] = (uint8_t)fmin(alpha, fmax(0.0, round(g * alpha)));
|
||||
p[2] = (uint8_t)fmin(alpha, fmax(0.0, round(b * alpha)));
|
||||
}
|
||||
|
||||
void adjust_camera_raw(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double redGain, double greenGain, double blueGain, double exposure, double contrast,
|
||||
double highlights, double shadows, double whites, double blacks,
|
||||
double vibrance, double saturation, int clipping) {
|
||||
double light = exp2(exposure);
|
||||
double contrastScale = 1.0 + contrast / 100.0;
|
||||
double highlightAmount = highlights / 100.0;
|
||||
double shadowAmount = shadows / 100.0;
|
||||
double whiteAmount = whites / 100.0;
|
||||
double blackAmount = blacks / 100.0;
|
||||
double vibranceAmount = vibrance / 100.0;
|
||||
double saturationAmount = saturation / 100.0;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
double r = fmin(255.0, p[0] * 255.0 / alpha) / 255.0;
|
||||
double g = fmin(255.0, p[1] * 255.0 / alpha) / 255.0;
|
||||
double b = fmin(255.0, p[2] * 255.0 / alpha) / 255.0;
|
||||
r = camera_clamp(srgb_to_linear(r) * redGain * light);
|
||||
g = camera_clamp(srgb_to_linear(g) * greenGain * light);
|
||||
b = camera_clamp(srgb_to_linear(b) * blueGain * light);
|
||||
r = camera_clamp(0.5 + (linear_to_srgb(r) - 0.5) * contrastScale);
|
||||
g = camera_clamp(0.5 + (linear_to_srgb(g) - 0.5) * contrastScale);
|
||||
b = camera_clamp(0.5 + (linear_to_srgb(b) - 0.5) * contrastScale);
|
||||
scale_luminance(&r, &g, &b, tone_highlights(rec709(r, g, b), highlightAmount));
|
||||
scale_luminance(&r, &g, &b, tone_shadows(rec709(r, g, b), shadowAmount));
|
||||
scale_luminance(&r, &g, &b, tone_whites(rec709(r, g, b), whiteAmount));
|
||||
scale_luminance(&r, &g, &b, tone_blacks(rec709(r, g, b), blackAmount));
|
||||
vibrance_and_saturation(&r, &g, &b, vibranceAmount, saturationAmount);
|
||||
if (clipping == 1) {
|
||||
int rc = r >= 254.5 / 255.0, gc = g >= 254.5 / 255.0, bc = b >= 254.5 / 255.0;
|
||||
r = rc ? 1 : 0;
|
||||
g = gc ? 1 : 0;
|
||||
b = bc ? 1 : 0;
|
||||
} else if (clipping == 2) {
|
||||
int rc = r <= 0.5 / 255.0, gc = g <= 0.5 / 255.0, bc = b <= 0.5 / 255.0;
|
||||
if (rc || gc || bc) {
|
||||
r = rc ? 0 : 1;
|
||||
g = gc ? 0 : 1;
|
||||
b = bc ? 0 : 1;
|
||||
} else {
|
||||
r = g = b = 1;
|
||||
}
|
||||
}
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static size_t clamped_index(int index, size_t limit) {
|
||||
if (index < 0) return 0;
|
||||
if ((size_t)index >= limit) return limit - 1;
|
||||
return (size_t)index;
|
||||
}
|
||||
|
||||
// Edge-clamped box blur. `dst` may not alias `src`. Returns 0 when the temporary row buffer cannot be allocated.
|
||||
static int box_blur_plane(const float *src, float *dst, size_t width, size_t height, int radius) {
|
||||
if (radius < 1) {
|
||||
memcpy(dst, src, width * height * sizeof(float));
|
||||
return 1;
|
||||
}
|
||||
float *temp = malloc(width * height * sizeof(float));
|
||||
if (!temp) return 0;
|
||||
int window = radius * 2 + 1;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
double sum = 0;
|
||||
for (int k = -radius; k <= radius; ++k) sum += src[y * width + clamped_index(k, width)];
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
temp[y * width + x] = (float)(sum / window);
|
||||
sum += src[y * width + clamped_index((int)x + radius + 1, width)];
|
||||
sum -= src[y * width + clamped_index((int)x - radius, width)];
|
||||
}
|
||||
}
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
double sum = 0;
|
||||
for (int k = -radius; k <= radius; ++k) sum += temp[clamped_index(k, height) * width + x];
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
dst[y * width + x] = (float)(sum / window);
|
||||
sum += temp[clamped_index((int)y + radius + 1, height) * width + x];
|
||||
sum -= temp[clamped_index((int)y - radius, height) * width + x];
|
||||
}
|
||||
}
|
||||
free(temp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int effects_radius(double base, double scale) {
|
||||
double radius = base * (scale > 0 ? scale : 1);
|
||||
if (radius < 1) radius = 1;
|
||||
if (radius > 64) radius = 64;
|
||||
return (int)lround(radius);
|
||||
}
|
||||
|
||||
static void effects_dehaze(double *r, double *g, double *b, double amount) {
|
||||
double d = amount / 100.0;
|
||||
double y = rec709(*r, *g, *b);
|
||||
double contrast = 1.0 + 0.8 * d;
|
||||
double pivot = 0.45 - 0.1 * (d > 0 ? d : 0);
|
||||
double y2 = camera_clamp(pivot + (y - 0.45) * contrast);
|
||||
if (d < 0) y2 = camera_clamp(y2 + (-d) * (1.0 - y2) * 0.45);
|
||||
else y2 = camera_clamp(y2 - d * fmax(0.0, 0.4 - y2));
|
||||
scale_luminance(r, g, b, y2);
|
||||
y2 = rec709(*r, *g, *b);
|
||||
double sat = 1.0 + 0.7 * d;
|
||||
*r = camera_clamp(y2 + (*r - y2) * sat);
|
||||
*g = camera_clamp(y2 + (*g - y2) * sat);
|
||||
*b = camera_clamp(y2 + (*b - y2) * sat);
|
||||
}
|
||||
|
||||
static void effects_vignette(double *r, double *g, double *b, size_t x, size_t y, size_t width, size_t height,
|
||||
double amount, double midpoint, double roundness, double feather, double highlights,
|
||||
int style) {
|
||||
if (amount == 0 || width == 0 || height == 0) return;
|
||||
double nx = ((double)x + 0.5) / (double)width * 2.0 - 1.0;
|
||||
double ny = ((double)y + 0.5) / (double)height * 2.0 - 1.0;
|
||||
double square = fmax(fabs(nx), fabs(ny));
|
||||
double circle = hypot(nx, ny) / sqrt(2.0);
|
||||
double shape = (1.0 - roundness / 100.0) * 0.5;
|
||||
double dist = circle + (square - circle) * shape;
|
||||
double start = (midpoint / 100.0) * 0.85;
|
||||
double soft = feather / 100.0;
|
||||
if (soft < 0.05) soft = 0.05;
|
||||
double t = (dist - start) / soft;
|
||||
t = camera_clamp(t);
|
||||
double mask = t * t * (3.0 - 2.0 * t);
|
||||
double effect = (amount / 100.0) * mask;
|
||||
// Highlight Priority eases a darkening vignette off bright pixels. The other styles do not.
|
||||
if (effect < 0 && style == 0) {
|
||||
double bright = camera_clamp((rec709(*r, *g, *b) - 0.45) / 0.55);
|
||||
effect *= 1.0 - (highlights / 100.0) * bright;
|
||||
}
|
||||
if (effect < 0) {
|
||||
double factor = 1.0 + effect;
|
||||
*r *= factor; *g *= factor; *b *= factor;
|
||||
} else if (effect > 0) {
|
||||
*r = *r + (1.0 - *r) * effect;
|
||||
*g = *g + (1.0 - *g) * effect;
|
||||
*b = *b + (1.0 - *b) * effect;
|
||||
}
|
||||
if (style == 1 && mask > 0) {
|
||||
double lum = rec709(*r, *g, *b);
|
||||
double sat = 1.0 - 0.75 * mask * fabs(amount / 100.0);
|
||||
*r = camera_clamp(lum + (*r - lum) * sat);
|
||||
*g = camera_clamp(lum + (*g - lum) * sat);
|
||||
*b = camera_clamp(lum + (*b - lum) * sat);
|
||||
}
|
||||
}
|
||||
|
||||
void adjust_camera_raw_clip_overlay(uint8_t *rgba, size_t width, size_t height, size_t stride, int shadows, int highlights) {
|
||||
if (!shadows && !highlights) return;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
double r = fmin(1.0, p[0] / alpha);
|
||||
double g = fmin(1.0, p[1] / alpha);
|
||||
double b = fmin(1.0, p[2] / alpha);
|
||||
if (shadows && (r <= 0.5 / 255.0 || g <= 0.5 / 255.0 || b <= 0.5 / 255.0)) {
|
||||
r *= 0.35; g *= 0.35; b = b * 0.35 + 0.65;
|
||||
}
|
||||
if (highlights && (r >= 254.5 / 255.0 || g >= 254.5 / 255.0 || b >= 254.5 / 255.0)) {
|
||||
r = r * 0.35 + 0.65; g *= 0.35; b *= 0.35;
|
||||
}
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void adjust_camera_raw_effects(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double texture, double clarity, double dehaze,
|
||||
double glow, int glowStyle, double glowRange, double glowSpread, double glowWarmth,
|
||||
double vignetteAmount, double vignetteMidpoint, double vignetteRoundness,
|
||||
double vignetteFeather, double vignetteHighlights, int vignetteStyle,
|
||||
double scale) {
|
||||
if (width == 0 || height == 0) return;
|
||||
if (texture == 0 && clarity == 0 && dehaze == 0 && !(glow > 0) && vignetteAmount == 0) return;
|
||||
size_t count = width * height;
|
||||
float *luma = NULL, *fine = NULL, *coarse = NULL, *glowPlane = NULL;
|
||||
int glowRadius = 1;
|
||||
int failed = 0;
|
||||
if (texture != 0 || clarity != 0 || glow > 0) {
|
||||
luma = malloc(count * sizeof(float));
|
||||
if (!luma) return;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) { luma[y * width + x] = 0; continue; }
|
||||
double r = fmin(1.0, p[0] / alpha);
|
||||
double g = fmin(1.0, p[1] / alpha);
|
||||
double b = fmin(1.0, p[2] / alpha);
|
||||
luma[y * width + x] = (float)rec709(r, g, b);
|
||||
}
|
||||
}
|
||||
if (texture != 0) {
|
||||
fine = malloc(count * sizeof(float));
|
||||
if (!fine || !box_blur_plane(luma, fine, width, height, effects_radius(1, scale))) failed = 1;
|
||||
}
|
||||
if (!failed && clarity != 0) {
|
||||
coarse = malloc(count * sizeof(float));
|
||||
if (!coarse || !box_blur_plane(luma, coarse, width, height, effects_radius(4, scale))) failed = 1;
|
||||
}
|
||||
if (!failed && glow > 0) {
|
||||
double spread = glowSpread / 100.0;
|
||||
double base = glowStyle == 1 ? 2.0 : 5.0;
|
||||
double widened = base * (1.0 + spread);
|
||||
if (widened < 1) widened = 1;
|
||||
glowRadius = effects_radius(widened, scale);
|
||||
float threshold = (float)(0.55 + 0.4 * (glowRange / 100.0));
|
||||
glowPlane = malloc(count * sizeof(float));
|
||||
float *source = malloc(count * sizeof(float));
|
||||
if (!glowPlane || !source) failed = 1;
|
||||
else {
|
||||
float denom = 1.0f - threshold;
|
||||
if (denom < 0.05f) denom = 0.05f;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
float t = (luma[i] - threshold) / denom;
|
||||
if (t < 0) t = 0;
|
||||
if (t > 1) t = 1;
|
||||
source[i] = t;
|
||||
}
|
||||
failed = !box_blur_plane(source, glowPlane, width, height, glowRadius);
|
||||
}
|
||||
free(source);
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
free(luma); free(fine); free(coarse); free(glowPlane);
|
||||
return;
|
||||
}
|
||||
double warmth = glowWarmth / 100.0;
|
||||
double glowRed, glowGreen, glowBlue, glowGain;
|
||||
if (glowStyle == 2) {
|
||||
// Halation's fringe is red. Warmth pushes it further that way, rather than toward yellow or blue.
|
||||
glowRed = 1;
|
||||
glowGreen = 0.35 - 0.3 * warmth;
|
||||
glowBlue = 0.2 - 0.2 * warmth;
|
||||
glowGain = 1;
|
||||
} else {
|
||||
glowRed = 0.75 + 0.25 * warmth;
|
||||
glowGreen = 0.6 + 0.2 * warmth;
|
||||
glowBlue = 0.75 - 0.6 * warmth;
|
||||
glowGain = glowStyle == 1 ? 1.4 : 1;
|
||||
}
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
size_t index = y * width + x;
|
||||
double r = fmin(1.0, p[0] / alpha);
|
||||
double g = fmin(1.0, p[1] / alpha);
|
||||
double b = fmin(1.0, p[2] / alpha);
|
||||
if (fine || coarse) {
|
||||
double tone = rec709(r, g, b);
|
||||
double detail = 0;
|
||||
if (fine) detail += (texture / 100.0) * (tone - fine[index]);
|
||||
if (coarse) detail += (clarity / 100.0) * (tone - coarse[index]);
|
||||
if (detail != 0) scale_luminance(&r, &g, &b, camera_clamp(tone + detail));
|
||||
}
|
||||
if (dehaze != 0) effects_dehaze(&r, &g, &b, dehaze);
|
||||
if (glowPlane && glow > 0) {
|
||||
double add = glowPlane[index] * (glow / 100.0) * glowGain;
|
||||
r = camera_clamp(r + add * glowRed);
|
||||
g = camera_clamp(g + add * glowGreen);
|
||||
b = camera_clamp(b + add * glowBlue);
|
||||
}
|
||||
effects_vignette(&r, &g, &b, x, y, width, height, vignetteAmount, vignetteMidpoint, vignetteRoundness,
|
||||
vignetteFeather, vignetteHighlights, vignetteStyle);
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
free(luma);
|
||||
free(fine);
|
||||
free(coarse);
|
||||
free(glowPlane);
|
||||
}
|
||||
|
||||
static double lut_at(const float *lut, double value) {
|
||||
double scaled = camera_clamp(value) * 255.0;
|
||||
int lo = (int)scaled;
|
||||
int hi = lo < 255 ? lo + 1 : 255;
|
||||
double t = scaled - lo;
|
||||
return lut[lo] + (lut[hi] - lut[lo]) * t;
|
||||
}
|
||||
|
||||
static void rgb_to_hsl(double r, double g, double b, double *h, double *s, double *l) {
|
||||
double maxc = fmax(r, fmax(g, b)), minc = fmin(r, fmin(g, b));
|
||||
*l = (maxc + minc) * 0.5;
|
||||
double d = maxc - minc;
|
||||
if (d < 1e-6) { *h = 0; *s = 0; return; }
|
||||
*s = d / (1.0 - fabs(2.0 * *l - 1.0));
|
||||
if (maxc == r) *h = fmod((g - b) / d, 6.0);
|
||||
else if (maxc == g) *h = (b - r) / d + 2.0;
|
||||
else *h = (r - g) / d + 4.0;
|
||||
*h /= 6.0;
|
||||
if (*h < 0) *h += 1;
|
||||
}
|
||||
|
||||
static double hue_to_rgb(double p, double q, double t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1.0 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 0.5) return q;
|
||||
if (t < 2.0 / 3) return p + (q - p) * (2.0 / 3 - t) * 6;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void hsl_to_rgb(double h, double s, double l, double *r, double *g, double *b) {
|
||||
if (s <= 1e-6) { *r = *g = *b = l; return; }
|
||||
double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
double p = 2 * l - q;
|
||||
*r = hue_to_rgb(p, q, h + 1.0 / 3);
|
||||
*g = hue_to_rgb(p, q, h);
|
||||
*b = hue_to_rgb(p, q, h - 1.0 / 3);
|
||||
}
|
||||
|
||||
static double circular_distance(double a, double b) {
|
||||
double d = fabs(a - b);
|
||||
return d > 0.5 ? 1 - d : d;
|
||||
}
|
||||
|
||||
static const double mixer_centers[8] = {0, 30.0 / 360, 60.0 / 360, 120.0 / 360, 180.0 / 360, 240.0 / 360, 270.0 / 360, 300.0 / 360};
|
||||
|
||||
static double point_weight(double h, double s, double l, const float *point) {
|
||||
double hueHalf = point[6] > 0.01f ? point[6] : 0.01f;
|
||||
double satHalf = point[7] > 0.01f ? point[7] : 0.01f;
|
||||
double lumHalf = point[8] > 0.01f ? point[8] : 0.01f;
|
||||
double hueW = 1 - circular_distance(h, point[0]) / hueHalf;
|
||||
double satW = 1 - fabs(s - point[1]) / satHalf;
|
||||
double lumW = 1 - fabs(l - point[2]) / lumHalf;
|
||||
if (hueW < 0 || satW < 0 || lumW < 0) return 0;
|
||||
return hueW * satW * lumW;
|
||||
}
|
||||
|
||||
void adjust_camera_raw_curve_color(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
const float *lumaLut, const float *redLut, const float *greenLut, const float *blueLut,
|
||||
double refineSaturation, const float *mixer, int pointCount, const float *points,
|
||||
const float *grade, double blending, double balance, int visualize) {
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
double tone = rec709(r, g, b);
|
||||
double mapped = lut_at(lumaLut, tone);
|
||||
scale_luminance(&r, &g, &b, mapped);
|
||||
if (refineSaturation != 0 && tone > 1e-4) {
|
||||
double factor = 1 + refineSaturation * (mapped / tone - 1);
|
||||
double lum = rec709(r, g, b);
|
||||
r = camera_clamp(lum + (r - lum) * factor);
|
||||
g = camera_clamp(lum + (g - lum) * factor);
|
||||
b = camera_clamp(lum + (b - lum) * factor);
|
||||
}
|
||||
r = lut_at(redLut, r); g = lut_at(greenLut, g); b = lut_at(blueLut, b);
|
||||
double h, s, l;
|
||||
rgb_to_hsl(r, g, b, &h, &s, &l);
|
||||
double sourceHue = h, sourceSat = s, sourceLum = l;
|
||||
double hueDelta = 0, satDelta = 0, lumDelta = 0, weightSum = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
double dist = circular_distance(h, mixer_centers[i]);
|
||||
double w = 1 - dist / (40.0 / 360);
|
||||
if (w <= 0) continue;
|
||||
hueDelta += mixer[i] * w * (30.0 / 360);
|
||||
satDelta += mixer[8 + i] * w;
|
||||
lumDelta += mixer[16 + i] * w * 0.25;
|
||||
weightSum += w;
|
||||
}
|
||||
if (weightSum > 1) { hueDelta /= weightSum; satDelta /= weightSum; lumDelta /= weightSum; }
|
||||
h += hueDelta; if (h < 0) h += 1; if (h >= 1) h -= 1;
|
||||
s = camera_clamp(s * (1 + satDelta));
|
||||
l = camera_clamp(l + lumDelta);
|
||||
for (int i = 0; i < pointCount; ++i) {
|
||||
const float *point = points + i * 9;
|
||||
double w = point_weight(h, s, l, point);
|
||||
if (w <= 0) continue;
|
||||
h += point[3] * w * (30.0 / 360);
|
||||
s = camera_clamp(s * (1 + point[4] * w));
|
||||
l = camera_clamp(l + point[5] * w * 0.25);
|
||||
}
|
||||
if (h < 0) h += 1; if (h >= 1) h -= 1;
|
||||
hsl_to_rgb(h, s, l, &r, &g, &b);
|
||||
// Balance moves the crossover between the shadow and highlight wheels. Toward highlights
|
||||
// it has to move down, so more of the picture counts as highlight and the shadow wheel
|
||||
// loses its hold; the other sign strengthened the shadow tint it was meant to weaken.
|
||||
double split = 0.5 - balance * 0.2;
|
||||
double reach = 0.12 + blending * 0.38;
|
||||
double shadowW = camera_clamp((split + reach - rec709(r, g, b)) / fmax(0.05, reach * 2));
|
||||
double highlightW = camera_clamp((rec709(r, g, b) - (split - reach)) / fmax(0.05, reach * 2));
|
||||
double midW = camera_clamp(1 - fabs(rec709(r, g, b) - split) / (0.35 + reach));
|
||||
double sum = shadowW + midW + highlightW;
|
||||
if (sum > 1e-4) { shadowW /= sum; midW /= sum; highlightW /= sum; }
|
||||
double weights[4] = {shadowW, midW, highlightW, 1};
|
||||
for (int wheel = 0; wheel < 4; ++wheel) {
|
||||
double wh = grade[wheel * 3], ws = grade[wheel * 3 + 1], wl = grade[wheel * 3 + 2];
|
||||
double w = weights[wheel];
|
||||
if (w <= 0 || (ws <= 0 && wl == 0)) continue;
|
||||
double cr, cg, cb;
|
||||
hsl_to_rgb(wh, 1, 0.5, &cr, &cg, &cb);
|
||||
r = camera_clamp(r + (cr - 0.5) * ws * w * 0.85);
|
||||
g = camera_clamp(g + (cg - 0.5) * ws * w * 0.85);
|
||||
b = camera_clamp(b + (cb - 0.5) * ws * w * 0.85);
|
||||
if (wl != 0) scale_luminance(&r, &g, &b, camera_clamp(rec709(r, g, b) + wl * 0.25 * w));
|
||||
}
|
||||
if (visualize >= 0 && visualize < pointCount && point_weight(sourceHue, sourceSat, sourceLum, points + visualize * 9) <= 0.05) {
|
||||
r *= 0.35; g *= 0.35; b *= 0.35;
|
||||
}
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static double detail_radius(double slider, double scale) {
|
||||
double base = 0.5 + (slider / 100.0) * 2.5;
|
||||
double radius = base * (scale > 0 ? scale : 1);
|
||||
if (radius < 0.5) radius = 0.5;
|
||||
if (radius > 64) radius = 64;
|
||||
return radius;
|
||||
}
|
||||
|
||||
static double pixel_hue_deg(double r, double g, double b) {
|
||||
double maxc = fmax(r, fmax(g, b)), minc = fmin(r, fmin(g, b));
|
||||
double chroma = maxc - minc;
|
||||
if (chroma < 1e-6) return 0;
|
||||
double hue;
|
||||
if (maxc == r) hue = fmod((g - b) / chroma, 6.0);
|
||||
else if (maxc == g) hue = (b - r) / chroma + 2.0;
|
||||
else hue = (r - g) / chroma + 4.0;
|
||||
hue = hue * 60.0;
|
||||
if (hue < 0) hue += 360.0;
|
||||
return hue;
|
||||
}
|
||||
|
||||
static int hue_in_range(double hue, double low, double high) {
|
||||
if (low <= high) return hue >= low && hue <= high;
|
||||
return hue >= low || hue <= high;
|
||||
}
|
||||
|
||||
static float sharpen_edge_at(const float *luma, size_t width, size_t height, size_t x, size_t y, int radius) {
|
||||
if (radius < 1) radius = 1;
|
||||
float center = luma[y * width + x];
|
||||
float sum = 0;
|
||||
int count = 0;
|
||||
for (int dy = -radius; dy <= radius; dy += radius) {
|
||||
for (int dx = -radius; dx <= radius; dx += radius) {
|
||||
if (dx == 0 && dy == 0) continue;
|
||||
long sx = (long)x + dx, sy = (long)y + dy;
|
||||
if (sx < 0 || sy < 0 || (size_t)sx >= width || (size_t)sy >= height) continue;
|
||||
sum += fabsf(luma[sy * width + sx] - center);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count ? sum / count : 0;
|
||||
}
|
||||
|
||||
void adjust_camera_raw_sharpen_mask_overlay(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double sharpenRadius, double sharpenDetail, double sharpenMasking, double scale) {
|
||||
if (width == 0 || height == 0) return;
|
||||
size_t count = width * height;
|
||||
float *luma = malloc(count * sizeof(float));
|
||||
if (!luma) return;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) { luma[y * width + x] = 0; continue; }
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
luma[y * width + x] = (float)rec709(r, g, b);
|
||||
}
|
||||
}
|
||||
int radius = effects_radius(detail_radius(sharpenRadius, scale), 1);
|
||||
double threshold = (sharpenMasking / 100.0) * 0.35;
|
||||
double detailBoost = 0.5 + sharpenDetail / 100.0;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
float edge = sharpen_edge_at(luma, width, height, x, y, radius);
|
||||
double mask = camera_clamp((edge * detailBoost - threshold) / fmax(0.04, 0.35 - threshold * 0.5));
|
||||
uint8_t gray = (uint8_t)lround(mask * alpha);
|
||||
p[0] = p[1] = p[2] = gray;
|
||||
}
|
||||
}
|
||||
free(luma);
|
||||
}
|
||||
|
||||
void adjust_camera_raw_detail(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double sharpenAmount, double sharpenRadius, double sharpenDetail, double sharpenMasking,
|
||||
double noiseLuminance, double noiseLuminanceDetail, double noiseLuminanceContrast,
|
||||
double noiseColor, double noiseColorDetail, double noiseColorSmoothness, double scale) {
|
||||
if (width == 0 || height == 0) return;
|
||||
if (sharpenAmount == 0 && noiseLuminance == 0 && noiseColor == 0) return;
|
||||
size_t count = width * height;
|
||||
float *luma = malloc(count * sizeof(float));
|
||||
float *work = malloc(count * sizeof(float));
|
||||
if (!luma || !work) { free(luma); free(work); return; }
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) { luma[y * width + x] = 0; continue; }
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
luma[y * width + x] = (float)rec709(r, g, b);
|
||||
}
|
||||
}
|
||||
if (noiseLuminance > 0) {
|
||||
int radius = effects_radius(1.0 + noiseLuminance / 50.0, scale);
|
||||
if (!box_blur_plane(luma, work, width, height, radius)) { free(luma); free(work); return; }
|
||||
double strength = noiseLuminance / 100.0;
|
||||
double preserve = noiseLuminanceDetail / 100.0;
|
||||
double contrast = noiseLuminanceContrast / 100.0;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
size_t index = y * width + x;
|
||||
float edge = sharpen_edge_at(luma, width, height, x, y, 1);
|
||||
double local = strength * (1.0 - preserve * fmin(1.0, edge * 6.0));
|
||||
float blurred = work[index];
|
||||
float target = (float)(luma[index] * (1.0 - local) + blurred * local);
|
||||
if (contrast != 0) target = (float)(target + contrast * 0.25 * (luma[index] - blurred));
|
||||
luma[index] = target;
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
scale_luminance(&r, &g, &b, target);
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (noiseColor > 0) {
|
||||
int radius = effects_radius(1.0 + noiseColorSmoothness / 40.0, scale);
|
||||
float *chroma = malloc(count * sizeof(float));
|
||||
float *chromaBlur = malloc(count * sizeof(float));
|
||||
if (!chroma || !chromaBlur) { free(chroma); free(chromaBlur); free(luma); free(work); return; }
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
double h, s, l;
|
||||
rgb_to_hsl(r, g, b, &h, &s, &l);
|
||||
chroma[y * width + x] = (float)s;
|
||||
}
|
||||
}
|
||||
if (!box_blur_plane(chroma, chromaBlur, width, height, radius)) {
|
||||
free(chroma); free(chromaBlur); free(luma); free(work); return;
|
||||
}
|
||||
double strength = noiseColor / 100.0;
|
||||
double preserve = noiseColorDetail / 100.0;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
size_t index = y * width + x;
|
||||
float edge = fabsf(chroma[index] - chromaBlur[index]);
|
||||
double local = strength * (1.0 - preserve * fmin(1.0, edge * 4.0));
|
||||
float sat = chroma[index] * (float)(1.0 - local) + chromaBlur[index] * (float)local;
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
double h, s, l;
|
||||
rgb_to_hsl(r, g, b, &h, &s, &l);
|
||||
s = sat;
|
||||
hsl_to_rgb(h, s, l, &r, &g, &b);
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
free(chroma);
|
||||
free(chromaBlur);
|
||||
}
|
||||
if (sharpenAmount > 0) {
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
luma[y * width + x] = (float)rec709(r, g, b);
|
||||
}
|
||||
}
|
||||
int radius = effects_radius(detail_radius(sharpenRadius, scale), 1);
|
||||
if (!box_blur_plane(luma, work, width, height, radius)) { free(luma); free(work); return; }
|
||||
double amount = sharpenAmount / 100.0;
|
||||
double detailMix = sharpenDetail / 100.0;
|
||||
double threshold = (sharpenMasking / 100.0) * 0.35;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
size_t index = y * width + x;
|
||||
float edge = sharpen_edge_at(luma, width, height, x, y, radius);
|
||||
double mask = camera_clamp((edge * (0.5 + detailMix) - threshold) / fmax(0.04, 0.35 - threshold * 0.5));
|
||||
double high = luma[index] - work[index];
|
||||
double sharpened = camera_clamp(luma[index] + high * amount * mask * (0.5 + detailMix));
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
scale_luminance(&r, &g, &b, sharpened);
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(luma);
|
||||
free(work);
|
||||
}
|
||||
|
||||
static void optics_defringe(double *r, double *g, double *b, double purpleAmount, double purpleLow, double purpleHigh,
|
||||
double greenAmount, double greenLow, double greenHigh) {
|
||||
double hue = pixel_hue_deg(*r, *g, *b);
|
||||
double maxc = fmax(*r, fmax(*g, *b)), minc = fmin(*r, fmin(*g, *b));
|
||||
double chroma = maxc - minc;
|
||||
if (chroma < 1e-6) return;
|
||||
double sat = chroma / maxc;
|
||||
double reduce = 0;
|
||||
if (purpleAmount > 0 && hue_in_range(hue, purpleLow, purpleHigh)) reduce = fmax(reduce, purpleAmount / 100.0);
|
||||
if (greenAmount > 0 && hue_in_range(hue, greenLow, greenHigh)) reduce = fmax(reduce, greenAmount / 100.0);
|
||||
if (reduce <= 0) return;
|
||||
double lum = rec709(*r, *g, *b);
|
||||
double factor = 1.0 - reduce * sat;
|
||||
*r = camera_clamp(lum + (*r - lum) * factor);
|
||||
*g = camera_clamp(lum + (*g - lum) * factor);
|
||||
*b = camera_clamp(lum + (*b - lum) * factor);
|
||||
}
|
||||
|
||||
static void optics_chromatic(uint8_t *rgba, size_t width, size_t height, size_t stride, double strength) {
|
||||
if (strength <= 0) return;
|
||||
uint8_t *copy = malloc(height * stride);
|
||||
if (!copy) return;
|
||||
for (size_t y = 0; y < height; ++y) memcpy(copy + y * stride, rgba + y * stride, width * 4);
|
||||
double cx = width * 0.5, cy = height * 0.5;
|
||||
double maxR = hypot(cx, cy);
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
const uint8_t *srcRow = copy + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
double dx = x + 0.5 - cx, dy = y + 0.5 - cy;
|
||||
double radial = hypot(dx, dy) / maxR;
|
||||
double shift = strength * radial * radial * 2.5;
|
||||
long rx = (long)lround(x - shift), bx = (long)lround(x + shift);
|
||||
const uint8_t *pr = srcRow + clamped_index(rx, width) * 4;
|
||||
const uint8_t *pb = srcRow + clamped_index(bx, width) * 4;
|
||||
double g = fmin(1.0, srcRow[x * 4 + 1] / alpha);
|
||||
double r = fmin(1.0, pr[0] / fmax(1.0, pr[3]));
|
||||
double b = fmin(1.0, pb[2] / fmax(1.0, pb[3]));
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
}
|
||||
|
||||
static void optics_vignette_correct(double *r, double *g, double *b, size_t x, size_t y, size_t width, size_t height,
|
||||
double amount, double midpoint) {
|
||||
if (amount == 0 || width == 0 || height == 0) return;
|
||||
double nx = ((double)x + 0.5) / (double)width * 2.0 - 1.0;
|
||||
double ny = ((double)y + 0.5) / (double)height * 2.0 - 1.0;
|
||||
double dist = hypot(nx, ny) / sqrt(2.0);
|
||||
double start = (midpoint / 100.0) * 0.85;
|
||||
double t = camera_clamp((dist - start) / 0.35);
|
||||
double mask = t * t * (3.0 - 2.0 * t);
|
||||
double lift = (amount / 100.0) * mask;
|
||||
if (lift > 0) {
|
||||
*r = camera_clamp(*r + (1.0 - *r) * lift);
|
||||
*g = camera_clamp(*g + (1.0 - *g) * lift);
|
||||
*b = camera_clamp(*b + (1.0 - *b) * lift);
|
||||
} else {
|
||||
double factor = 1.0 + lift;
|
||||
*r *= factor; *g *= factor; *b *= factor;
|
||||
}
|
||||
}
|
||||
|
||||
void adjust_camera_raw_optics(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
int removeChromatic, int lensProfile, double profileDistortion, double profileVignetting,
|
||||
double distortionK, double purpleAmount, double purpleHueLow, double purpleHueHigh,
|
||||
double greenAmount, double greenHueLow, double greenHueHigh,
|
||||
double vignetteAmount, double vignetteMidpoint, double scale) {
|
||||
if (width == 0 || height == 0) return;
|
||||
double profileVignette = lensProfile ? profileVignetting / 100.0 : 0;
|
||||
double vignette = vignetteAmount + profileVignette * 35.0;
|
||||
if (distortionK != 0) {
|
||||
size_t bytes = height * stride;
|
||||
uint8_t *copy = malloc(bytes);
|
||||
if (!copy) return;
|
||||
memcpy(copy, rgba, bytes);
|
||||
lens_distort(copy, rgba, width, height, stride, distortionK);
|
||||
free(copy);
|
||||
}
|
||||
if (removeChromatic) optics_chromatic(rgba, width, height, stride, 0.45);
|
||||
if (purpleAmount == 0 && greenAmount == 0 && vignette == 0) return;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
optics_defringe(&r, &g, &b, purpleAmount, purpleHueLow, purpleHueHigh, greenAmount, greenHueLow, greenHueHigh);
|
||||
optics_vignette_correct(&r, &g, &b, x, y, width, height, vignette, vignetteMidpoint);
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void adjust_camera_raw_calibration(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double shadowTint, double redHue, double redSaturation,
|
||||
double greenHue, double greenSaturation, double blueHue, double blueSaturation,
|
||||
int processVersion) {
|
||||
if (width == 0 || height == 0) return;
|
||||
double versionScale = processVersion <= 1 ? 0.55 : processVersion == 2 ? 0.65 : processVersion == 3 ? 0.75
|
||||
: processVersion == 4 ? 0.85 : processVersion == 5 ? 0.92 : 1.0;
|
||||
double tint = shadowTint / 100.0 * versionScale;
|
||||
double rh = redHue / 100.0 * (15.0 / 360.0) * versionScale;
|
||||
double rs = redSaturation / 100.0 * 0.45 * versionScale;
|
||||
double gh = greenHue / 100.0 * (15.0 / 360.0) * versionScale;
|
||||
double gs = greenSaturation / 100.0 * 0.45 * versionScale;
|
||||
double bh = blueHue / 100.0 * (15.0 / 360.0) * versionScale;
|
||||
double bs = blueSaturation / 100.0 * 0.45 * versionScale;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *row = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
uint8_t *p = row + x * 4;
|
||||
double alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
double r = fmin(1.0, p[0] / alpha), g = fmin(1.0, p[1] / alpha), b = fmin(1.0, p[2] / alpha);
|
||||
double h, s, l;
|
||||
rgb_to_hsl(r, g, b, &h, &s, &l);
|
||||
if (l < 0.35 && tint != 0) {
|
||||
h += tint * 0.06;
|
||||
if (h < 0) h += 1;
|
||||
if (h >= 1) h -= 1;
|
||||
}
|
||||
double maxc = fmax(r, fmax(g, b)), minc = fmin(r, fmin(g, b));
|
||||
if (maxc - minc > 1e-5) {
|
||||
if (r >= g && r >= b) { h += rh; s = camera_clamp(s * (1 + rs)); }
|
||||
else if (g >= r && g >= b) { h += gh; s = camera_clamp(s * (1 + gs)); }
|
||||
else { h += bh; s = camera_clamp(s * (1 + bs)); }
|
||||
if (h < 0) h += 1;
|
||||
if (h >= 1) h -= 1;
|
||||
}
|
||||
hsl_to_rgb(h, s, l, &r, &g, &b);
|
||||
write_premultiplied(p, r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
-1
@@ -8,7 +8,7 @@
|
||||
void adjust_gradient_map(uint8_t *rgba, size_t width, size_t height, size_t stride, const uint8_t *table);
|
||||
// Film grain on premultiplied RGBA pixels: the same brightness change on all three channels, strongest
|
||||
// in the midtones. `amount` is 0–100, `size` the grain's scale in document units, and `roughness`
|
||||
// (0–100) mixes in per-unit noise. Pixel (x, y) sits at (originX + (x + 0.5) × unitsPerPixel,
|
||||
// (0–100) adds smaller irregular particles whose scale remains relative to `size`. Pixel (x, y) sits at (originX + (x + 0.5) × unitsPerPixel,
|
||||
// originY + (y + 0.5) × unitsPerPixel), and its grain depends only on that position and `seed`, so a
|
||||
// piece of an image gets the same grain as that part of the whole.
|
||||
void adjust_grain(uint8_t *rgba, size_t width, size_t height, size_t stride, double amount, double size,
|
||||
@@ -30,4 +30,55 @@ void adjust_color_balance(uint8_t *rgba, size_t width, size_t height, size_t str
|
||||
// After resampling with a filter that rings (Lanczos), premultiplied RGBA colors can exceed their alpha;
|
||||
// this clamps each channel back to its pixel's alpha. `count` is the number of pixels.
|
||||
void rgba_clamp_premultiplied(uint8_t *rgba, size_t count);
|
||||
// Camera Raw's Light and Color groups on premultiplied RGBA pixels, in this order: white balance
|
||||
// (the three channel gains), exposure in stops of linear light, contrast about mid gray, highlights,
|
||||
// shadows, whites, blacks, vibrance, then saturation. Temperature and tint are relative, so the gains
|
||||
// are computed by the caller. Amounts are Camera Raw's own ranges (exposure −5…5, the rest −100…100).
|
||||
// `clipping` 0 renders the grade; 1 replaces it with a highlight-clip view (clipped channels lit on
|
||||
// black); 2 replaces it with a shadow-clip view (clipped channels dark on white). Alpha is kept.
|
||||
void adjust_camera_raw(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double redGain, double greenGain, double blueGain, double exposure, double contrast,
|
||||
double highlights, double shadows, double whites, double blacks,
|
||||
double vibrance, double saturation, int clipping);
|
||||
// Camera Raw Effects after Light and Color. Texture is a fine local contrast, Clarity a broader one.
|
||||
// Dehaze raises contrast and saturation when positive and lifts the shadows when negative. Glow, its
|
||||
// range, spread and warmth do nothing until `glow` is above zero: styles are 0 diffusion, 1 bloom,
|
||||
// 2 halation. Vignette styles are 0 highlight priority, 1 color priority, 2 paint overlay; Highlights
|
||||
// protects bright pixels only while the amount darkens. `scale` is preview pixels per layer pixel, so
|
||||
// the radii match a full-size render. Grain is applied separately. Alpha is kept.
|
||||
// Blue over clipped shadows and red over clipped highlights, on top of the grade. Preview only.
|
||||
void adjust_camera_raw_clip_overlay(uint8_t *rgba, size_t width, size_t height, size_t stride, int shadows, int highlights);
|
||||
// Curve, Color Mixer, and Color Grading after the basic grade. `lumaLut` and the channel LUTs are 256
|
||||
// entries. `mixer` is 24 floats: hue, saturation, luminance for eight families, −1…1. Each point color is
|
||||
// 9 floats (hue, saturation, luminance, three shifts −1…1, three range half-widths). `grade` is four wheels
|
||||
// of hue turns, saturation 0…1, and luminance −1…1. `visualize` darkens pixels outside that point color.
|
||||
void adjust_camera_raw_curve_color(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
const float *lumaLut, const float *redLut, const float *greenLut, const float *blueLut,
|
||||
double refineSaturation, const float *mixer, int pointCount, const float *points,
|
||||
const float *grade, double blending, double balance, int visualize);
|
||||
void adjust_camera_raw_effects(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double texture, double clarity, double dehaze,
|
||||
double glow, int glowStyle, double glowRange, double glowSpread, double glowWarmth,
|
||||
double vignetteAmount, double vignetteMidpoint, double vignetteRoundness,
|
||||
double vignetteFeather, double vignetteHighlights, int vignetteStyle,
|
||||
double scale);
|
||||
// Manual noise reduction, then sharpening. `scale` maps radius to preview pixels. Applied after the creative grade.
|
||||
void adjust_camera_raw_detail(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double sharpenAmount, double sharpenRadius, double sharpenDetail, double sharpenMasking,
|
||||
double noiseLuminance, double noiseLuminanceDetail, double noiseLuminanceContrast,
|
||||
double noiseColor, double noiseColorDetail, double noiseColorSmoothness, double scale);
|
||||
// Preview only: white where sharpening would land, black where masking protects. Uses the current sharpen sliders.
|
||||
void adjust_camera_raw_sharpen_mask_overlay(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double sharpenRadius, double sharpenDetail, double sharpenMasking, double scale);
|
||||
// Chromatic aberration, lens distortion, defringe, and lens-vignetting correction. `distortionK` matches `lens_distort`.
|
||||
void adjust_camera_raw_optics(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
int removeChromatic, int lensProfile, double profileDistortion, double profileVignetting,
|
||||
double distortionK, double purpleAmount, double purpleHueLow, double purpleHueHigh,
|
||||
double greenAmount, double greenHueLow, double greenHueHigh,
|
||||
double vignetteAmount, double vignetteMidpoint, double scale);
|
||||
// Camera calibration before the main grade. Primary hue and saturation shifts are −100…100; shadow tint is green/magenta.
|
||||
void adjust_camera_raw_calibration(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
double shadowTint, double redHue, double redSaturation,
|
||||
double greenHue, double greenSaturation, double blueHue, double blueSaturation,
|
||||
int processVersion);
|
||||
#endif
|
||||
Reference in new issue
Block a user