summaryrefslogtreecommitdiff
path: root/jrrtilevq-cli.c
blob: 3f46cc6f2944df21bec0e2b81bcca8e8ca2c544a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/* Standard headers that do not require the C runtime */
#include <stddef.h>
#include <limits.h>
#include <float.h>
#include <stdarg.h>
#include <stdint.h>       // C99
#include <stdbool.h>      // C99
//#include <iso646.h>       // don't use this
//#include <stdalign.h>     // C11
//#include <stdnoreturn.h>  // C11

const char *PROGRAM_NAME = "jrrtilevq";
const char *USAGE_TEXT =
    "jrrtilevq - combines similar tiles in PNG file.\n"
    "\n"
    "Usage:\n"
    "  jrrtilevq -t 8x8 -n 256 INPUT.png [OUTPUT.png]\n"
    "\n"
    "Options:\n"
    "  -h        show this help message and exit\n"
    "  -t NxN    The width and height of tiles (default 8x8)\n"
    "  -n N      reduce image to at least this many tiles (default 256)\n"
    "  -f        Match tiles by flipping horizontally and vertically\n"
;

#define XXH_INLINE_ALL
#include "xxhash.h"
#define JRRTILEVQ_HASH_FUNCTION XXH3_64bits
#define JRRTILEVQ_IMPLEMENTATION
#include "jrrtilevq.h"

#include "lodepng.h"

#include <stdio.h>
#include <stdlib.h>

// Reads files without fseek and ftell capabilities (stdin, named pipes)
// this does so by using realloc a bunch of times.
// returns 0 and does *not* set data_ptr if error or zero sized file.
size_t read_whole_file_without_fseek(uint8_t **data_ptr, FILE *f)
{
	uint8_t *data = NULL;
	size_t length = 0;
	uint8_t *realloc_data = NULL;
	// I want a slower exponential growth then something like a simple
	// "capacity *= 2", so I choose the fibonacci sequence (growth rate of about 1.6).
	// 4KiB (common memory page size) times the 6th fibonacci number is 32kiB.
	size_t capacity = 32768;
	size_t previous_capacity = 20480;

	if (!data_ptr || !f)
		return 0;

	data = malloc(capacity);
	if (!data)
		return 0;

	while (!feof(f)) {
		length += fread(data + length, sizeof(uint8_t), capacity - length, f);
		if (ferror(f))
			goto free_and_return_empty;

		if (length == capacity) {
			size_t n = capacity;
			capacity = capacity + previous_capacity;
			previous_capacity = n;
			realloc_data = realloc(data, capacity);
			if (!realloc_data)
				goto free_and_return_empty;
			data = realloc_data;
		}
	}
	if (!length)
		goto free_and_return_empty;
	// shrink buffer to fit the final size
	realloc_data = realloc(data, length);
	if (!realloc_data)
		goto free_and_return_empty;
	data = realloc_data;

	*data_ptr = data;
	return length;
free_and_return_empty:
	free(data);
	return 0;
}

// returns 0 and does *not* set data_ptr if error or zero sized file.
size_t read_whole_file(uint8_t **data_ptr, FILE *f)
{
	if (!data_ptr || !f)
		return 0;

	if (fseek(f, 0, SEEK_END) != 0) {
		// If fseek fails the file cursor is still at the beginning
		// so no need for rewind(f)
		return read_whole_file_without_fseek(data_ptr, f);
	}

	// fseek works, so now do it the simple way
	size_t length = ftell(f);
	if (length <= 0)
		return 0;
	uint8_t *data = malloc(length);
	if (!data)
		return 0;
	rewind(f);
	size_t read_length = fread(data, sizeof(uint8_t), length, f);
	if (read_length != length) {
		free(data);
		return 0;
	}

	*data_ptr = data;
	return length;
}


int main (int argc, char *argv[])
{
	char* input_filename = NULL;
	char* output_filename = NULL;
	uint8_t* image_buf;
	unsigned image_width;
	unsigned image_height;

	unsigned transforms = 1;
	unsigned max_number_of_tiles = 256;
	unsigned tile_width = 8;
	unsigned tile_height = 8;

	int argn;
	unsigned parsed_number;
	char *strtol_ptr;
	char *strtol_ptr_result;
	for (argn = 1; argn < argc; ++argn) {
		if (argv[argn][0] != '-') {
			break; //assume the rest of arguments are the input and output filenames
		}
		switch (argv[argn][1]) {
			case 'h':
				fputs(USAGE_TEXT, stdout);
				return 0;
			break; case 'f':
				transforms = 0x0f;
			break; case 'n':
				strtol_ptr = argv[argn]+2;
				if ((*strtol_ptr == '\0') && (argn+1 < argc)) {
					++argn;
					strtol_ptr = argv[argn];
				}
				parsed_number = strtoul(strtol_ptr, &strtol_ptr_result, 0);
				if (strtol_ptr == strtol_ptr_result) {
					fprintf(stderr, "Failed to parse the -n argument\n");
					return 1;
				}
				max_number_of_tiles = parsed_number;
			break; case 't':
				strtol_ptr = argv[argn]+2;
				if ((*strtol_ptr == '\0') && (argn+1 < argc)) {
					++argn;
					strtol_ptr = argv[argn];
				}
				parsed_number = strtoul(strtol_ptr, &strtol_ptr_result, 0);
				if (strtol_ptr == strtol_ptr_result) {
					fprintf(stderr, "Failed to parse the width of the -t argument\n");
					return 1;
				}
				tile_width = parsed_number;
				strtol_ptr = strtol_ptr_result+1;
				parsed_number = strtoul(strtol_ptr, &strtol_ptr_result, 0);
				if (strtol_ptr == strtol_ptr_result) {
					fprintf(stderr, "Failed to parse the height of the -t argument\n");
					return 1;
				}
				tile_height = parsed_number;
			break; default:
				fprintf(stderr, "Unreconized argument, sorry\n");
				return 1;
		}
	}
	size_t input_name_size;
	char buf[FILENAME_MAX];
	switch (argc - argn) {
		case 0:
			fputs(USAGE_TEXT, stdout);
			return 0;
		break; case 1:
			input_filename = argv[argn];
			input_name_size = strlen(input_filename);
			strncpy(buf, argv[argn], FILENAME_MAX-1);
			buf[input_name_size-4] = '\0';  // remove '.png'
			strncat(buf, ".out.png", FILENAME_MAX-1);
			output_filename = buf;
		break; default:
			input_filename = argv[argn];
			output_filename = argv[argn+1];
	}

	//printf("tile_width: %d, tile_height: %d, max_number_of_tiles: %d, transforms: %d, input_filename: \"%s\", output_filename: \"%s\", number of non option args: %d\n", tile_width, tile_height, max_number_of_tiles, transforms, input_filename, output_filename, argc - argn);

	unsigned lodepng_error;

	lodepng_error = lodepng_decode32_file(&image_buf, &image_width, &image_height, input_filename);
	if (lodepng_error) {
		fprintf(stderr, "%s: LodePNG error %u: %s\n", input_filename, lodepng_error, lodepng_error_text(lodepng_error));
		return 1;
	}

	jrrtilevq_pad_image(&image_buf, &image_width, &image_height, tile_width, tile_height, 0, 0);

	jrrtilevq(image_buf, image_width, image_height, tile_width, tile_height, max_number_of_tiles, transforms);

	lodepng_error = lodepng_encode32_file(output_filename, image_buf, image_width, image_height);
	if (lodepng_error) {
		fprintf(stderr, "%s: LodePNG error %u: %s\n", output_filename, lodepng_error, lodepng_error_text(lodepng_error));
		return 1;
	}

	free(image_buf);
	return 0;
}