Simple and fast tool to add a fancy blurred letterbox effect to images.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <jpeglib.h>
  5. #include <omp.h>
  6. static int blur_radius = 15;
  7. static int blur_times = 5;
  8. static void blur_h(
  9. unsigned char *dest, unsigned char *src,
  10. int stride, int width, int height, int radius) {
  11. double coeff = 1.0 / (radius * 2 + 1);
  12. #pragma omp parallel for
  13. for (int i = 0; i < height; ++i) {
  14. int iwidth = i * stride * 3;
  15. double r_acc = 0.0;
  16. double g_acc = 0.0;
  17. double b_acc = 0.0;
  18. for (int j = -radius; j < width; ++j) {
  19. if (j - radius - 1 >= 0) {
  20. int index = iwidth + (j - radius - 1) * 3;
  21. r_acc -= coeff * src[index + 0];
  22. g_acc -= coeff * src[index + 1];
  23. b_acc -= coeff * src[index + 2];
  24. }
  25. if (j + radius < width) {
  26. int index = iwidth + (j + radius) * 3;
  27. r_acc += coeff * src[index + 0];
  28. g_acc += coeff * src[index + 1];
  29. b_acc += coeff * src[index + 2];
  30. }
  31. if (j < 0)
  32. continue;
  33. int index = iwidth + j * 3;
  34. dest[index + 0] = r_acc + 0.5;
  35. dest[index + 1] = g_acc + 0.5;
  36. dest[index + 2] = b_acc + 0.5;
  37. }
  38. }
  39. }
  40. static void blur_v(
  41. unsigned char *dest, unsigned char *src,
  42. int stride, int width, int height, int radius) {
  43. double coeff = 1.0 / (radius * 2 + 1);
  44. #pragma omp parallel for
  45. for (int j = 0; j < width; ++j) {
  46. double r_acc = 0.0;
  47. double g_acc = 0.0;
  48. double b_acc = 0.0;
  49. for (int i = -radius; i < height; ++i) {
  50. if (i - radius - 1 >= 0) {
  51. int index = (i - radius - 1) * stride * 3 + j * 3;
  52. r_acc -= coeff * src[index + 0];
  53. g_acc -= coeff * src[index + 1];
  54. b_acc -= coeff * src[index + 2];
  55. }
  56. if (i + radius < height) {
  57. int index = (i + radius) * stride * 3 + j * 3;
  58. r_acc += coeff * src[index + 0];
  59. g_acc += coeff * src[index + 1];
  60. b_acc += coeff * src[index + 2];
  61. }
  62. if (i < 0)
  63. continue;
  64. int index = i * stride * 3 + j * 3;
  65. dest[index + 0] = r_acc + 0.5;
  66. dest[index + 1] = g_acc + 0.5;
  67. dest[index + 2] = b_acc + 0.5;
  68. }
  69. }
  70. }
  71. static void blur_once(
  72. unsigned char *dest, unsigned char *src, unsigned char *scratch,
  73. int stride, int width, int height, int radius) {
  74. blur_h(scratch, src, stride, width, height, radius);
  75. blur_v(dest, scratch, stride, width, height, radius);
  76. }
  77. void blur_rect(
  78. unsigned char *image, unsigned char *scratch, unsigned char *scratch2,
  79. int stride, int x, int y, int w, int h, int radius, int times) {
  80. if (w == 0 || h == 0)
  81. return;
  82. unsigned char *orig = image;
  83. int idx = y * stride * 3 + x * 3;
  84. for (int i = 0; i < times; ++i) {
  85. blur_once(scratch + idx, image + idx, scratch2, stride, w, h, radius);
  86. if (i != times - 1) {
  87. unsigned char *tmp = image;
  88. image = scratch;
  89. scratch = tmp;
  90. }
  91. }
  92. if (image != orig)
  93. memcpy(scratch + idx, image + idx, stride * h * 3);
  94. }
  95. unsigned char *process_image(
  96. unsigned char *input_image, int input_width, int input_height,
  97. int desired_width, int desired_height) {
  98. unsigned char *output_image = calloc(3, desired_width * desired_height);
  99. double scale_w = (double)desired_width / (double)input_width;
  100. double scale_h = (double)desired_height / (double)input_height;
  101. double scale;
  102. int rect_x, rect_y, rect_w, rect_h;
  103. if (scale_w < scale_h)
  104. scale = scale_w;
  105. else
  106. scale = scale_h;
  107. rect_w = input_width * scale;
  108. rect_h = input_height * scale;
  109. rect_x = (desired_width - rect_w) / 2.0;
  110. rect_y = (desired_height - rect_h) / 2.0;
  111. // Write main image
  112. for (int y = 0; y < rect_h; ++y) {
  113. for (int x = 0; x < rect_w; ++x) {
  114. int input_y = y / scale;
  115. int input_x = x / scale;
  116. int input_idx = input_y * input_width * 3 + input_x * 3;
  117. int output_idx = (y + rect_y) * desired_width * 3 + (x + rect_x) * 3;
  118. output_image[output_idx + 0] = input_image[input_idx + 0];
  119. output_image[output_idx + 1] = input_image[input_idx + 1];
  120. output_image[output_idx + 2] = input_image[input_idx + 2];
  121. }
  122. }
  123. // Top/bottom
  124. for (int y = 0; y < rect_y; ++y) {
  125. for (int x = 0; x < rect_w; ++x) {
  126. int input_idx = (y + rect_y) * desired_width * 3 + (x + rect_x) * 3;
  127. int output_idx = y * desired_width * 3 + (x + rect_x) * 3;
  128. output_image[output_idx + 0] = output_image[input_idx + 0];
  129. output_image[output_idx + 1] = output_image[input_idx + 1];
  130. output_image[output_idx + 2] = output_image[input_idx + 2];
  131. input_idx = (y + rect_h) * desired_width * 3 + (x + rect_x) * 3;
  132. output_idx = (y + rect_h + rect_y) * desired_width * 3 + (x + rect_x) * 3;
  133. output_image[output_idx + 0] = output_image[input_idx + 0];
  134. output_image[output_idx + 1] = output_image[input_idx + 1];
  135. output_image[output_idx + 2] = output_image[input_idx + 2];
  136. }
  137. }
  138. // Left/right
  139. for (int x = 0; x < rect_x; ++x) {
  140. for (int y = 0; y < rect_h; ++y) {
  141. int input_idx_l = (y + rect_y) * desired_width * 3 + (x + rect_x) * 3;
  142. int output_idx_l = y * desired_width * 3 + (x) * 3;
  143. int input_idx_r = (y + rect_y) * desired_width * 3 + (x + rect_w) * 3;
  144. int output_idx_r = y * desired_width * 3 + (x + rect_w + rect_x) * 3;
  145. output_image[output_idx_l + 0] = output_image[input_idx_l + 0];
  146. output_image[output_idx_l + 1] = output_image[input_idx_l + 1];
  147. output_image[output_idx_l + 2] = output_image[input_idx_l + 2];
  148. output_image[output_idx_r + 0] = output_image[input_idx_r + 0];
  149. output_image[output_idx_r + 1] = output_image[input_idx_r + 1];
  150. output_image[output_idx_r + 2] = output_image[input_idx_r + 2];
  151. }
  152. }
  153. // Blur
  154. unsigned char *scratch = calloc(3, desired_width * desired_height);
  155. unsigned char *scratch2 = calloc(3, desired_width * desired_height);
  156. blur_rect(
  157. output_image, scratch, scratch2, desired_width,
  158. 0, 0, desired_width, rect_y, blur_radius, blur_times);
  159. blur_rect(
  160. output_image, scratch, scratch2, desired_width,
  161. 0, rect_y + rect_h, desired_width, rect_y, blur_radius, blur_times);
  162. blur_rect(
  163. output_image, scratch, scratch2, desired_width,
  164. 0, 0, rect_x, desired_height, blur_radius, blur_times);
  165. blur_rect(
  166. output_image, scratch, scratch2, desired_width,
  167. rect_x + rect_w, 0, rect_x, desired_height, blur_radius, blur_times);
  168. free(scratch);
  169. free(scratch2);
  170. return output_image;
  171. }
  172. unsigned char *read_jpg(FILE *in, int *width, int *height) {
  173. struct jpeg_decompress_struct cinfo = { 0 };
  174. struct jpeg_error_mgr jerr = { 0 };
  175. cinfo.err = jpeg_std_error(&jerr);
  176. jpeg_create_decompress(&cinfo);
  177. jpeg_stdio_src(&cinfo, in);
  178. jpeg_read_header(&cinfo, TRUE);
  179. jpeg_start_decompress(&cinfo);
  180. *width = cinfo.output_width;
  181. *height = cinfo.output_height;
  182. int bytes_per_line = cinfo.output_width * 3;
  183. unsigned char *image = malloc(bytes_per_line * cinfo.output_height);
  184. /* Make a one-row-high sample array that will go away when done with image */
  185. JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)
  186. ((j_common_ptr)&cinfo, JPOOL_IMAGE, bytes_per_line, 1);
  187. while (cinfo.output_scanline < cinfo.output_height) {
  188. jpeg_read_scanlines(&cinfo, buffer, 1);
  189. /* Assume put_scanline_someplace wants a pointer and sample count. */
  190. memcpy(&image[bytes_per_line * (cinfo.output_scanline - 1)], buffer[0], bytes_per_line);
  191. }
  192. jpeg_finish_decompress(&cinfo);
  193. jpeg_destroy_decompress(&cinfo);
  194. return image;
  195. }
  196. void write_jpg(FILE *out, unsigned char *image, int width, int height) {
  197. struct jpeg_compress_struct cinfo = { 0 };
  198. struct jpeg_error_mgr jerr = { 0 };
  199. cinfo.err = jpeg_std_error(&jerr);
  200. jpeg_create_compress(&cinfo);
  201. jpeg_stdio_dest(&cinfo, out);
  202. cinfo.image_width = width - blur_radius * 2;
  203. cinfo.image_height = height - blur_radius * 2;
  204. cinfo.input_components = 3;
  205. cinfo.in_color_space = JCS_RGB;
  206. jpeg_set_defaults(&cinfo);
  207. jpeg_set_quality(&cinfo, 90, FALSE);
  208. jpeg_start_compress(&cinfo, TRUE);
  209. int bytes_per_line = width * 3;
  210. JSAMPROW row_pointer[1];
  211. while (cinfo.next_scanline < cinfo.image_height) {
  212. row_pointer[0] = &image[(cinfo.next_scanline + blur_radius) * bytes_per_line + blur_radius * 3];
  213. jpeg_write_scanlines(&cinfo, row_pointer, 1);
  214. }
  215. jpeg_finish_compress(&cinfo);
  216. jpeg_destroy_compress(&cinfo);
  217. }
  218. int main(int argc, char **argv) {
  219. if (argc != 4) {
  220. printf("Usage: %s <resolution> <input> <output>\n", argv[0]);
  221. return EXIT_FAILURE;
  222. }
  223. int desired_width, desired_height;
  224. if (sscanf(argv[1], "%ix%i", &desired_width, &desired_height) != 2) {
  225. fprintf(stderr, "Error parsing resolution '%s'\n", argv[1]);
  226. return EXIT_FAILURE;
  227. }
  228. desired_width += blur_radius * 2;
  229. desired_height += blur_radius * 2;
  230. FILE *in;
  231. if (strcmp(argv[2], "-") == 0) {
  232. in = stdin;
  233. } else {
  234. in = fopen(argv[2], "r");
  235. if (in == NULL) {
  236. perror(argv[2]);
  237. return EXIT_FAILURE;
  238. }
  239. }
  240. int input_width, input_height;
  241. unsigned char *input_image = read_jpg(in, &input_width, &input_height);
  242. fclose(in);
  243. unsigned char *output_image = process_image(
  244. input_image, input_width, input_height, desired_width, desired_height);
  245. free(input_image);
  246. FILE *out;
  247. if (strcmp(argv[3], "-") == 0) {
  248. out = stdout;
  249. } else {
  250. out = fopen(argv[3], "w");
  251. if (out == NULL) {
  252. perror(argv[3]);
  253. free(output_image);
  254. return EXIT_FAILURE;
  255. }
  256. }
  257. write_jpg(out, output_image, desired_width, desired_height);
  258. free(output_image);
  259. return EXIT_SUCCESS;
  260. }