Groonga 3.0.9 Source Code Document
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ngx_http_image_filter_module.c
Go to the documentation of this file.
1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) Nginx, Inc.
5  */
6 
7 
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11 
12 #include <gd.h>
13 
14 
15 #define NGX_HTTP_IMAGE_OFF 0
16 #define NGX_HTTP_IMAGE_TEST 1
17 #define NGX_HTTP_IMAGE_SIZE 2
18 #define NGX_HTTP_IMAGE_RESIZE 3
19 #define NGX_HTTP_IMAGE_CROP 4
20 #define NGX_HTTP_IMAGE_ROTATE 5
21 
22 
23 #define NGX_HTTP_IMAGE_START 0
24 #define NGX_HTTP_IMAGE_READ 1
25 #define NGX_HTTP_IMAGE_PROCESS 2
26 #define NGX_HTTP_IMAGE_PASS 3
27 #define NGX_HTTP_IMAGE_DONE 4
28 
29 
30 #define NGX_HTTP_IMAGE_NONE 0
31 #define NGX_HTTP_IMAGE_JPEG 1
32 #define NGX_HTTP_IMAGE_GIF 2
33 #define NGX_HTTP_IMAGE_PNG 3
34 
35 
36 #define NGX_HTTP_IMAGE_BUFFERED 0x08
37 
38 
39 typedef struct {
46 
49 
55 
56  size_t buffer_size;
58 
59 
60 typedef struct {
61  u_char *image;
62  u_char *last;
63 
64  size_t length;
65 
71 
76 
77 
78 static ngx_int_t ngx_http_image_send(ngx_http_request_t *r,
80 static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
81 static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
82 static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
83 static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
85 static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
87 static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
88 static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
90 
91 static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
93 static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
95 static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
96  int colors);
97 static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
98  gdImagePtr img, int *size);
99 static void ngx_http_image_cleanup(void *data);
100 static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r,
102 static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value);
103 
104 
105 static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
106 static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
107  void *child);
108 static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
109  void *conf);
110 static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf,
111  ngx_command_t *cmd, void *conf);
112 static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
113  void *conf);
114 static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);
115 
116 
117 static ngx_command_t ngx_http_image_filter_commands[] = {
118 
119  { ngx_string("image_filter"),
121  ngx_http_image_filter,
123  0,
124  NULL },
125 
126  { ngx_string("image_filter_jpeg_quality"),
128  ngx_http_image_filter_jpeg_quality,
130  0,
131  NULL },
132 
133  { ngx_string("image_filter_sharpen"),
135  ngx_http_image_filter_sharpen,
137  0,
138  NULL },
139 
140  { ngx_string("image_filter_transparency"),
144  offsetof(ngx_http_image_filter_conf_t, transparency),
145  NULL },
146 
147  { ngx_string("image_filter_interlace"),
151  offsetof(ngx_http_image_filter_conf_t, interlace),
152  NULL },
153 
154  { ngx_string("image_filter_buffer"),
158  offsetof(ngx_http_image_filter_conf_t, buffer_size),
159  NULL },
160 
162 };
163 
164 
165 static ngx_http_module_t ngx_http_image_filter_module_ctx = {
166  NULL, /* preconfiguration */
167  ngx_http_image_filter_init, /* postconfiguration */
168 
169  NULL, /* create main configuration */
170  NULL, /* init main configuration */
171 
172  NULL, /* create server configuration */
173  NULL, /* merge server configuration */
174 
175  ngx_http_image_filter_create_conf, /* create location configuration */
176  ngx_http_image_filter_merge_conf /* merge location configuration */
177 };
178 
179 
182  &ngx_http_image_filter_module_ctx, /* module context */
183  ngx_http_image_filter_commands, /* module directives */
184  NGX_HTTP_MODULE, /* module type */
185  NULL, /* init master */
186  NULL, /* init module */
187  NULL, /* init process */
188  NULL, /* init thread */
189  NULL, /* exit thread */
190  NULL, /* exit process */
191  NULL, /* exit master */
193 };
194 
195 
196 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
197 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
198 
199 
200 static ngx_str_t ngx_http_image_types[] = {
201  ngx_string("image/jpeg"),
202  ngx_string("image/gif"),
203  ngx_string("image/png")
204 };
205 
206 
207 static ngx_int_t
208 ngx_http_image_header_filter(ngx_http_request_t *r)
209 {
210  off_t len;
213 
215  return ngx_http_next_header_filter(r);
216  }
217 
218  ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
219 
220  if (ctx) {
221  ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module);
222  return ngx_http_next_header_filter(r);
223  }
224 
225  conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
226 
227  if (conf->filter == NGX_HTTP_IMAGE_OFF) {
228  return ngx_http_next_header_filter(r);
229  }
230 
232  >= sizeof("multipart/x-mixed-replace") - 1
234  (u_char *) "multipart/x-mixed-replace",
235  sizeof("multipart/x-mixed-replace") - 1)
236  == 0)
237  {
239  "image filter: multipart/x-mixed-replace response");
240 
241  return NGX_ERROR;
242  }
243 
244  ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
245  if (ctx == NULL) {
246  return NGX_ERROR;
247  }
248 
249  ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);
250 
251  len = r->headers_out.content_length_n;
252 
253  if (len != -1 && len > (off_t) conf->buffer_size) {
255  "image filter: too big response: %O", len);
256 
258  }
259 
260  if (len == -1) {
261  ctx->length = conf->buffer_size;
262 
263  } else {
264  ctx->length = (size_t) len;
265  }
266 
267  if (r->headers_out.refresh) {
268  r->headers_out.refresh->hash = 0;
269  }
270 
272  r->allow_ranges = 0;
273 
274  return NGX_OK;
275 }
276 
277 
278 static ngx_int_t
279 ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
280 {
281  ngx_int_t rc;
282  ngx_str_t *ct;
283  ngx_chain_t out;
286 
287  ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");
288 
289  if (in == NULL) {
290  return ngx_http_next_body_filter(r, in);
291  }
292 
293  ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
294 
295  if (ctx == NULL) {
296  return ngx_http_next_body_filter(r, in);
297  }
298 
299  switch (ctx->phase) {
300 
302 
303  ctx->type = ngx_http_image_test(r, in);
304 
305  conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
306 
307  if (ctx->type == NGX_HTTP_IMAGE_NONE) {
308 
309  if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
310  out.buf = ngx_http_image_json(r, NULL);
311 
312  if (out.buf) {
313  out.next = NULL;
314  ctx->phase = NGX_HTTP_IMAGE_DONE;
315 
316  return ngx_http_image_send(r, ctx, &out);
317  }
318  }
319 
321  &ngx_http_image_filter_module,
323  }
324 
325  /* override content type */
326 
327  ct = &ngx_http_image_types[ctx->type - 1];
329  r->headers_out.content_type = *ct;
331 
332  if (conf->filter == NGX_HTTP_IMAGE_TEST) {
333  ctx->phase = NGX_HTTP_IMAGE_PASS;
334 
335  return ngx_http_image_send(r, ctx, in);
336  }
337 
338  ctx->phase = NGX_HTTP_IMAGE_READ;
339 
340  /* fall through */
341 
342  case NGX_HTTP_IMAGE_READ:
343 
344  rc = ngx_http_image_read(r, in);
345 
346  if (rc == NGX_AGAIN) {
347  return NGX_OK;
348  }
349 
350  if (rc == NGX_ERROR) {
352  &ngx_http_image_filter_module,
354  }
355 
356  /* fall through */
357 
359 
360  out.buf = ngx_http_image_process(r);
361 
362  if (out.buf == NULL) {
364  &ngx_http_image_filter_module,
366  }
367 
368  out.next = NULL;
369  ctx->phase = NGX_HTTP_IMAGE_PASS;
370 
371  return ngx_http_image_send(r, ctx, &out);
372 
373  case NGX_HTTP_IMAGE_PASS:
374 
375  return ngx_http_next_body_filter(r, in);
376 
377  default: /* NGX_HTTP_IMAGE_DONE */
378 
379  rc = ngx_http_next_body_filter(r, NULL);
380 
381  /* NGX_ERROR resets any pending data */
382  return (rc == NGX_OK) ? NGX_ERROR : rc;
383  }
384 }
385 
386 
387 static ngx_int_t
388 ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx,
389  ngx_chain_t *in)
390 {
391  ngx_int_t rc;
392 
393  rc = ngx_http_next_header_filter(r);
394 
395  if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
396  return NGX_ERROR;
397  }
398 
399  rc = ngx_http_next_body_filter(r, in);
400 
401  if (ctx->phase == NGX_HTTP_IMAGE_DONE) {
402  /* NGX_ERROR resets any pending data */
403  return (rc == NGX_OK) ? NGX_ERROR : rc;
404  }
405 
406  return rc;
407 }
408 
409 
410 static ngx_uint_t
411 ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
412 {
413  u_char *p;
414 
415  p = in->buf->pos;
416 
417  if (in->buf->last - p < 16) {
418  return NGX_HTTP_IMAGE_NONE;
419  }
420 
422  "image filter: \"%c%c\"", p[0], p[1]);
423 
424  if (p[0] == 0xff && p[1] == 0xd8) {
425 
426  /* JPEG */
427 
428  return NGX_HTTP_IMAGE_JPEG;
429 
430  } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
431  && p[5] == 'a')
432  {
433  if (p[4] == '9' || p[4] == '7') {
434  /* GIF */
435  return NGX_HTTP_IMAGE_GIF;
436  }
437 
438  } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
439  && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
440  {
441  /* PNG */
442 
443  return NGX_HTTP_IMAGE_PNG;
444  }
445 
446  return NGX_HTTP_IMAGE_NONE;
447 }
448 
449 
450 static ngx_int_t
451 ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
452 {
453  u_char *p;
454  size_t size, rest;
455  ngx_buf_t *b;
456  ngx_chain_t *cl;
458 
459  ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
460 
461  if (ctx->image == NULL) {
462  ctx->image = ngx_palloc(r->pool, ctx->length);
463  if (ctx->image == NULL) {
464  return NGX_ERROR;
465  }
466 
467  ctx->last = ctx->image;
468  }
469 
470  p = ctx->last;
471 
472  for (cl = in; cl; cl = cl->next) {
473 
474  b = cl->buf;
475  size = b->last - b->pos;
476 
478  "image buf: %uz", size);
479 
480  rest = ctx->image + ctx->length - p;
481  size = (rest < size) ? rest : size;
482 
483  p = ngx_cpymem(p, b->pos, size);
484  b->pos += size;
485 
486  if (b->last_buf) {
487  ctx->last = p;
488  return NGX_OK;
489  }
490  }
491 
492  ctx->last = p;
494 
495  return NGX_AGAIN;
496 }
497 
498 
499 static ngx_buf_t *
500 ngx_http_image_process(ngx_http_request_t *r)
501 {
502  ngx_int_t rc;
505 
507 
508  ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
509 
510  rc = ngx_http_image_size(r, ctx);
511 
512  conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
513 
514  if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
515  return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
516  }
517 
518  ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle);
519 
520  if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
521 
522  if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) {
523  return NULL;
524  }
525 
526  return ngx_http_image_resize(r, ctx);
527  }
528 
529  ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width);
530  if (ctx->max_width == 0) {
531  return NULL;
532  }
533 
534  ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv,
535  conf->height);
536  if (ctx->max_height == 0) {
537  return NULL;
538  }
539 
540  if (rc == NGX_OK
541  && ctx->width <= ctx->max_width
542  && ctx->height <= ctx->max_height
543  && ctx->angle == 0
544  && !ctx->force)
545  {
546  return ngx_http_image_asis(r, ctx);
547  }
548 
549  return ngx_http_image_resize(r, ctx);
550 }
551 
552 
553 static ngx_buf_t *
554 ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
555 {
556  size_t len;
557  ngx_buf_t *b;
558 
559  b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
560  if (b == NULL) {
561  return NULL;
562  }
563 
564  b->memory = 1;
565  b->last_buf = 1;
566 
568 
570  ngx_str_set(&r->headers_out.content_type, "text/plain");
572 
573  if (ctx == NULL) {
574  b->pos = (u_char *) "{}" CRLF;
575  b->last = b->pos + sizeof("{}" CRLF) - 1;
576 
577  ngx_http_image_length(r, b);
578 
579  return b;
580  }
581 
582  len = sizeof("{ \"img\" : "
583  "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
584  + 2 * NGX_SIZE_T_LEN;
585 
586  b->pos = ngx_pnalloc(r->pool, len);
587  if (b->pos == NULL) {
588  return NULL;
589  }
590 
591  b->last = ngx_sprintf(b->pos,
592  "{ \"img\" : "
593  "{ \"width\": %uz,"
594  " \"height\": %uz,"
595  " \"type\": \"%s\" } }" CRLF,
596  ctx->width, ctx->height,
597  ngx_http_image_types[ctx->type - 1].data + 6);
598 
599  ngx_http_image_length(r, b);
600 
601  return b;
602 }
603 
604 
605 static ngx_buf_t *
606 ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
607 {
608  ngx_buf_t *b;
609 
610  b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
611  if (b == NULL) {
612  return NULL;
613  }
614 
615  b->pos = ctx->image;
616  b->last = ctx->last;
617  b->memory = 1;
618  b->last_buf = 1;
619 
620  ngx_http_image_length(r, b);
621 
622  return b;
623 }
624 
625 
626 static void
627 ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
628 {
629  r->headers_out.content_length_n = b->last - b->pos;
630 
631  if (r->headers_out.content_length) {
633  }
634 
635  r->headers_out.content_length = NULL;
636 }
637 
638 
639 static ngx_int_t
640 ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
641 {
642  u_char *p, *last;
643  size_t len, app;
644  ngx_uint_t width, height;
645 
646  p = ctx->image;
647 
648  switch (ctx->type) {
649 
650  case NGX_HTTP_IMAGE_JPEG:
651 
652  p += 2;
653  last = ctx->image + ctx->length - 10;
654  width = 0;
655  height = 0;
656  app = 0;
657 
658  while (p < last) {
659 
660  if (p[0] == 0xff && p[1] != 0xff) {
661 
663  "JPEG: %02xd %02xd", p[0], p[1]);
664 
665  p++;
666 
667  if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
668  || *p == 0xc9 || *p == 0xca || *p == 0xcb)
669  && (width == 0 || height == 0))
670  {
671  width = p[6] * 256 + p[7];
672  height = p[4] * 256 + p[5];
673  }
674 
676  "JPEG: %02xd %02xd", p[1], p[2]);
677 
678  len = p[1] * 256 + p[2];
679 
680  if (*p >= 0xe1 && *p <= 0xef) {
681  /* application data, e.g., EXIF, Adobe XMP, etc. */
682  app += len;
683  }
684 
685  p += len;
686 
687  continue;
688  }
689 
690  p++;
691  }
692 
693  if (width == 0 || height == 0) {
694  return NGX_DECLINED;
695  }
696 
697  if (ctx->length / 20 < app) {
698  /* force conversion if application data consume more than 5% */
699  ctx->force = 1;
701  "app data size: %uz", app);
702  }
703 
704  break;
705 
706  case NGX_HTTP_IMAGE_GIF:
707 
708  if (ctx->length < 10) {
709  return NGX_DECLINED;
710  }
711 
712  width = p[7] * 256 + p[6];
713  height = p[9] * 256 + p[8];
714 
715  break;
716 
717  case NGX_HTTP_IMAGE_PNG:
718 
719  if (ctx->length < 24) {
720  return NGX_DECLINED;
721  }
722 
723  width = p[18] * 256 + p[19];
724  height = p[22] * 256 + p[23];
725 
726  break;
727 
728  default:
729 
730  return NGX_DECLINED;
731  }
732 
734  "image size: %d x %d", width, height);
735 
736  ctx->width = width;
737  ctx->height = height;
738 
739  return NGX_OK;
740 }
741 
742 
743 static ngx_buf_t *
744 ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
745 {
746  int sx, sy, dx, dy, ox, oy, ax, ay, size,
747  colors, palette, transparent, sharpen,
748  red, green, blue, t;
749  u_char *out;
750  ngx_buf_t *b;
751  ngx_uint_t resize;
752  gdImagePtr src, dst;
753  ngx_pool_cleanup_t *cln;
755 
756  src = ngx_http_image_source(r, ctx);
757 
758  if (src == NULL) {
759  return NULL;
760  }
761 
762  sx = gdImageSX(src);
763  sy = gdImageSY(src);
764 
765  conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
766 
767  if (!ctx->force
768  && ctx->angle == 0
769  && (ngx_uint_t) sx <= ctx->max_width
770  && (ngx_uint_t) sy <= ctx->max_height)
771  {
772  gdImageDestroy(src);
773  return ngx_http_image_asis(r, ctx);
774  }
775 
776  colors = gdImageColorsTotal(src);
777 
778  if (colors && conf->transparency) {
779  transparent = gdImageGetTransparent(src);
780 
781  if (transparent != -1) {
782  palette = colors;
783  red = gdImageRed(src, transparent);
784  green = gdImageGreen(src, transparent);
785  blue = gdImageBlue(src, transparent);
786 
787  goto transparent;
788  }
789  }
790 
791  palette = 0;
792  transparent = -1;
793  red = 0;
794  green = 0;
795  blue = 0;
796 
797 transparent:
798 
799  gdImageColorTransparent(src, -1);
800 
801  dx = sx;
802  dy = sy;
803 
804  if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {
805 
806  if ((ngx_uint_t) dx > ctx->max_width) {
807  dy = dy * ctx->max_width / dx;
808  dy = dy ? dy : 1;
809  dx = ctx->max_width;
810  }
811 
812  if ((ngx_uint_t) dy > ctx->max_height) {
813  dx = dx * ctx->max_height / dy;
814  dx = dx ? dx : 1;
815  dy = ctx->max_height;
816  }
817 
818  resize = 1;
819 
820  } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
821 
822  resize = 0;
823 
824  } else { /* NGX_HTTP_IMAGE_CROP */
825 
826  resize = 0;
827 
828  if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) {
829  if ((ngx_uint_t) dx > ctx->max_width) {
830  dy = dy * ctx->max_width / dx;
831  dy = dy ? dy : 1;
832  dx = ctx->max_width;
833  resize = 1;
834  }
835 
836  } else {
837  if ((ngx_uint_t) dy > ctx->max_height) {
838  dx = dx * ctx->max_height / dy;
839  dx = dx ? dx : 1;
840  dy = ctx->max_height;
841  resize = 1;
842  }
843  }
844  }
845 
846  if (resize) {
847  dst = ngx_http_image_new(r, dx, dy, palette);
848  if (dst == NULL) {
849  gdImageDestroy(src);
850  return NULL;
851  }
852 
853  if (colors == 0) {
854  gdImageSaveAlpha(dst, 1);
855  gdImageAlphaBlending(dst, 0);
856  }
857 
858  gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);
859 
860  if (colors) {
861  gdImageTrueColorToPalette(dst, 1, 256);
862  }
863 
864  gdImageDestroy(src);
865 
866  } else {
867  dst = src;
868  }
869 
870  if (ctx->angle) {
871  src = dst;
872 
873  ax = (dx % 2 == 0) ? 1 : 0;
874  ay = (dy % 2 == 0) ? 1 : 0;
875 
876  switch (ctx->angle) {
877 
878  case 90:
879  case 270:
880  dst = ngx_http_image_new(r, dy, dx, palette);
881  if (dst == NULL) {
882  gdImageDestroy(src);
883  return NULL;
884  }
885  if (ctx->angle == 90) {
886  ox = dy / 2 + ay;
887  oy = dx / 2 - ax;
888 
889  } else {
890  ox = dy / 2 - ay;
891  oy = dx / 2 + ax;
892  }
893 
894  gdImageCopyRotated(dst, src, ox, oy, 0, 0,
895  dx + ax, dy + ay, ctx->angle);
896  gdImageDestroy(src);
897 
898  t = dx;
899  dx = dy;
900  dy = t;
901  break;
902 
903  case 180:
904  dst = ngx_http_image_new(r, dx, dy, palette);
905  if (dst == NULL) {
906  gdImageDestroy(src);
907  return NULL;
908  }
909  gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0,
910  dx + ax, dy + ay, ctx->angle);
911  gdImageDestroy(src);
912  break;
913  }
914  }
915 
916  if (conf->filter == NGX_HTTP_IMAGE_CROP) {
917 
918  src = dst;
919 
920  if ((ngx_uint_t) dx > ctx->max_width) {
921  ox = dx - ctx->max_width;
922 
923  } else {
924  ox = 0;
925  }
926 
927  if ((ngx_uint_t) dy > ctx->max_height) {
928  oy = dy - ctx->max_height;
929 
930  } else {
931  oy = 0;
932  }
933 
934  if (ox || oy) {
935 
936  dst = ngx_http_image_new(r, dx - ox, dy - oy, colors);
937 
938  if (dst == NULL) {
939  gdImageDestroy(src);
940  return NULL;
941  }
942 
943  ox /= 2;
944  oy /= 2;
945 
947  "image crop: %d x %d @ %d x %d",
948  dx, dy, ox, oy);
949 
950  if (colors == 0) {
951  gdImageSaveAlpha(dst, 1);
952  gdImageAlphaBlending(dst, 0);
953  }
954 
955  gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy);
956 
957  if (colors) {
958  gdImageTrueColorToPalette(dst, 1, 256);
959  }
960 
961  gdImageDestroy(src);
962  }
963  }
964 
965  if (transparent != -1 && colors) {
966  gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue));
967  }
968 
969  sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen);
970  if (sharpen > 0) {
971  gdImageSharpen(dst, sharpen);
972  }
973 
974  gdImageInterlace(dst, (int) conf->interlace);
975 
976  out = ngx_http_image_out(r, ctx->type, dst, &size);
977 
979  "image: %d x %d %d", sx, sy, colors);
980 
981  gdImageDestroy(dst);
982  ngx_pfree(r->pool, ctx->image);
983 
984  if (out == NULL) {
985  return NULL;
986  }
987 
988  cln = ngx_pool_cleanup_add(r->pool, 0);
989  if (cln == NULL) {
990  gdFree(out);
991  return NULL;
992  }
993 
994  b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
995  if (b == NULL) {
996  gdFree(out);
997  return NULL;
998  }
999 
1000  cln->handler = ngx_http_image_cleanup;
1001  cln->data = out;
1002 
1003  b->pos = out;
1004  b->last = out + size;
1005  b->memory = 1;
1006  b->last_buf = 1;
1007 
1008  ngx_http_image_length(r, b);
1009 
1010  return b;
1011 }
1012 
1013 
1014 static gdImagePtr
1015 ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
1016 {
1017  char *failed;
1018  gdImagePtr img;
1019 
1020  img = NULL;
1021 
1022  switch (ctx->type) {
1023 
1024  case NGX_HTTP_IMAGE_JPEG:
1025  img = gdImageCreateFromJpegPtr(ctx->length, ctx->image);
1026  failed = "gdImageCreateFromJpegPtr() failed";
1027  break;
1028 
1029  case NGX_HTTP_IMAGE_GIF:
1030  img = gdImageCreateFromGifPtr(ctx->length, ctx->image);
1031  failed = "gdImageCreateFromGifPtr() failed";
1032  break;
1033 
1034  case NGX_HTTP_IMAGE_PNG:
1035  img = gdImageCreateFromPngPtr(ctx->length, ctx->image);
1036  failed = "gdImageCreateFromPngPtr() failed";
1037  break;
1038 
1039  default:
1040  failed = "unknown image type";
1041  break;
1042  }
1043 
1044  if (img == NULL) {
1045  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
1046  }
1047 
1048  return img;
1049 }
1050 
1051 
1052 static gdImagePtr
1053 ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors)
1054 {
1055  gdImagePtr img;
1056 
1057  if (colors == 0) {
1058  img = gdImageCreateTrueColor(w, h);
1059 
1060  if (img == NULL) {
1062  "gdImageCreateTrueColor() failed");
1063  return NULL;
1064  }
1065 
1066  } else {
1067  img = gdImageCreate(w, h);
1068 
1069  if (img == NULL) {
1071  "gdImageCreate() failed");
1072  return NULL;
1073  }
1074  }
1075 
1076  return img;
1077 }
1078 
1079 
1080 static u_char *
1081 ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img,
1082  int *size)
1083 {
1084  char *failed;
1085  u_char *out;
1086  ngx_int_t jq;
1088 
1089  out = NULL;
1090 
1091  switch (type) {
1092 
1093  case NGX_HTTP_IMAGE_JPEG:
1094  conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
1095 
1096  jq = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality);
1097  if (jq <= 0) {
1098  return NULL;
1099  }
1100 
1101  out = gdImageJpegPtr(img, size, jq);
1102  failed = "gdImageJpegPtr() failed";
1103  break;
1104 
1105  case NGX_HTTP_IMAGE_GIF:
1106  out = gdImageGifPtr(img, size);
1107  failed = "gdImageGifPtr() failed";
1108  break;
1109 
1110  case NGX_HTTP_IMAGE_PNG:
1111  out = gdImagePngPtr(img, size);
1112  failed = "gdImagePngPtr() failed";
1113  break;
1114 
1115  default:
1116  failed = "unknown image type";
1117  break;
1118  }
1119 
1120  if (out == NULL) {
1121  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
1122  }
1123 
1124  return out;
1125 }
1126 
1127 
1128 static void
1129 ngx_http_image_cleanup(void *data)
1130 {
1131  gdFree(data);
1132 }
1133 
1134 
1135 static ngx_uint_t
1136 ngx_http_image_filter_get_value(ngx_http_request_t *r,
1138 {
1139  ngx_str_t val;
1140 
1141  if (cv == NULL) {
1142  return v;
1143  }
1144 
1145  if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
1146  return 0;
1147  }
1148 
1149  return ngx_http_image_filter_value(&val);
1150 }
1151 
1152 
1153 static ngx_uint_t
1154 ngx_http_image_filter_value(ngx_str_t *value)
1155 {
1156  ngx_int_t n;
1157 
1158  if (value->len == 1 && value->data[0] == '-') {
1159  return (ngx_uint_t) -1;
1160  }
1161 
1162  n = ngx_atoi(value->data, value->len);
1163 
1164  if (n > 0) {
1165  return (ngx_uint_t) n;
1166  }
1167 
1168  return 0;
1169 }
1170 
1171 
1172 static void *
1173 ngx_http_image_filter_create_conf(ngx_conf_t *cf)
1174 {
1176 
1177  conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t));
1178  if (conf == NULL) {
1179  return NULL;
1180  }
1181 
1182  /*
1183  * set by ngx_pcalloc():
1184  *
1185  * conf->width = 0;
1186  * conf->height = 0;
1187  * conf->angle = 0;
1188  * conf->wcv = NULL;
1189  * conf->hcv = NULL;
1190  * conf->acv = NULL;
1191  * conf->jqcv = NULL;
1192  * conf->shcv = NULL;
1193  */
1194 
1195  conf->filter = NGX_CONF_UNSET_UINT;
1197  conf->sharpen = NGX_CONF_UNSET_UINT;
1198  conf->transparency = NGX_CONF_UNSET;
1199  conf->interlace = NGX_CONF_UNSET;
1201 
1202  return conf;
1203 }
1204 
1205 
1206 static char *
1207 ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
1208 {
1209  ngx_http_image_filter_conf_t *prev = parent;
1210  ngx_http_image_filter_conf_t *conf = child;
1211 
1212  if (conf->filter == NGX_CONF_UNSET_UINT) {
1213 
1214  if (prev->filter == NGX_CONF_UNSET_UINT) {
1215  conf->filter = NGX_HTTP_IMAGE_OFF;
1216 
1217  } else {
1218  conf->filter = prev->filter;
1219  conf->width = prev->width;
1220  conf->height = prev->height;
1221  conf->angle = prev->angle;
1222  conf->wcv = prev->wcv;
1223  conf->hcv = prev->hcv;
1224  conf->acv = prev->acv;
1225  }
1226  }
1227 
1228  if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) {
1229 
1230  /* 75 is libjpeg default quality */
1232 
1233  if (conf->jqcv == NULL) {
1234  conf->jqcv = prev->jqcv;
1235  }
1236  }
1237 
1238  if (conf->sharpen == NGX_CONF_UNSET_UINT) {
1239  ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0);
1240 
1241  if (conf->shcv == NULL) {
1242  conf->shcv = prev->shcv;
1243  }
1244  }
1245 
1247 
1248  ngx_conf_merge_value(conf->interlace, prev->interlace, 0);
1249 
1251  1 * 1024 * 1024);
1252 
1253  return NGX_CONF_OK;
1254 }
1255 
1256 
1257 static char *
1258 ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1259 {
1260  ngx_http_image_filter_conf_t *imcf = conf;
1261 
1262  ngx_str_t *value;
1263  ngx_int_t n;
1264  ngx_uint_t i;
1267 
1268  value = cf->args->elts;
1269 
1270  i = 1;
1271 
1272  if (cf->args->nelts == 2) {
1273  if (ngx_strcmp(value[i].data, "off") == 0) {
1274  imcf->filter = NGX_HTTP_IMAGE_OFF;
1275 
1276  } else if (ngx_strcmp(value[i].data, "test") == 0) {
1277  imcf->filter = NGX_HTTP_IMAGE_TEST;
1278 
1279  } else if (ngx_strcmp(value[i].data, "size") == 0) {
1280  imcf->filter = NGX_HTTP_IMAGE_SIZE;
1281 
1282  } else {
1283  goto failed;
1284  }
1285 
1286  return NGX_CONF_OK;
1287 
1288  } else if (cf->args->nelts == 3) {
1289 
1290  if (ngx_strcmp(value[i].data, "rotate") == 0) {
1291  if (imcf->filter != NGX_HTTP_IMAGE_RESIZE
1292  && imcf->filter != NGX_HTTP_IMAGE_CROP)
1293  {
1294  imcf->filter = NGX_HTTP_IMAGE_ROTATE;
1295  }
1296 
1298 
1299  ccv.cf = cf;
1300  ccv.value = &value[++i];
1301  ccv.complex_value = &cv;
1302 
1303  if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1304  return NGX_CONF_ERROR;
1305  }
1306 
1307  if (cv.lengths == NULL) {
1308  n = ngx_http_image_filter_value(&value[i]);
1309 
1310  if (n != 90 && n != 180 && n != 270) {
1311  goto failed;
1312  }
1313 
1314  imcf->angle = (ngx_uint_t) n;
1315 
1316  } else {
1317  imcf->acv = ngx_palloc(cf->pool,
1318  sizeof(ngx_http_complex_value_t));
1319  if (imcf->acv == NULL) {
1320  return NGX_CONF_ERROR;
1321  }
1322 
1323  *imcf->acv = cv;
1324  }
1325 
1326  return NGX_CONF_OK;
1327 
1328  } else {
1329  goto failed;
1330  }
1331  }
1332 
1333  if (ngx_strcmp(value[i].data, "resize") == 0) {
1334  imcf->filter = NGX_HTTP_IMAGE_RESIZE;
1335 
1336  } else if (ngx_strcmp(value[i].data, "crop") == 0) {
1337  imcf->filter = NGX_HTTP_IMAGE_CROP;
1338 
1339  } else {
1340  goto failed;
1341  }
1342 
1344 
1345  ccv.cf = cf;
1346  ccv.value = &value[++i];
1347  ccv.complex_value = &cv;
1348 
1349  if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1350  return NGX_CONF_ERROR;
1351  }
1352 
1353  if (cv.lengths == NULL) {
1354  n = ngx_http_image_filter_value(&value[i]);
1355 
1356  if (n == 0) {
1357  goto failed;
1358  }
1359 
1360  imcf->width = (ngx_uint_t) n;
1361 
1362  } else {
1363  imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1364  if (imcf->wcv == NULL) {
1365  return NGX_CONF_ERROR;
1366  }
1367 
1368  *imcf->wcv = cv;
1369  }
1370 
1372 
1373  ccv.cf = cf;
1374  ccv.value = &value[++i];
1375  ccv.complex_value = &cv;
1376 
1377  if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1378  return NGX_CONF_ERROR;
1379  }
1380 
1381  if (cv.lengths == NULL) {
1382  n = ngx_http_image_filter_value(&value[i]);
1383 
1384  if (n == 0) {
1385  goto failed;
1386  }
1387 
1388  imcf->height = (ngx_uint_t) n;
1389 
1390  } else {
1391  imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1392  if (imcf->hcv == NULL) {
1393  return NGX_CONF_ERROR;
1394  }
1395 
1396  *imcf->hcv = cv;
1397  }
1398 
1399  return NGX_CONF_OK;
1400 
1401 failed:
1402 
1403  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
1404  &value[i]);
1405 
1406  return NGX_CONF_ERROR;
1407 }
1408 
1409 
1410 static char *
1411 ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd,
1412  void *conf)
1413 {
1414  ngx_http_image_filter_conf_t *imcf = conf;
1415 
1416  ngx_str_t *value;
1417  ngx_int_t n;
1420 
1421  value = cf->args->elts;
1422 
1424 
1425  ccv.cf = cf;
1426  ccv.value = &value[1];
1427  ccv.complex_value = &cv;
1428 
1429  if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1430  return NGX_CONF_ERROR;
1431  }
1432 
1433  if (cv.lengths == NULL) {
1434  n = ngx_http_image_filter_value(&value[1]);
1435 
1436  if (n <= 0) {
1438  "invalid value \"%V\"", &value[1]);
1439  return NGX_CONF_ERROR;
1440  }
1441 
1442  imcf->jpeg_quality = (ngx_uint_t) n;
1443 
1444  } else {
1445  imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1446  if (imcf->jqcv == NULL) {
1447  return NGX_CONF_ERROR;
1448  }
1449 
1450  *imcf->jqcv = cv;
1451  }
1452 
1453  return NGX_CONF_OK;
1454 }
1455 
1456 
1457 static char *
1458 ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
1459  void *conf)
1460 {
1461  ngx_http_image_filter_conf_t *imcf = conf;
1462 
1463  ngx_str_t *value;
1464  ngx_int_t n;
1467 
1468  value = cf->args->elts;
1469 
1471 
1472  ccv.cf = cf;
1473  ccv.value = &value[1];
1474  ccv.complex_value = &cv;
1475 
1476  if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1477  return NGX_CONF_ERROR;
1478  }
1479 
1480  if (cv.lengths == NULL) {
1481  n = ngx_http_image_filter_value(&value[1]);
1482 
1483  if (n < 0) {
1485  "invalid value \"%V\"", &value[1]);
1486  return NGX_CONF_ERROR;
1487  }
1488 
1489  imcf->sharpen = (ngx_uint_t) n;
1490 
1491  } else {
1492  imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1493  if (imcf->shcv == NULL) {
1494  return NGX_CONF_ERROR;
1495  }
1496 
1497  *imcf->shcv = cv;
1498  }
1499 
1500  return NGX_CONF_OK;
1501 }
1502 
1503 
1504 static ngx_int_t
1505 ngx_http_image_filter_init(ngx_conf_t *cf)
1506 {
1507  ngx_http_next_header_filter = ngx_http_top_header_filter;
1508  ngx_http_top_header_filter = ngx_http_image_header_filter;
1509 
1510  ngx_http_next_body_filter = ngx_http_top_body_filter;
1511  ngx_http_top_body_filter = ngx_http_image_body_filter;
1512 
1513  return NGX_OK;
1514 }