OmaPhoto 1.0.0: the Linux port of Compositor
A layered image editor for Linux and Omarchy, ported from Compositor (github.com/robbietilton/Compositor), whose Swift source is its specification. It reads and writes Compositor's .comp projects. Packages for Ubuntu, Fedora and Arch come from the release workflow; scripts/install/ holds one install script per distribution family, and flake.nix serves NixOS. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
c864304695
564 files changed
+100804
No files matched your search
@@ -0,0 +1,96 @@
|
||||
#include "AdjustPixels.h"
|
||||
#include <math.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++) {
|
||||
uint8_t *p = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; x++, p += 4) {
|
||||
unsigned a = p[3];
|
||||
if (a == 0) continue;
|
||||
unsigned r = p[0], g = p[1], b = p[2];
|
||||
if (a < 255) {
|
||||
r = (r * 255u + a / 2) / a;
|
||||
g = (g * 255u + a / 2) / a;
|
||||
b = (b * 255u + a / 2) / a;
|
||||
if (r > 255) r = 255;
|
||||
if (g > 255) g = 255;
|
||||
if (b > 255) b = 255;
|
||||
}
|
||||
unsigned level = (2126u * r + 7152u * g + 722u * b + 5000u) / 10000u;
|
||||
const uint8_t *color = table + (level > 255 ? 255 : level) * 3;
|
||||
p[0] = (uint8_t)((color[0] * a + 127u) / 255u);
|
||||
p[1] = (uint8_t)((color[1] * a + 127u) / 255u);
|
||||
p[2] = (uint8_t)((color[2] * a + 127u) / 255u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t mix32(uint32_t x) {
|
||||
x ^= x >> 16;
|
||||
x *= 0x7feb352dU;
|
||||
x ^= x >> 15;
|
||||
x *= 0x846ca68bU;
|
||||
x ^= x >> 16;
|
||||
return x;
|
||||
}
|
||||
|
||||
// A value in −1…1 for an integer lattice point, fixed by the point and the seed. Two uniform halves
|
||||
// summed give a triangular spread, closer to film grain than flat noise.
|
||||
static inline float lattice(int64_t ix, int64_t iy, uint32_t seed) {
|
||||
uint32_t h = mix32((uint32_t)ix * 0x9E3779B1U ^ mix32((uint32_t)iy * 0x85EBCA77U ^ seed));
|
||||
return (float)(h & 0xFFFFU) / 65535.0f + (float)(h >> 16) / 65535.0f - 1.0f;
|
||||
}
|
||||
|
||||
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,
|
||||
double roughness, uint32_t seed, double originX, double originY, double unitsPerPixel) {
|
||||
if (!(amount > 0) || !(unitsPerPixel > 0)) return;
|
||||
if (!(size > 0)) size = 1;
|
||||
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);
|
||||
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 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;
|
||||
float level = (0.2126f * r + 0.7152f * g + 0.0722f * b) / 255.0f;
|
||||
if (level > 1) level = 1;
|
||||
// Film grain shows most in the midtones.
|
||||
float delta = noise * strength * (0.4f + 2.4f * level * (1.0f - level));
|
||||
float coverage = (float)a / 255.0f;
|
||||
p[0] = (uint8_t)(clamp255(r + delta) * coverage + 0.5f);
|
||||
p[1] = (uint8_t)(clamp255(g + delta) * coverage + 0.5f);
|
||||
p[2] = (uint8_t)(clamp255(b + delta) * coverage + 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rgba_clamp_premultiplied(uint8_t *rgba, size_t count) {
|
||||
for (size_t i = 0; i < count; i++, rgba += 4) {
|
||||
uint8_t a = rgba[3];
|
||||
if (rgba[0] > a) rgba[0] = a;
|
||||
if (rgba[1] > a) rgba[1] = a;
|
||||
if (rgba[2] > a) rgba[2] = a;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef AdjustPixels_h
|
||||
#define AdjustPixels_h
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
// Gradient Map on premultiplied RGBA pixels (4 bytes per pixel, `stride` bytes per row): each pixel's
|
||||
// luminance picks a color from `table` (256 × 3 straight sRGB bytes, darkest first). Alpha is kept and
|
||||
// fully transparent pixels are left alone.
|
||||
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,
|
||||
// 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,
|
||||
double roughness, uint32_t seed, double originX, double originY, double unitsPerPixel);
|
||||
// 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);
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "BrushPixels.h"
|
||||
|
||||
void brush_alpha_bounds(const uint8_t *bytes, size_t width, size_t height, size_t stride, size_t bounds[4]) {
|
||||
size_t left = width, right = 0, top = height, bottom = 0;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
const uint8_t *row = bytes + y * stride;
|
||||
size_t first = 0;
|
||||
while (first < width && row[first * 4 + 3] == 0) ++first;
|
||||
if (first == width) continue;
|
||||
size_t last = width;
|
||||
while (last > first && row[(last - 1) * 4 + 3] == 0) --last;
|
||||
if (first < left) left = first;
|
||||
if (last > right) right = last;
|
||||
if (y < top) top = y;
|
||||
bottom = y + 1;
|
||||
}
|
||||
bounds[0] = right ? left : 0;
|
||||
bounds[1] = right ? top : 0;
|
||||
bounds[2] = right;
|
||||
bounds[3] = bottom;
|
||||
}
|
||||
|
||||
void layer_unpremultiply_opaque(uint8_t *rgba, size_t stride, size_t width, size_t height) {
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *p = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x, p += 4) {
|
||||
unsigned a = p[3];
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
unsigned v = a ? (p[c] * 255u + a / 2) / a : 0;
|
||||
p[c] = v > 255 ? 255 : v;
|
||||
}
|
||||
p[3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
void layer_restore_alpha(uint8_t *rgba, size_t stride, const uint8_t *alpha, size_t alphaStride, size_t width, size_t height) {
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t *p = rgba + y * stride;
|
||||
for (size_t x = 0; x < width; ++x, p += 4) {
|
||||
unsigned a = alpha[y * alphaStride + x];
|
||||
for (int c = 0; c < 3; ++c) p[c] = (p[c] * a + 127) / 255;
|
||||
p[3] = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
void layer_extract_alpha(const uint8_t *rgba, size_t rgbaStride, uint8_t *gray, size_t grayStride, size_t width, size_t height) {
|
||||
for (size_t y = 0; y < height; ++y)
|
||||
for (size_t x = 0; x < width; ++x)
|
||||
gray[y * grayStride + x] = rgba[y * rgbaStride + x * 4 + 3];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef BrushPixels_h
|
||||
#define BrushPixels_h
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
// Half-open bounds of nonzero alpha in premultiplied RGBA. Empty returns all zero.
|
||||
void brush_alpha_bounds(const uint8_t *bytes, size_t width, size_t height, size_t stride, size_t bounds[4]);
|
||||
void layer_extract_alpha(const uint8_t *rgba, size_t rgbaStride, uint8_t *gray, size_t grayStride, size_t width, size_t height);
|
||||
void layer_unpremultiply_opaque(uint8_t *rgba, size_t stride, size_t width, size_t height);
|
||||
void layer_restore_alpha(uint8_t *rgba, size_t stride, const uint8_t *alpha, size_t alphaStride, size_t width, size_t height);
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "ContentFill.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
|
||||
static uint32_t next_random(uint32_t *state) {
|
||||
*state = *state * 1664525u + 1013904223u;
|
||||
return *state;
|
||||
}
|
||||
static double match(const uint8_t *pixels, size_t stride, const uint8_t *known,
|
||||
int w, int h, int p, int q, int radius) {
|
||||
int px=p%w, py=p/w, qx=q%w, qy=q/w, count=0;
|
||||
double sum=0;
|
||||
for(int dy=-radius; dy<=radius; ++dy) for(int dx=-radius; dx<=radius; ++dx) {
|
||||
int x=px+dx,y=py+dy,sx=qx+dx,sy=qy+dy;
|
||||
if(x<0||y<0||x>=w||y>=h||sx<0||sy<0||sx>=w||sy>=h||!known[y*w+x]) continue;
|
||||
const uint8_t *a=pixels+y*stride+x*4, *b=pixels+sy*stride+sx*4;
|
||||
for(int c=0;c<4;++c) { int d=a[c]-b[c]; sum+=d*d; }
|
||||
++count;
|
||||
}
|
||||
return count ? sum/count : DBL_MAX;
|
||||
}
|
||||
int content_fill(uint8_t *pixels, size_t stride, const uint8_t *mask, size_t ms, int w, int h) {
|
||||
size_t n=(size_t)w*h;
|
||||
uint8_t *known=calloc(n,1), *target=calloc(n,1), *valid=calloc(n,1), *queued=calloc(n,1);
|
||||
int *donors=malloc(n*sizeof(int)), *queue=malloc(n*sizeof(int)), *chosen=malloc(n*sizeof(int));
|
||||
if(!known||!target||!valid||!queued||!donors||!queue||!chosen) {
|
||||
free(known);free(target);free(valid);free(queued);free(donors);free(queue);free(chosen);return -1;
|
||||
}
|
||||
int radius=(w>=5 && h>=5)?2:0;
|
||||
size_t missing=0, donorCount=0, head=0,tail=0, scan=0;
|
||||
// Selected pixels are filled. Unselected opaque pixels are the image to match and copy from; unselected
|
||||
// transparent ones are neither — nothing to match against, and left as they are.
|
||||
for(int y=0;y<h;++y) for(int x=0;x<w;++x) {
|
||||
int p=y*w+x; target[p]=mask[y*ms+x]!=0; known[p]=!target[p] && pixels[y*stride+x*4+3]==255; chosen[p]=-1;
|
||||
if(target[p]) ++missing;
|
||||
}
|
||||
if(!missing) { donorCount=1; goto done; }
|
||||
for(int y=0;y<h;++y) for(int x=0;x<w;++x) {
|
||||
int p=y*w+x;
|
||||
if(!known[p]) continue;
|
||||
int ok=1;
|
||||
for(int dy=-radius;dy<=radius&&ok;++dy) for(int dx=-radius;dx<=radius;++dx) {
|
||||
int sx=x+dx,sy=y+dy;
|
||||
if(sx<0||sy<0||sx>=w||sy>=h||!known[sy*w+sx]) {ok=0;break;}
|
||||
}
|
||||
if(ok) { valid[p]=1;donors[donorCount++]=p; }
|
||||
}
|
||||
if(!donorCount) goto done;
|
||||
for(int y=0;y<h;++y) for(int x=0;x<w;++x) {
|
||||
int p=y*w+x;
|
||||
if(target[p] && ((x&&known[p-1])||(x+1<w&&known[p+1])||(y&&known[p-w])||(y+1<h&&known[p+w]))) {
|
||||
queue[tail++]=p;queued[p]=1;
|
||||
}
|
||||
}
|
||||
uint32_t seed=0x6d2b79f5;
|
||||
for(;;) {
|
||||
while(head<tail) {
|
||||
int p=queue[head++],x=p%w,y=p/w,best=-1;
|
||||
double score=DBL_MAX;
|
||||
int neighbors[4]={x?p-1:-1,x+1<w?p+1:-1,y?p-w:-1,y+1<h?p+w:-1};
|
||||
// Propagate coherent source offsets, then refine with randomized patch search.
|
||||
for(int k=0;k<28;++k) {
|
||||
int q=-1;
|
||||
if(k<4) {
|
||||
int t=neighbors[k];
|
||||
if(t>=0) q=(chosen[t]>=0?chosen[t]:t)+(p-t);
|
||||
} else q=donors[next_random(&seed)%donorCount];
|
||||
if(q<0||(size_t)q>=n||!valid[q]) continue;
|
||||
double s=match(pixels,stride,known,w,h,p,q,radius);
|
||||
if(best<0||s<score) {score=s;best=q;}
|
||||
}
|
||||
if(best<0) best=donors[0];
|
||||
for(int r=64;r>=1;r/=2) {
|
||||
int qx=best%w+(int)(next_random(&seed)%(2*r+1))-r;
|
||||
int qy=best/w+(int)(next_random(&seed)%(2*r+1))-r;
|
||||
if(qx<0||qy<0||qx>=w||qy>=h||!valid[qy*w+qx]) continue;
|
||||
int q=qy*w+qx; double s=match(pixels,stride,known,w,h,p,q,radius);
|
||||
if(s<score) {score=s;best=q;}
|
||||
}
|
||||
memcpy(pixels+y*stride+x*4,pixels+(best/w)*stride+(best%w)*4,4);
|
||||
known[p]=1;chosen[p]=best;
|
||||
for(int k=0;k<4;++k) { int q=neighbors[k]; if(q>=0&&target[q]&&!known[q]&&!queued[q]) {queued[q]=1;queue[tail++]=q;} }
|
||||
}
|
||||
// A selected area that only transparency touches starts from the best random donor, then spreads.
|
||||
while(scan<n && (!target[scan]||known[scan])) ++scan;
|
||||
if(scan>=n) break;
|
||||
queue[tail++]=(int)scan; queued[scan]=1;
|
||||
}
|
||||
done:
|
||||
free(known);free(target);free(valid);free(queued);free(donors);free(queue);free(chosen);
|
||||
return donorCount ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
// Returns 1 on success, 0 when no source patch exists, -1 on allocation failure.
|
||||
int content_fill(uint8_t *rgba, size_t stride, const uint8_t *mask, size_t maskStride, int width, int height);
|
||||
@@ -0,0 +1,259 @@
|
||||
#include "HealPixels.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enum { OUTSIDE = 0, RING = 1, HOLE = 2 };
|
||||
|
||||
void heal_coverage_bounds(const uint8_t *gray, size_t width, size_t height, size_t stride, long bounds[4]) {
|
||||
long x0 = (long)width, y0 = (long)height, x1 = 0, y1 = 0;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
const uint8_t *row = gray + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
if (!row[x]) continue;
|
||||
if ((long)x < x0) x0 = (long)x;
|
||||
if ((long)x + 1 > x1) x1 = (long)x + 1;
|
||||
if ((long)y < y0) y0 = (long)y;
|
||||
if ((long)y + 1 > y1) y1 = (long)y + 1;
|
||||
}
|
||||
}
|
||||
if (x1 <= x0 || y1 <= y0) x0 = y0 = x1 = y1 = 0;
|
||||
bounds[0] = x0; bounds[1] = y0; bounds[2] = x1; bounds[3] = y1;
|
||||
}
|
||||
|
||||
static inline uint32_t heal_hash(uint32_t x) {
|
||||
x ^= x >> 16; x *= 0x7feb352dU;
|
||||
x ^= x >> 15; x *= 0x846ca68bU;
|
||||
x ^= x >> 16;
|
||||
return x;
|
||||
}
|
||||
static inline double heal_unit(uint32_t key) { return (double)(heal_hash(key) >> 8) / 16777216.0; }
|
||||
|
||||
// Mean squared difference between the ring around the spot and the ring around the patch
|
||||
// offset by (dx, dy). Infinite when the patch would overlap the spot or leave the image.
|
||||
static double heal_score(const uint8_t *rgba, size_t stride, const uint8_t *role, long wx0, long wy0,
|
||||
long ww, long wh, long dx, long dy, long W, long H) {
|
||||
if (labs(dx) < ww && labs(dy) < wh) return INFINITY;
|
||||
if (wx0 + dx < 0 || wy0 + dy < 0 || wx0 + ww + dx > W || wy0 + wh + dy > H) return INFINITY;
|
||||
double sum = 0;
|
||||
long n = 0;
|
||||
for (long y = 0; y < wh; ++y) {
|
||||
for (long x = 0; x < ww; ++x) {
|
||||
if (role[y * ww + x] != RING) continue;
|
||||
const uint8_t *t = rgba + (size_t)(wy0 + y) * stride + (size_t)(wx0 + x) * 4;
|
||||
const uint8_t *s = rgba + (size_t)(wy0 + y + dy) * stride + (size_t)(wx0 + x + dx) * 4;
|
||||
for (int c = 0; c < 4; ++c) { double d = (double)t[c] - s[c]; sum += d * d; }
|
||||
++n;
|
||||
}
|
||||
}
|
||||
return n ? sum / n : INFINITY;
|
||||
}
|
||||
|
||||
// Solves for smooth values over HOLE pixels, fixed to the RING values around them. A coarser
|
||||
// copy is solved first and used as the starting point, so large spots settle in few passes.
|
||||
static void heal_solve(float *value, const uint8_t *role, long w, long h, int depth) {
|
||||
int iterations = 300;
|
||||
if (w > 32 && h > 32 && depth < 16) {
|
||||
long cw = (w + 1) / 2, ch = (h + 1) / 2;
|
||||
float *coarse = calloc((size_t)(cw * ch * 4), sizeof(float));
|
||||
uint8_t *coarseRole = calloc((size_t)(cw * ch), 1);
|
||||
if (coarse && coarseRole) {
|
||||
for (long y = 0; y < ch; ++y) {
|
||||
for (long x = 0; x < cw; ++x) {
|
||||
int known = 0, hole = 0;
|
||||
float knownSum[4] = {0, 0, 0, 0}, holeSum[4] = {0, 0, 0, 0};
|
||||
for (long j = 0; j < 2; ++j) {
|
||||
for (long i = 0; i < 2; ++i) {
|
||||
long fx = x * 2 + i, fy = y * 2 + j;
|
||||
if (fx >= w || fy >= h) continue;
|
||||
long p = fy * w + fx;
|
||||
if (role[p] == RING) { ++known; for (int c = 0; c < 4; ++c) knownSum[c] += value[p * 4 + c]; }
|
||||
else if (role[p] == HOLE) { ++hole; for (int c = 0; c < 4; ++c) holeSum[c] += value[p * 4 + c]; }
|
||||
}
|
||||
}
|
||||
long q = y * cw + x;
|
||||
if (known) { coarseRole[q] = RING; for (int c = 0; c < 4; ++c) coarse[q * 4 + c] = knownSum[c] / known; }
|
||||
else if (hole) { coarseRole[q] = HOLE; for (int c = 0; c < 4; ++c) coarse[q * 4 + c] = holeSum[c] / hole; }
|
||||
}
|
||||
}
|
||||
heal_solve(coarse, coarseRole, cw, ch, depth + 1);
|
||||
for (long y = 0; y < h; ++y) {
|
||||
for (long x = 0; x < w; ++x) {
|
||||
long p = y * w + x, q = (y / 2) * cw + x / 2;
|
||||
if (role[p] == HOLE && coarseRole[q] == HOLE) memcpy(value + p * 4, coarse + q * 4, 4 * sizeof(float));
|
||||
}
|
||||
}
|
||||
iterations = 40;
|
||||
}
|
||||
free(coarse);
|
||||
free(coarseRole);
|
||||
}
|
||||
const float omega = 1.8f;
|
||||
for (int it = 0; it < iterations; ++it) {
|
||||
for (long y = 0; y < h; ++y) {
|
||||
for (long x = 0; x < w; ++x) {
|
||||
long p = y * w + x;
|
||||
if (role[p] != HOLE) continue;
|
||||
float sum[4] = {0, 0, 0, 0};
|
||||
int n = 0;
|
||||
long neighbors[4][2] = {{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}};
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
long nx = neighbors[k][0], ny = neighbors[k][1];
|
||||
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
|
||||
long q = ny * w + nx;
|
||||
if (role[q] == OUTSIDE) continue;
|
||||
for (int c = 0; c < 4; ++c) sum[c] += value[q * 4 + c];
|
||||
++n;
|
||||
}
|
||||
if (!n) continue;
|
||||
for (int c = 0; c < 4; ++c) value[p * 4 + c] += omega * (sum[c] / n - value[p * 4 + c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int spot_heal(uint8_t *rgba, const uint8_t *coverage, size_t width, size_t height, size_t stride,
|
||||
float opacity, int mode, uint32_t seed) {
|
||||
long W = (long)width, H = (long)height;
|
||||
long bounds[4];
|
||||
heal_coverage_bounds(coverage, width, height, width, bounds);
|
||||
if (bounds[2] <= bounds[0]) return 0;
|
||||
long bw = bounds[2] - bounds[0], bh = bounds[3] - bounds[1], size = bw > bh ? bw : bh;
|
||||
long ring = size / 8;
|
||||
if (ring < 2) ring = 2;
|
||||
if (ring > 16) ring = 16;
|
||||
// Work box: the spot plus its ring, clipped to the image.
|
||||
long wx0 = bounds[0] - ring < 0 ? 0 : bounds[0] - ring, wy0 = bounds[1] - ring < 0 ? 0 : bounds[1] - ring;
|
||||
long wx1 = bounds[2] + ring > W ? W : bounds[2] + ring, wy1 = bounds[3] + ring > H ? H : bounds[3] + ring;
|
||||
long ww = wx1 - wx0, wh = wy1 - wy0, wn = ww * wh;
|
||||
|
||||
uint8_t *role = calloc((size_t)wn, 1), *near = calloc((size_t)wn, 1);
|
||||
long *prefix = malloc((size_t)((ww > wh ? ww : wh) + 1) * sizeof(long));
|
||||
float *value = malloc((size_t)wn * 4 * sizeof(float));
|
||||
int status = -1;
|
||||
if (!role || !near || !prefix || !value) goto done;
|
||||
for (long y = 0; y < wh; ++y)
|
||||
for (long x = 0; x < ww; ++x)
|
||||
role[y * ww + x] = coverage[(size_t)(wy0 + y) * width + (size_t)(wx0 + x)] ? HOLE : OUTSIDE;
|
||||
// The ring: pixels within `ring` of the spot (a square dilation, row pass then column pass).
|
||||
for (long y = 0; y < wh; ++y) {
|
||||
prefix[0] = 0;
|
||||
for (long x = 0; x < ww; ++x) prefix[x + 1] = prefix[x] + (role[y * ww + x] == HOLE);
|
||||
for (long x = 0; x < ww; ++x) {
|
||||
long lo = x - ring < 0 ? 0 : x - ring, hi = x + ring + 1 > ww ? ww : x + ring + 1;
|
||||
near[y * ww + x] = prefix[hi] - prefix[lo] > 0;
|
||||
}
|
||||
}
|
||||
for (long x = 0; x < ww; ++x) {
|
||||
prefix[0] = 0;
|
||||
for (long y = 0; y < wh; ++y) prefix[y + 1] = prefix[y] + near[y * ww + x];
|
||||
for (long y = 0; y < wh; ++y) {
|
||||
long lo = y - ring < 0 ? 0 : y - ring, hi = y + ring + 1 > wh ? wh : y + ring + 1;
|
||||
if (role[y * ww + x] == OUTSIDE && prefix[hi] - prefix[lo] > 0) role[y * ww + x] = RING;
|
||||
}
|
||||
}
|
||||
long ringCount = 0;
|
||||
for (long p = 0; p < wn; ++p) ringCount += role[p] == RING;
|
||||
if (!ringCount) { status = 0; goto done; }
|
||||
|
||||
// Source patch for Content-Aware and Proximity Match.
|
||||
long ox = 0, oy = 0;
|
||||
int haveSource = 0;
|
||||
if (mode != 1) {
|
||||
static const double factors[5] = {1.05, 1.35, 1.75, 2.25, 2.8};
|
||||
int count = mode == 2 ? 2 : 5;
|
||||
double best = INFINITY;
|
||||
for (int f = 0; f < count; ++f) {
|
||||
for (int a = 0; a < 24; ++a) {
|
||||
double angle = a * M_PI / 12.0;
|
||||
long dx = lround(cos(angle) * factors[f] * ww), dy = lround(sin(angle) * factors[f] * wh);
|
||||
double score = heal_score(rgba, stride, role, wx0, wy0, ww, wh, dx, dy, W, H);
|
||||
if (!isfinite(score)) continue;
|
||||
score *= mode == 2 ? 1.0 + 0.6 * f : 1.0 + 0.1 * f; // nearer patches win ties
|
||||
if (score < best) { best = score; ox = dx; oy = dy; }
|
||||
}
|
||||
}
|
||||
if (isfinite(best)) {
|
||||
// Fine-tune the alignment so repeating texture lines up.
|
||||
long cx = ox, cy = oy;
|
||||
double refined = heal_score(rgba, stride, role, wx0, wy0, ww, wh, cx, cy, W, H);
|
||||
for (long j = -3; j <= 3; ++j) {
|
||||
for (long i = -3; i <= 3; ++i) {
|
||||
double score = heal_score(rgba, stride, role, wx0, wy0, ww, wh, cx + i, cy + j, W, H);
|
||||
if (score < refined) { refined = score; ox = cx + i; oy = cy + j; }
|
||||
}
|
||||
}
|
||||
haveSource = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Membrane: the edge difference between the original and the patch (or the original itself
|
||||
// for a smooth fill), spread across the spot.
|
||||
double mean[4] = {0, 0, 0, 0}, detail[3] = {0, 0, 0};
|
||||
for (long y = 0; y < wh; ++y) {
|
||||
for (long x = 0; x < ww; ++x) {
|
||||
long p = y * ww + x;
|
||||
if (role[p] != RING) { memset(value + p * 4, 0, 4 * sizeof(float)); continue; }
|
||||
long ix = wx0 + x, iy = wy0 + y;
|
||||
const uint8_t *t = rgba + (size_t)iy * stride + (size_t)ix * 4;
|
||||
const uint8_t *s = haveSource ? rgba + (size_t)(iy + oy) * stride + (size_t)(ix + ox) * 4 : NULL;
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
value[p * 4 + c] = (float)t[c] - (s ? s[c] : 0);
|
||||
mean[c] += value[p * 4 + c];
|
||||
}
|
||||
if (!haveSource) {
|
||||
// Fine detail around the spot: each pixel against the average of its neighbours.
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
double around = 0; int n = 0;
|
||||
long offsets[4][2] = {{ix - 1, iy}, {ix + 1, iy}, {ix, iy - 1}, {ix, iy + 1}};
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
if (offsets[k][0] < 0 || offsets[k][1] < 0 || offsets[k][0] >= W || offsets[k][1] >= H) continue;
|
||||
around += rgba[(size_t)offsets[k][1] * stride + (size_t)offsets[k][0] * 4 + c];
|
||||
++n;
|
||||
}
|
||||
if (n) { double d = t[c] - around / n; detail[c] += d * d; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int c = 0; c < 4; ++c) mean[c] /= ringCount;
|
||||
for (long p = 0; p < wn; ++p)
|
||||
if (role[p] == HOLE) for (int c = 0; c < 4; ++c) value[p * 4 + c] = (float)mean[c];
|
||||
heal_solve(value, role, ww, wh, 0);
|
||||
for (int c = 0; c < 3; ++c) detail[c] = sqrt(detail[c] / ringCount) * 0.9;
|
||||
|
||||
for (long y = 0; y < wh; ++y) {
|
||||
for (long x = 0; x < ww; ++x) {
|
||||
long p = y * ww + x;
|
||||
if (role[p] != HOLE) continue;
|
||||
long ix = wx0 + x, iy = wy0 + y;
|
||||
uint8_t *t = rgba + (size_t)iy * stride + (size_t)ix * 4;
|
||||
const uint8_t *s = haveSource ? rgba + (size_t)(iy + oy) * stride + (size_t)(ix + ox) * 4 : NULL;
|
||||
double amount = coverage[(size_t)iy * width + (size_t)ix] / 255.0 * opacity;
|
||||
double grain = 0;
|
||||
if (!haveSource) {
|
||||
uint32_t key = heal_hash(seed ^ heal_hash((uint32_t)(iy * W + ix)));
|
||||
double u1 = heal_unit(key), u2 = heal_unit(key ^ 0x68e31da4U);
|
||||
grain = sqrt(-2.0 * log(1.0 - u1)) * cos(2.0 * M_PI * u2);
|
||||
}
|
||||
double out[4];
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
double healed = (s ? s[c] : 0) + value[p * 4 + c] + (c < 3 ? grain * detail[c] : 0);
|
||||
out[c] = t[c] + (healed - t[c]) * amount;
|
||||
}
|
||||
double alpha = out[3] < 0 ? 0 : out[3] > 255 ? 255 : out[3];
|
||||
t[3] = (uint8_t)lround(alpha);
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
double v = out[c] < 0 ? 0 : out[c] > t[3] ? t[3] : out[c];
|
||||
t[c] = (uint8_t)lround(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
status = 0;
|
||||
done:
|
||||
free(role);
|
||||
free(near);
|
||||
free(prefix);
|
||||
free(value);
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef HealPixels_h
|
||||
#define HealPixels_h
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
// Half-open bounds of nonzero bytes in a gray bitmap; all zero when empty.
|
||||
void heal_coverage_bounds(const uint8_t *gray, size_t width, size_t height, size_t stride, long bounds[4]);
|
||||
// Spot healing, in place, over premultiplied RGBA (4 bytes per pixel, `stride` bytes per row).
|
||||
// `coverage` (width * height bytes, 0–255) marks what to heal.
|
||||
// mode 0, Content-Aware: copies texture from the nearby patch whose surrounding ring of pixels
|
||||
// best matches the ring around the spot;
|
||||
// mode 1, Create Texture: fills smoothly from the spot's edges and adds grain matching the
|
||||
// detail around it;
|
||||
// mode 2, Proximity Match: like 0, but takes the closest good patch.
|
||||
// Copied texture is blended so it meets the surrounding tone exactly: the difference along the
|
||||
// spot's edge is spread smoothly across it (a membrane fill). The result replaces the original
|
||||
// by coverage × opacity. Returns 0, or -1 when memory runs out.
|
||||
int spot_heal(uint8_t *rgba, const uint8_t *coverage, size_t width, size_t height, size_t stride,
|
||||
float opacity, int mode, uint32_t seed);
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "LensPixels.h"
|
||||
#include <math.h>
|
||||
|
||||
void lens_distort(const uint8_t *source, uint8_t *destination, size_t width, size_t height, size_t stride, double k) {
|
||||
double cx = width * 0.5, cy = height * 0.5;
|
||||
double halfDiagonal2 = cx * cx + cy * cy;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
double dy = y + 0.5 - cy;
|
||||
uint8_t *out = destination + y * stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
double dx = x + 0.5 - cx;
|
||||
double scale = 1.0 - k * (dx * dx + dy * dy) / halfDiagonal2;
|
||||
// Source position in pixel-center coordinates.
|
||||
double sx = cx + dx * scale - 0.5, sy = cy + dy * scale - 0.5;
|
||||
double fx0 = floor(sx), fy0 = floor(sy);
|
||||
double fx = sx - fx0, fy = sy - fy0;
|
||||
long x0 = (long)fx0, y0 = (long)fy0;
|
||||
double sums[4] = {0, 0, 0, 0};
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
long row = y0 + j;
|
||||
if (row < 0 || row >= (long)height) continue;
|
||||
double wy = j ? fy : 1 - fy;
|
||||
if (wy == 0) continue;
|
||||
const uint8_t *line = source + (size_t)row * stride;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
long column = x0 + i;
|
||||
if (column < 0 || column >= (long)width) continue;
|
||||
double weight = wy * (i ? fx : 1 - fx);
|
||||
if (weight == 0) continue;
|
||||
const uint8_t *p = line + (size_t)column * 4;
|
||||
for (int c = 0; c < 4; ++c) sums[c] += weight * p[c];
|
||||
}
|
||||
}
|
||||
for (int c = 0; c < 4; ++c) out[x * 4 + c] = (uint8_t)lround(sums[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef LensPixels_h
|
||||
#define LensPixels_h
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
// Radial lens distortion over premultiplied RGBA (4 bytes per pixel, `stride` bytes per row, same
|
||||
// layout for source and destination). Each destination pixel samples the source bilinearly at
|
||||
// its offset from the image center scaled by (1 - k * r²), where r is that offset relative to the
|
||||
// half-diagonal: k > 0 pulls samples inward (straightens barrel distortion, corners crop), k < 0
|
||||
// pushes them outward (straightens pincushion distortion, corners turn transparent). Pixels
|
||||
// outside the source are transparent. k = 0 copies the source exactly.
|
||||
void lens_distort(const uint8_t *source, uint8_t *destination, size_t width, size_t height, size_t stride, double k);
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "LevelsPixels.h"
|
||||
#include <math.h>
|
||||
void levels_apply(uint8_t *pixels, size_t count, const float *tables) {
|
||||
for (size_t i=0; i<count; ++i) {
|
||||
uint8_t *p = pixels + i*4;
|
||||
float alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
for (int channel=0; channel<3; ++channel) {
|
||||
float x = fminf(255, p[channel]*255.0f/alpha);
|
||||
int lo = (int)x, hi = lo < 255 ? lo+1 : 255;
|
||||
const float *table = tables + channel*256;
|
||||
float result = table[lo] + (table[hi]-table[lo])*(x-lo);
|
||||
p[channel] = (uint8_t)fminf(alpha, fmaxf(0, roundf(result*alpha)));
|
||||
}
|
||||
}
|
||||
}
|
||||
void levels_histogram(const uint8_t *pixels, const uint8_t *coverage, size_t count, double *bins) {
|
||||
for (size_t i=0; i<count; ++i) {
|
||||
const uint8_t *p = pixels+i*4;
|
||||
if (!p[3]) continue;
|
||||
double weight = p[3]/255.0 * (coverage ? coverage[i]/255.0 : 1);
|
||||
for (int channel=0; channel<3; ++channel) {
|
||||
int value = (int)fmin(255, round(p[channel]*255.0/p[3]));
|
||||
bins[(channel+1)*256+value] += weight;
|
||||
bins[value] += weight/3.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
void levels_apply(uint8_t *pixels, size_t count, const float *tables);
|
||||
void levels_histogram(const uint8_t *pixels, const uint8_t *coverage, size_t count, double *bins);
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "NoisePixels.h"
|
||||
#include <math.h>
|
||||
|
||||
// A well-mixed 32-bit hash, so neighbouring pixels get unrelated values.
|
||||
static inline uint32_t noise_hash(uint32_t x) {
|
||||
x ^= x >> 16; x *= 0x7feb352dU;
|
||||
x ^= x >> 15; x *= 0x846ca68bU;
|
||||
x ^= x >> 16;
|
||||
return x;
|
||||
}
|
||||
|
||||
// Uniform in [0, 1).
|
||||
static inline float noise_unit(uint32_t key) { return (float)(noise_hash(key) >> 8) * (1.0f / 16777216.0f); }
|
||||
|
||||
void noise_add(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
float amount, int gaussian, int monochromatic, uint32_t seed) {
|
||||
float spread = amount / 100.0f * 127.5f;
|
||||
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;
|
||||
unsigned alpha = p[3];
|
||||
if (!alpha) continue;
|
||||
uint32_t base = noise_hash(seed ^ noise_hash((uint32_t)(y * width + x)));
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
uint32_t key = monochromatic ? base : base + (uint32_t)c * 0x9e3779b9U;
|
||||
float n;
|
||||
if (gaussian) {
|
||||
// Box–Muller: two uniform values make one normally distributed one.
|
||||
float u1 = noise_unit(key), u2 = noise_unit(key ^ 0x68e31da4U);
|
||||
n = sqrtf(-2.0f * logf(1.0f - u1)) * cosf(6.2831853f * u2) * spread * (2.0f / 3.0f);
|
||||
} else {
|
||||
n = (noise_unit(key) * 2.0f - 1.0f) * spread;
|
||||
}
|
||||
float value = (float)p[c] * 255.0f / (float)alpha + n;
|
||||
value = value < 0 ? 0 : value > 255 ? 255 : value;
|
||||
p[c] = (uint8_t)lroundf(value * (float)alpha / 255.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef NoisePixels_h
|
||||
#define NoisePixels_h
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
// Adds noise to the color of premultiplied RGBA pixels (4 bytes per pixel, `stride` bytes per
|
||||
// row), leaving alpha untouched and fully transparent pixels alone. `amount` is Photoshop's
|
||||
// percentage: uniform noise spans ±amount% of half the range, Gaussian noise has a standard
|
||||
// deviation of two thirds of that. Monochromatic adds the same value to all three channels.
|
||||
// Each pixel's noise depends only on its position and `seed`, so the same seed gives the same grain.
|
||||
void noise_add(uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
float amount, int gaussian, int monochromatic, uint32_t seed);
|
||||
#endif
|
||||
@@ -0,0 +1,174 @@
|
||||
#include "WandPixels.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enum { EAST = 1, SOUTH = 2, WEST = 4, NORTH = 8 };
|
||||
// Outlines with more pixel edges than this are refused: the path would be too slow to draw.
|
||||
static const size_t wand_edge_limit = 8000000;
|
||||
|
||||
static inline int wand_matches(const uint8_t *p, const int reference[4], int tolerance) {
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
int d = (int)p[c] - reference[c];
|
||||
if (d < -tolerance || d > tolerance) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
long wand_mask(const uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
size_t seedX, size_t seedY, size_t radius, int tolerance, int contiguous, uint8_t *mask) {
|
||||
if (!width || !height) return 0;
|
||||
memset(mask, 0, width * height);
|
||||
if (seedX >= width || seedY >= height) return 0;
|
||||
size_t x0 = seedX > radius ? seedX - radius : 0, x1 = seedX + radius < width ? seedX + radius : width - 1;
|
||||
size_t y0 = seedY > radius ? seedY - radius : 0, y1 = seedY + radius < height ? seedY + radius : height - 1;
|
||||
unsigned long sums[4] = {0, 0, 0, 0}, samples = 0;
|
||||
for (size_t y = y0; y <= y1; ++y)
|
||||
for (size_t x = x0; x <= x1; ++x, ++samples)
|
||||
for (int c = 0; c < 4; ++c) sums[c] += rgba[y * stride + x * 4 + c];
|
||||
int reference[4];
|
||||
for (int c = 0; c < 4; ++c) reference[c] = (int)((sums[c] + samples / 2) / samples);
|
||||
|
||||
long count = 0;
|
||||
if (!contiguous) {
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
const uint8_t *row = rgba + y * stride;
|
||||
uint8_t *out = mask + y * width;
|
||||
for (size_t x = 0; x < width; ++x)
|
||||
if (wand_matches(row + x * 4, reference, tolerance)) { out[x] = 255; ++count; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Scanline flood fill: each popped seed fills its whole horizontal run, then pushes one
|
||||
// seed per matching run in the rows directly above and below it.
|
||||
size_t capacity = 4096, top = 1;
|
||||
size_t *stack = malloc(capacity * 2 * sizeof(size_t));
|
||||
if (!stack) return -1;
|
||||
stack[0] = seedX;
|
||||
stack[1] = seedY;
|
||||
while (top) {
|
||||
--top;
|
||||
size_t x = stack[top * 2], y = stack[top * 2 + 1];
|
||||
const uint8_t *row = rgba + y * stride;
|
||||
uint8_t *out = mask + y * width;
|
||||
if (out[x] || !wand_matches(row + x * 4, reference, tolerance)) continue;
|
||||
size_t left = x, right = x;
|
||||
while (left > 0 && !out[left - 1] && wand_matches(row + (left - 1) * 4, reference, tolerance)) --left;
|
||||
while (right + 1 < width && !out[right + 1] && wand_matches(row + (right + 1) * 4, reference, tolerance)) ++right;
|
||||
memset(out + left, 255, right - left + 1);
|
||||
count += (long)(right - left + 1);
|
||||
for (int side = 0; side < 2; ++side) {
|
||||
if (side == 0 ? y == 0 : y + 1 >= height) continue;
|
||||
size_t ny = side == 0 ? y - 1 : y + 1;
|
||||
const uint8_t *nrow = rgba + ny * stride;
|
||||
const uint8_t *nout = mask + ny * width;
|
||||
int inRun = 0;
|
||||
for (size_t nx = left; nx <= right; ++nx) {
|
||||
int candidate = !nout[nx] && wand_matches(nrow + nx * 4, reference, tolerance);
|
||||
if (candidate && !inRun) {
|
||||
if (top == capacity) {
|
||||
size_t *grown = realloc(stack, capacity * 4 * sizeof(size_t));
|
||||
if (!grown) { free(stack); return -1; }
|
||||
stack = grown;
|
||||
capacity *= 2;
|
||||
}
|
||||
stack[top * 2] = nx;
|
||||
stack[top * 2 + 1] = ny;
|
||||
++top;
|
||||
}
|
||||
inRun = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(stack);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Headings, clockwise on screen (y grows downward): east, south, west, north.
|
||||
static inline int turn_right(int d) { return d == NORTH ? EAST : d << 1; }
|
||||
static inline int turn_left(int d) { return d == EAST ? NORTH : d >> 1; }
|
||||
|
||||
int wand_trace(const uint8_t *mask, size_t width, size_t height,
|
||||
int32_t **points, size_t *pointCount, int32_t **loops, size_t *loopCount) {
|
||||
*points = NULL;
|
||||
*loops = NULL;
|
||||
*pointCount = 0;
|
||||
*loopCount = 0;
|
||||
if (!width || !height) return 0;
|
||||
if (width >= INT32_MAX || height >= INT32_MAX) return -1;
|
||||
// Each vertex of the (width + 1) × (height + 1) grid records the directed boundary edges
|
||||
// leaving it: a selected pixel's unselected sides, walked clockwise around the pixel.
|
||||
size_t stride = width + 1, vertices = stride * (height + 1), edges = 0;
|
||||
uint8_t *out = calloc(vertices, 1);
|
||||
if (!out) return -1;
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
const uint8_t *row = mask + y * width;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
if (!row[x]) continue;
|
||||
if (y == 0 || !mask[(y - 1) * width + x]) { out[y * stride + x] |= EAST; ++edges; }
|
||||
if (x + 1 == width || !row[x + 1]) { out[y * stride + x + 1] |= SOUTH; ++edges; }
|
||||
if (y + 1 == height || !mask[(y + 1) * width + x]) { out[(y + 1) * stride + x + 1] |= WEST; ++edges; }
|
||||
if (x == 0 || !row[x - 1]) { out[(y + 1) * stride + x] |= NORTH; ++edges; }
|
||||
}
|
||||
if (edges > wand_edge_limit) { free(out); return -2; }
|
||||
}
|
||||
|
||||
size_t pointCapacity = 1024, loopCapacity = 256, np = 0, nl = 0;
|
||||
int32_t *pts = malloc(pointCapacity * 2 * sizeof(int32_t));
|
||||
int32_t *lens = malloc(loopCapacity * sizeof(int32_t));
|
||||
if (!pts || !lens) goto fail;
|
||||
for (size_t start = 0; start < vertices; ++start) {
|
||||
while (out[start]) {
|
||||
size_t first = np, v = start;
|
||||
int heading = 0, initial = 0;
|
||||
do {
|
||||
int bits = out[v], d;
|
||||
// Where two loops meet at a corner, turning right keeps them apart.
|
||||
if (!heading) d = bits & -bits;
|
||||
else if (bits & turn_right(heading)) d = turn_right(heading);
|
||||
else if (bits & heading) d = heading;
|
||||
else if (bits & turn_left(heading)) d = turn_left(heading);
|
||||
else d = bits & -bits;
|
||||
if (!d) break;
|
||||
out[v] &= (uint8_t)~d;
|
||||
if (d != heading) {
|
||||
if (np == pointCapacity) {
|
||||
int32_t *grown = realloc(pts, pointCapacity * 4 * sizeof(int32_t));
|
||||
if (!grown) goto fail;
|
||||
pts = grown;
|
||||
pointCapacity *= 2;
|
||||
}
|
||||
pts[np * 2] = (int32_t)(v % stride);
|
||||
pts[np * 2 + 1] = (int32_t)(v / stride);
|
||||
++np;
|
||||
}
|
||||
if (!heading) initial = d;
|
||||
heading = d;
|
||||
v = d == EAST ? v + 1 : d == WEST ? v - 1 : d == SOUTH ? v + stride : v - stride;
|
||||
} while (v != start);
|
||||
// The start is a corner unless the loop arrives on the heading it left with.
|
||||
if (heading == initial && np > first) {
|
||||
memmove(pts + first * 2, pts + (first + 1) * 2, (np - first - 1) * 2 * sizeof(int32_t));
|
||||
--np;
|
||||
}
|
||||
if (nl == loopCapacity) {
|
||||
int32_t *grown = realloc(lens, loopCapacity * 2 * sizeof(int32_t));
|
||||
if (!grown) goto fail;
|
||||
lens = grown;
|
||||
loopCapacity *= 2;
|
||||
}
|
||||
lens[nl++] = (int32_t)(np - first);
|
||||
}
|
||||
}
|
||||
free(out);
|
||||
*points = pts;
|
||||
*loops = lens;
|
||||
*pointCount = np;
|
||||
*loopCount = nl;
|
||||
return 0;
|
||||
fail:
|
||||
free(out);
|
||||
free(pts);
|
||||
free(lens);
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef WandPixels_h
|
||||
#define WandPixels_h
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
// Magic Wand match over premultiplied RGBA (4 bytes per pixel, `stride` bytes per row, rows
|
||||
// top-down). The reference color is the average over a (2 * radius + 1)² square around the
|
||||
// seed, clipped to the image. A pixel matches when every channel, alpha included, is within
|
||||
// `tolerance` of it. Contiguous fills 4-connected from the seed (nothing when the seed itself
|
||||
// doesn't match); otherwise every matching pixel. Writes 255 for selected, 0 elsewhere, into
|
||||
// `mask` (width * height bytes). Returns the number selected, or -1 when memory runs out.
|
||||
long wand_mask(const uint8_t *rgba, size_t width, size_t height, size_t stride,
|
||||
size_t seedX, size_t seedY, size_t radius, int tolerance, int contiguous, uint8_t *mask);
|
||||
// Outline of the nonzero pixels of `mask`, along pixel edges, as closed loops of corner points
|
||||
// (x, y pairs in pixel-edge coordinates). Outer boundaries run clockwise and holes
|
||||
// counterclockwise in top-left coordinates, so the winding rule fills exactly those pixels.
|
||||
// `points` receives 2 * pointCount values and `loops` each loop's corner count; both are
|
||||
// malloc'd and must be freed. Returns 0 on success, -1 when memory runs out, and -2 when the
|
||||
// outline is too detailed to be worth drawing.
|
||||
int wand_trace(const uint8_t *mask, size_t width, size_t height,
|
||||
int32_t **points, size_t *pointCount, int32_t **loops, size_t *loopCount);
|
||||
#endif
|
||||
Reference in new issue
Block a user