diff --git a/resynth.c b/resynth.c
index 3d3c530..f14f321 100644
--- a/resynth.c
+++ b/resynth.c
@@ -11,15 +11,17 @@
If not, visit to obtain one.
*/
-#include
-#include
-#include
-#include
+#include // for argument parsing with strtol (kyaa.h)
+#include // for log (neglog_cauchy) used in pixel diff. calculations
+#include // we're targetting C11 anyway, may as well use it
+#include // for uint8_t for pixel data
#include
#include
-#include
-#include
+#include // for file extension mangling
+#include // for time(0) as a random seed (srand)
+// decide which features we want from stb_image.
+// this should cover the most common formats.
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#define STBI_ONLY_JPEG
@@ -28,31 +30,51 @@
#define STBI_ONLY_GIF
#include "stb_image.h"
+// likewise for stb_image_write. by using the static keyword,
+// any unused formats and functions can be stripped from the resulting binary.
+// we only use png (stbi_write_png) in this case.
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#include "stb_image_write.h"
+// this isn't the prettiest way of handling memory errors,
+// but it should suffice for our one-thing one-shot program.
#define STRETCHY_BUFFER_OUT_OF_MEMORY \
fprintf(stderr, "fatal error: ran out of memory in stb__sbgrowf\n"); \
exit(1);
+// provides vector<>-like arrays of variable size.
#include "stretchy_buffer.h"
+// for command-line argument parsing
#include "kyaa.h"
+// convenience macros. hopefully these names don't interfere
+// with any defined in the standard library headers on any system.
+// it's technically not an error to redefine macros anyway.
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CLAMP(x, l, u) (MIN(MAX(x, l), u))
#define CLAMPV(x, l, u) x = CLAMP(x, l, u)
#define LEN(a) (sizeof(a) / sizeof((a)[0]))
+// this macro lets us state how much memory we want a pointer to employ,
+// while freeing its old memory as needed.
+// note that this expects the given variable to be initialized to NULL
+// for its first allocation; using an uninitialized pointer is erroneous.
+// (this should probably be renamed to something more meaningful)
#define MEMORY(a, size) \
do { \
if (a) (a) = (free(a), NULL); \
if (size > 0) (a) = (typeof(a))(calloc(size, sizeof((a)[0]))); \
} while (0) \
+// a simple extension to stretchy_buffer.h:
+// a macro that frees and nulls a pointer at the same time,
+// which should be safer and easier to program with.
#define sb_freeset(a) ((a) = (sb_free(a), NULL))
+// in some cases, we must declare functions as static to prevent
+// their symbols being exported, thus truly allowing them to be inlined.
#define INLINE static inline
// end of generic boilerplate, here's the actual program:
@@ -93,6 +115,8 @@ typedef struct {
int width, height, depth;
} Image;
+// convenience macros to simplify image handling.
+// note that these secretly expect an image##_array variable to exist.
#define IMAGE_RESIZE(image, w, h, d) \
do { \
image.width = w; \
@@ -113,6 +137,8 @@ typedef struct {
INLINE bool wrap_or_clip(const Parameters parameters, const Image image,
Coord *point) {
+ // like modulo, with optional bounds checking.
+ // (perhaps we should just use modulo if h_tile and v_tile are both true?)
while (point->x < 0) {
if (parameters.h_tile) point->x += image.width;
else return false;
@@ -134,6 +160,8 @@ INLINE bool wrap_or_clip(const Parameters parameters, const Image image,
typedef struct {
int input_bytes;
+ // note that these variables must exist alongside their "_array"s
+ // for the image macros to work.
Image data, corpus, status;
Pixel *data_array, *corpus_array;
Status *status_array;
@@ -146,7 +174,7 @@ typedef struct {
Status **neighbor_statuses;
int n_neighbors;
- int *diff_table; // uint16_t ?
+ int *diff_table; // (might be more efficient to store as uint16_t?)
int best;
Coord best_point;
@@ -168,6 +196,8 @@ static double neglog_cauchy(double x) {
}
static void make_offset_list(Resynth_state *s) {
+ // generate a vector of x,y offsets used to search around any given pixel.
+ // this is constrained by the minimum image size to prevent overlapping.
int width = MIN(s->corpus.width, s->data.width);
int height = MIN(s->corpus.height, s->data.height);
@@ -179,11 +209,13 @@ static void make_offset_list(Resynth_state *s) {
}
}
+ // TODO: describe how/why this is sorted
qsort(s->sorted_offsets, sb_count(s->sorted_offsets),
sizeof(Coord), coord_compare);
}
INLINE void try_point(Resynth_state *s, const Coord point) {
+ // consider a pixel and its neighbors as candidates for the best-fit.
int sum = 0;
for (int i = 0; i < s->n_neighbors; i++) {
@@ -207,10 +239,12 @@ INLINE void try_point(Resynth_state *s, const Coord point) {
}
static void run(Resynth_state *s, Parameters parameters) {
+ // "resynthesize" an output image from a given input image.
sb_freeset(s->data_points);
sb_freeset(s->corpus_points);
sb_freeset(s->sorted_offsets);
+ // (iirc i opted to put diff_table on heap to keep Resynth_state small)
MEMORY(s->diff_table, 512);
MEMORY(s->neighbors, parameters.neighbors);
MEMORY(s->neighbor_values, parameters.neighbors);
@@ -218,15 +252,19 @@ static void run(Resynth_state *s, Parameters parameters) {
IMAGE_RESIZE(s->status, s->data.width, s->data.height, 1);
+ // set default values and allocate points to shuffle later.
for (int y = 0; y < s->status.height; y++) {
for (int x = 0; x < s->status.width; x++) {
+ // (this might be redundant since this memory is calloc'd)
image_at(s->status, x, y)->has_source = false;
image_at(s->status, x, y)->has_value = false;
+
Coord coord = {x, y};
sb_push(s->data_points, coord);
}
}
+ // likewise for the corpus.
for (int y = 0; y < s->corpus.height; y++) {
for (int x = 0; x < s->corpus.width; x++) {
Coord coord = {x, y};
@@ -243,6 +281,11 @@ static void run(Resynth_state *s, Parameters parameters) {
make_offset_list(s);
+ // precompute how "different" a pixel value is from another.
+ // this greatly affects how apparent any seams are in the synthesized image.
+ // this is done per 8-bit channel, so only 256 * 2 values are needed.
+ // since we can't use negative indices, we pretend index 256 is 0 instead.
+ // (you could try adding CIELAB heuristics, but this seems robust enough)
if (parameters.autism > 0) for (int i = -256; i < 256; i++) {
double value = neglog_cauchy(i / 256.0 / parameters.autism) /
neglog_cauchy(1.0 / parameters.autism) * 65536.0;
@@ -251,17 +294,22 @@ static void run(Resynth_state *s, Parameters parameters) {
s->diff_table[256 + i] = (int)(i != 0) * 65536;
}
- int data_area = sb_count(s->data_points);
+ const int data_area = sb_count(s->data_points);
for (int p = 0; p < parameters.polish + p; p++) {
for (int i = 0; i < data_area; i++) {
- // shuffle
+ // shuffle in-place
int j = rand() % data_area;
Coord temp = s->data_points[i];
s->data_points[i] = s->data_points[j];
s->data_points[j] = temp;
}
+ // polishing improves pixels chosen early in the algorithm
+ // by reconsidering them after the output image has been filled.
+ // this greatly reduces the "sparklies" in the resulting image.
+ // this is achieved by appending the first n data points to the end.
+ // n is reduced exponentially by "magic" until it's less than 1.
if (parameters.magic) for (int n = data_area; n > 0;) {
n = n * parameters.magic / 256;
for (int i = 0; i < n; i++) {
@@ -270,18 +318,23 @@ static void run(Resynth_state *s, Parameters parameters) {
}
}
+ // prepare an array of neighbors we've already computed the difference of.
+ // this is a simple optimization and isn't critical to the algorithm.
+ // (tried_array is referred to implicitly by macros)
IMAGE_RESIZE(s->tried, s->corpus.width, s->corpus.height, 1);
- int corpus_area = s->corpus.width * s->corpus.height;
+ const int corpus_area = s->corpus.width * s->corpus.height;
for (int i = 0; i < corpus_area; i++) s->tried_array[i] = -1;
- // finally, do it
+ // finally, resynthesize.
for (int i = sb_count(s->data_points) - 1; i >= 0; i--) {
Coord position = s->data_points[i];
- // this point will always have a value by the end of this iteration
+ // this point is guaranteed to have a value after this iteration.
image_atc(s->status, position)->has_value = true;
+ // collect neighboring pixels as candidates for best-fit.
+ // the order we check and collect is relevant, thus "sorted_offsets".
s->n_neighbors = 0;
const int sorted_offsets_size = sb_count(s->sorted_offsets);
for (int j = 0; j < sorted_offsets_size; j++) {
@@ -301,8 +354,10 @@ static void run(Resynth_state *s, Parameters parameters) {
}
}
+ // (this macro might not exist on any compiler that isn't gcc or clang)
s->best = __INT_MAX__;
+ // consider each neighboring pixel collected as a best-fit.
for (int j = 0; j < s->n_neighbors && s->best != 0; j++) {
if (s->neighbor_statuses[j]->has_source) {
Coord point = coord_sub(s->neighbor_statuses[j]->source,
@@ -311,16 +366,22 @@ static void run(Resynth_state *s, Parameters parameters) {
point.x >= s->corpus.width || point.y >= s->corpus.height) {
continue;
}
+ // skip computing differences of points
+ // we've already done this iteration. not mandatory.
if (*image_atc(s->tried, point) == i) continue;
try_point(s, point);
*image_atc(s->tried, point) = i;
}
}
+ // try some random points in the corpus. this is required for
+ // choosing the first couple pixels, since they have no neighbors.
+ // after that, this step is optional. it can improve subjective quality.
for (int j = 0; j < parameters.tries && s->best != 0; j++) {
try_point(s, s->corpus_points[rand() % sb_count(s->corpus_points)]);
}
+ // finally, copy the best pixel to the output image.
for (int j = 0; j < s->input_bytes; j++) {
image_atc(s->data, position)[j] =
image_atc(s->corpus, s->best_point)[j];
@@ -334,8 +395,8 @@ static char *manipulate_filename(const char *fn,
const char *new_extension) {
#define MAX_LENGTH 256
int length = strlen(fn);
- if (length > MAX_LENGTH)
- length = MAX_LENGTH;
+ if (length > MAX_LENGTH) length = MAX_LENGTH;
+ // out_fn must be freed by the caller.
char *out_fn = (char *)calloc(2 * MAX_LENGTH, 1);
strncpy(out_fn, fn, length);
@@ -369,11 +430,12 @@ static const int disc00[] = {
int main(int argc, char *argv[]) {
Resynth_state state = {0};
- Resynth_state *s = &state; // just for consistency
+ Resynth_state *s = &state; // (just for consistency across functions)
Parameters parameters = {0};
parameters.v_tile = true;
parameters.h_tile = true;
+ // blah = our default; // original resynthizer default
parameters.magic = 192; // 192 (3/4)
parameters.autism = 32. / 256.; // 30. / 256.
parameters.neighbors = 29; // 30
@@ -383,6 +445,7 @@ int main(int argc, char *argv[]) {
int scale = 1;
unsigned long seed = 0;
+ // our main() return value. subtracted by one for each failed image.
int ret = 0;
KYAA_LOOP {