-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.c
328 lines (282 loc) · 11.6 KB
/
main.c
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#include "main.h"
#include <getopt.h>
/*
* Steps in algorithm
* 1. Find the dimensions for the tiles depending on the input image
* 2. Resize the input image so that the tiles fit perfectly (i.e. cut off not-used borders)
* 3. For every image, go over all the possible places it can be located and compute it's score for that position.
* If the computed score is better than the score of the image currently in the box, replace the image
* 4. Do this for all the images
*/
int main(int argc, char **argv) {
char *collage_image = NULL; // The image creating the collage for
char *tile_directory = NULL; // The directory containing the tile image candidates
int amount_of_tiles = 0; // The amount of tiles we're trying to fit into the collage
int amount_of_tile_files = 0; // The amount of tile files we're trying to use in our collage
char *output_file_name = NULL; // The file name for the output image, if left out this will be output.jpg
int c;
while ((c = getopt(argc, argv, "i:d:t:c:o:")) != -1) {
switch (c) {
case 'i':
collage_image = (char *) malloc(sizeof(char) * strlen(optarg) + 1);
test_allocation(collage_image);
strcpy(collage_image, optarg);
collage_image[strlen(optarg)] = '\0';
break;
case 'd':
tile_directory = (char *) malloc(sizeof(char) * strlen(optarg) + 1);
test_allocation(tile_directory);
strcpy(tile_directory, optarg);
tile_directory[strlen(optarg)] = '\0';
break;
case 't':
amount_of_tiles = atoi(optarg);
if (amount_of_tiles < 1) {
print_usage();
exit(1);
}
break;
case 'c':
amount_of_tile_files = atoi(optarg);
if (amount_of_tile_files < 1) {
print_usage();
exit(1);
}
break;
case 'o':
output_file_name = (char *) malloc(sizeof(char) * strlen(optarg) + 1);
test_allocation(output_file_name);
strcpy(output_file_name, optarg);
output_file_name[strlen(output_file_name)] = '\0';
break;
default:
print_usage();
exit(1);
}
}
if (output_file_name == NULL) {
output_file_name = (char *) malloc(sizeof(char) * 11);
test_allocation(output_file_name);
strcpy(output_file_name, "output.jpg\0");
}
if (collage_image == NULL || tile_directory == NULL || amount_of_tiles < 1 || amount_of_tile_files < 1) {
print_usage();
exit(1);
}
// Create a collage structure
collage *clg = malloc(sizeof(collage));
test_allocation(clg);
// Load the original image
clg->original_img = stbi_load(collage_image, &(clg->original_width), &(clg->original_height), &(clg->channels), 0);
// Compute the size of the tiles
int total_pixels = clg->original_height * clg->original_width;
int tile_pixel_size = (int) sqrt(total_pixels / amount_of_tiles);
printf("Creating collage for file: %s - With (pixel) dimensions of: %d x %d\n",
collage_image, clg->original_width, clg->original_height);
printf("Dimensions (pixels) of a tile: %d x %d\n", tile_pixel_size, tile_pixel_size);
// Compute the amount of tiles we will try to fit in the width and height
int images_in_width = (int) (clg->original_width / tile_pixel_size);
int images_in_height = (int) (clg->original_height / tile_pixel_size);
printf("Dimensions of tiles we will fit in: %d x %d. A total of: %d\n",
images_in_width, images_in_height, images_in_height * images_in_width);
clg->amount_width = images_in_width;
clg->amount_height = images_in_height;
// The new (pixel) width and height of the collage image
clg->new_width = images_in_width * tile_pixel_size;
clg->new_height = images_in_height * tile_pixel_size;
int collage_amount_pixels = clg->new_height * clg->new_width;
clg->new_img = (unsigned char *) malloc(sizeof(char) * collage_amount_pixels * clg->channels);
test_allocation(clg->new_img);
// Also multiply with the amount of channels
// Construct the score table, which will keep the scores of the current tiles in the collage
clg->score_table = (double **) malloc(sizeof(double *) * clg->amount_height);
test_allocation(clg->score_table);
int i;
for (i = 0; i < clg->amount_height; i += 1) {
clg->score_table[i] = (double *) malloc(sizeof(double) * clg->amount_width);
test_allocation(clg->score_table[i]);
int j;
for (j = 0; j < clg->amount_width; j += 1) {
clg->score_table[i][j] = (double) -1;
}
}
char *filename = (char *) malloc(sizeof(char) * (strlen(tile_directory) + amount_of_tile_files % 10 + 4));
test_allocation(filename);
int file_count;
for (file_count = 0; file_count < amount_of_tile_files; file_count++) {
update_status_bar(file_count, amount_of_tile_files, 20);
sprintf(filename, "%s%d.jpg", tile_directory, file_count + 1);
// Get the resized (square tile with correct pixel size)
unsigned char *resized = resized_image_from_filename(filename, tile_pixel_size, clg->channels);
int row;
for (row = 0; row < clg->amount_height; row += 1) {
int col;
for (col = 0; col < clg->amount_width; col += 1) {
// Compute the score for the current image and the (row, col) coordinates
double score = score_tile_euclidean(clg, resized, col, row, tile_pixel_size);
// If tile is empty, or new image has better (lower) score
if (clg->score_table[row][col] == -1 || clg->score_table[row][col] > score) {
copy_tile_to_image(clg, resized, tile_pixel_size, row, col);
// If we have changed the image, we should adapt score accordingly
clg->score_table[row][col] = score;
}
}
}
}
free(filename);
// Write the collage image
stbi_write_jpg(output_file_name, clg->new_width, clg->new_height, clg->channels, clg->new_img, 100);
free(collage_image);
free(tile_directory);
free(output_file_name);
for (i = 0; i < clg->amount_height; i += 1) {
free(clg->score_table[i]);
}
free(clg->score_table);
stbi_image_free(clg->original_img);
stbi_image_free(clg->new_img);
// AAAAAAAAAAAAAND, We're done, enjoy!
return 0;
}
/**
* Compute the minimum of two integers
* @param a
* @param b
* @return The minimum of the two
*/
int minimum(int a, int b) {
if (a <= b) {
return a;
} else {
return b;
}
}
/**
* Given the filename, open and resize the image
* @param filename The filename of the image
* @param resize_size The (pixel) size of the width and the height of the resized image
* @param collage_channels The amount of channels of the collage image
* @return The resized (tile) image
*/
unsigned char *resized_image_from_filename(char *filename, int resize_size, int collage_channels) {
int width;
int height;
int channels;
unsigned char *image = stbi_load(filename, &width, &height, &channels, 0);
// Make the image a square, so the resizing won't warp the image
int min_size = minimum(width, height);
unsigned char *square_image = malloc(sizeof(unsigned char) * min_size * min_size * channels);
test_allocation(square_image);
int r;
for (r = 0; r < min_size; r += 1) {
int c;
for (c = 0; c < min_size; c += 1) {
int ch;
for (ch = 0; ch < channels; ch += 1) {
square_image[r * min_size * channels + c * channels + ch] = image[r * width * channels + c * channels + ch];
}
}
}
// Resize the square image to the needed resolution
unsigned char *resized = malloc(sizeof(unsigned char) * resize_size * resize_size * channels);
test_allocation(resized);
stbir_resize_uint8(square_image, min_size, min_size, 0, resized, resize_size, resize_size, 0, channels);
stbi_image_free(image);
stbi_image_free(square_image);
return resized;
}
/**
* Compute the score for a tile in the collage depending on the original image, using the euclidean distance
* @param collage The collage structure
* @param tile_image The tile we want to compute the score for
* @param col The col we want to place the tile in the grid
* @param row The row " " " " " " " " "
* @param tile_size The (pixel) size of the tile
* @return
*/
double score_tile_euclidean(collage *collage, unsigned char *tile_image, int col, int row, int tile_size) {
double score = 0;
int i;
for (i = 0; i < tile_size; i += 1) {
int j;
for (j = 0; j < tile_size; j += 1) {
int index_original = row * tile_size * collage->original_width * collage->channels +
col * tile_size * collage->channels;
int index_tile = i * tile_size * collage->channels + j * collage->channels;
// Compute the euclidean distance between the two images
score += pow(collage->original_img[index_original] - tile_image[index_tile], 2) +
pow(collage->original_img[index_original + 1] - tile_image[index_tile + 1], 2) +
pow(collage->original_img[index_original + 2] - tile_image[index_tile + 2], 2);
}
}
// the lower the score, the similar the images are
return score;
}
/**
* Test if the allocation succeeded. If not, fail and exit.
* @param el The element we want to test
*/
void test_allocation(void *el) {
if (el == NULL) {
printf("Allocation failed.");
exit(1);
}
}
/**
* Print the usage.
*/
void print_usage() {
printf("USAGE: ./Collage -i [path_to_image] -d [path_to_tile_directory] -t [amount_of_tiles] -c [amount_of_tile_files] -o [output_name]\n");
printf(" [path_to_image]: path to the (jpg) image you want to create a collage for\n");
printf(" [path_to_tile_directory]: path to the directory that holds all the images that can be used as tiles for the collage\n");
printf(" [amount_of_tiles]: the amount of tiles the program will **try** to fit in, this will vary depending on the input image\n");
printf(" [amount_of_tile_files]: the amount of files the program should try from the tile_directory; note that all the files should be numbered incrementally\n");
printf(" [output_name]: the name you want to give to your output file; if left blank output.jpg will be used\n");
}
/**
* Copy the tile to the row and col in the collage
* @param clg The collage structure
* @param image The tile image
* @param tile_pixel_size The pixel size of the tile
* @param col Column
* @param row Row
*/
void copy_tile_to_image(collage *clg, unsigned char *image, int tile_pixel_size, int row, int col) {
int rowindex;
for (rowindex = 0; rowindex < tile_pixel_size; rowindex += 1) {
int colindex;
for (colindex = 0; colindex < tile_pixel_size; colindex += 1) {
int channel;
for (channel = 0; channel < clg->channels; channel += 1) {
int index = (row * tile_pixel_size) * clg->new_width * clg->channels +
(col * tile_pixel_size) * clg->channels +
rowindex * clg->new_width * clg->channels + colindex * clg->channels;
clg->new_img[index + channel] =
image[rowindex * tile_pixel_size * clg->channels + colindex * clg->channels + channel];
}
}
}
}
/**
* Little function that prints a status bar instead of printing 10203123123 lines with the number...
* @param index The current index we're working on
* @param total The total of tries
* @param length_bar The length we want the bar to have
*/
void update_status_bar(int index, int total, int length_bar) {
if (index > 0) {
printf("\r"); // Little hack to remove the last printed line
}
printf("Progress: [");
int amount_bars = (int) total / length_bar;
int bars = (int) index / amount_bars;
int i;
for (i = 0; i < length_bar; i++) {
if (i <= bars) {
printf("\u2588");
} else {
printf(" ");
}
}
printf("] %.2f%%", ((float) index / (float) total) * 100.0);
}