3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
8 #include <ngx_config.h>
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
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
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
36 #define NGX_HTTP_IMAGE_BUFFERED 0x08
44 ngx_uint_t jpeg_quality
;
47 ngx_flag_t transparency
;
50 ngx_http_complex_value_t
*wcv
;
51 ngx_http_complex_value_t
*hcv
;
52 ngx_http_complex_value_t
*acv
;
53 ngx_http_complex_value_t
*jqcv
;
54 ngx_http_complex_value_t
*shcv
;
57 } ngx_http_image_filter_conf_t
;
69 ngx_uint_t max_height
;
75 } ngx_http_image_filter_ctx_t
;
78 static ngx_int_t
ngx_http_image_send(ngx_http_request_t
*r
,
79 ngx_http_image_filter_ctx_t
*ctx
, ngx_chain_t
*in
);
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
,
84 ngx_http_image_filter_ctx_t
*ctx
);
85 static ngx_buf_t
*ngx_http_image_asis(ngx_http_request_t
*r
,
86 ngx_http_image_filter_ctx_t
*ctx
);
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
,
89 ngx_http_image_filter_ctx_t
*ctx
);
91 static ngx_buf_t
*ngx_http_image_resize(ngx_http_request_t
*r
,
92 ngx_http_image_filter_ctx_t
*ctx
);
93 static gdImagePtr
ngx_http_image_source(ngx_http_request_t
*r
,
94 ngx_http_image_filter_ctx_t
*ctx
);
95 static gdImagePtr
ngx_http_image_new(ngx_http_request_t
*r
, int w
, int h
,
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
,
101 ngx_http_complex_value_t
*cv
, ngx_uint_t v
);
102 static ngx_uint_t
ngx_http_image_filter_value(ngx_str_t
*value
);
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
,
108 static char *ngx_http_image_filter(ngx_conf_t
*cf
, ngx_command_t
*cmd
,
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
,
114 static ngx_int_t
ngx_http_image_filter_init(ngx_conf_t
*cf
);
117 static ngx_command_t ngx_http_image_filter_commands
[] = {
119 { ngx_string("image_filter"),
120 NGX_HTTP_LOC_CONF
|NGX_CONF_TAKE123
,
121 ngx_http_image_filter
,
122 NGX_HTTP_LOC_CONF_OFFSET
,
126 { ngx_string("image_filter_jpeg_quality"),
127 NGX_HTTP_MAIN_CONF
|NGX_HTTP_SRV_CONF
|NGX_HTTP_LOC_CONF
|NGX_CONF_TAKE1
,
128 ngx_http_image_filter_jpeg_quality
,
129 NGX_HTTP_LOC_CONF_OFFSET
,
133 { ngx_string("image_filter_sharpen"),
134 NGX_HTTP_MAIN_CONF
|NGX_HTTP_SRV_CONF
|NGX_HTTP_LOC_CONF
|NGX_CONF_TAKE1
,
135 ngx_http_image_filter_sharpen
,
136 NGX_HTTP_LOC_CONF_OFFSET
,
140 { ngx_string("image_filter_transparency"),
141 NGX_HTTP_MAIN_CONF
|NGX_HTTP_SRV_CONF
|NGX_HTTP_LOC_CONF
|NGX_CONF_FLAG
,
142 ngx_conf_set_flag_slot
,
143 NGX_HTTP_LOC_CONF_OFFSET
,
144 offsetof(ngx_http_image_filter_conf_t
, transparency
),
147 { ngx_string("image_filter_interlace"),
148 NGX_HTTP_MAIN_CONF
|NGX_HTTP_SRV_CONF
|NGX_HTTP_LOC_CONF
|NGX_CONF_FLAG
,
149 ngx_conf_set_flag_slot
,
150 NGX_HTTP_LOC_CONF_OFFSET
,
151 offsetof(ngx_http_image_filter_conf_t
, interlace
),
154 { ngx_string("image_filter_buffer"),
155 NGX_HTTP_MAIN_CONF
|NGX_HTTP_SRV_CONF
|NGX_HTTP_LOC_CONF
|NGX_CONF_TAKE1
,
156 ngx_conf_set_size_slot
,
157 NGX_HTTP_LOC_CONF_OFFSET
,
158 offsetof(ngx_http_image_filter_conf_t
, buffer_size
),
165 static ngx_http_module_t ngx_http_image_filter_module_ctx
= {
166 NULL
, /* preconfiguration */
167 ngx_http_image_filter_init
, /* postconfiguration */
169 NULL
, /* create main configuration */
170 NULL
, /* init main configuration */
172 NULL
, /* create server configuration */
173 NULL
, /* merge server configuration */
175 ngx_http_image_filter_create_conf
, /* create location configuration */
176 ngx_http_image_filter_merge_conf
/* merge location configuration */
180 ngx_module_t ngx_http_image_filter_module
= {
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 */
192 NGX_MODULE_V1_PADDING
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
;
200 static ngx_str_t ngx_http_image_types
[] = {
201 ngx_string("image/jpeg"),
202 ngx_string("image/gif"),
203 ngx_string("image/png")
208 ngx_http_image_header_filter(ngx_http_request_t
*r
)
211 ngx_http_image_filter_ctx_t
*ctx
;
212 ngx_http_image_filter_conf_t
*conf
;
214 if (r
->headers_out
.status
== NGX_HTTP_NOT_MODIFIED
) {
215 return ngx_http_next_header_filter(r
);
218 ctx
= ngx_http_get_module_ctx(r
, ngx_http_image_filter_module
);
221 ngx_http_set_ctx(r
, NULL
, ngx_http_image_filter_module
);
222 return ngx_http_next_header_filter(r
);
225 conf
= ngx_http_get_module_loc_conf(r
, ngx_http_image_filter_module
);
227 if (conf
->filter
== NGX_HTTP_IMAGE_OFF
) {
228 return ngx_http_next_header_filter(r
);
231 if (r
->headers_out
.content_type
.len
232 >= sizeof("multipart/x-mixed-replace") - 1
233 && ngx_strncasecmp(r
->headers_out
.content_type
.data
,
234 (u_char
*) "multipart/x-mixed-replace",
235 sizeof("multipart/x-mixed-replace") - 1)
238 ngx_log_error(NGX_LOG_ERR
, r
->connection
->log
, 0,
239 "image filter: multipart/x-mixed-replace response");
244 ctx
= ngx_pcalloc(r
->pool
, sizeof(ngx_http_image_filter_ctx_t
));
249 ngx_http_set_ctx(r
, ctx
, ngx_http_image_filter_module
);
251 len
= r
->headers_out
.content_length_n
;
253 if (len
!= -1 && len
> (off_t
) conf
->buffer_size
) {
254 ngx_log_error(NGX_LOG_ERR
, r
->connection
->log
, 0,
255 "image filter: too big response: %O", len
);
257 return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE
;
261 ctx
->length
= conf
->buffer_size
;
264 ctx
->length
= (size_t) len
;
267 if (r
->headers_out
.refresh
) {
268 r
->headers_out
.refresh
->hash
= 0;
271 r
->main_filter_need_in_memory
= 1;
279 ngx_http_image_body_filter(ngx_http_request_t
*r
, ngx_chain_t
*in
)
284 ngx_http_image_filter_ctx_t
*ctx
;
285 ngx_http_image_filter_conf_t
*conf
;
287 ngx_log_debug0(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0, "image filter");
290 return ngx_http_next_body_filter(r
, in
);
293 ctx
= ngx_http_get_module_ctx(r
, ngx_http_image_filter_module
);
296 return ngx_http_next_body_filter(r
, in
);
299 switch (ctx
->phase
) {
301 case NGX_HTTP_IMAGE_START
:
303 ctx
->type
= ngx_http_image_test(r
, in
);
305 conf
= ngx_http_get_module_loc_conf(r
, ngx_http_image_filter_module
);
307 if (ctx
->type
== NGX_HTTP_IMAGE_NONE
) {
309 if (conf
->filter
== NGX_HTTP_IMAGE_SIZE
) {
310 out
.buf
= ngx_http_image_json(r
, NULL
);
314 ctx
->phase
= NGX_HTTP_IMAGE_DONE
;
316 return ngx_http_image_send(r
, ctx
, &out
);
320 return ngx_http_filter_finalize_request(r
,
321 &ngx_http_image_filter_module
,
322 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE
);
325 /* override content type */
327 ct
= &ngx_http_image_types
[ctx
->type
- 1];
328 r
->headers_out
.content_type_len
= ct
->len
;
329 r
->headers_out
.content_type
= *ct
;
330 r
->headers_out
.content_type_lowcase
= NULL
;
332 if (conf
->filter
== NGX_HTTP_IMAGE_TEST
) {
333 ctx
->phase
= NGX_HTTP_IMAGE_PASS
;
335 return ngx_http_image_send(r
, ctx
, in
);
338 ctx
->phase
= NGX_HTTP_IMAGE_READ
;
342 case NGX_HTTP_IMAGE_READ
:
344 rc
= ngx_http_image_read(r
, in
);
346 if (rc
== NGX_AGAIN
) {
350 if (rc
== NGX_ERROR
) {
351 return ngx_http_filter_finalize_request(r
,
352 &ngx_http_image_filter_module
,
353 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE
);
358 case NGX_HTTP_IMAGE_PROCESS
:
360 out
.buf
= ngx_http_image_process(r
);
362 if (out
.buf
== NULL
) {
363 return ngx_http_filter_finalize_request(r
,
364 &ngx_http_image_filter_module
,
365 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE
);
369 ctx
->phase
= NGX_HTTP_IMAGE_PASS
;
371 return ngx_http_image_send(r
, ctx
, &out
);
373 case NGX_HTTP_IMAGE_PASS
:
375 return ngx_http_next_body_filter(r
, in
);
377 default: /* NGX_HTTP_IMAGE_DONE */
379 rc
= ngx_http_next_body_filter(r
, NULL
);
381 /* NGX_ERROR resets any pending data */
382 return (rc
== NGX_OK
) ? NGX_ERROR
: rc
;
388 ngx_http_image_send(ngx_http_request_t
*r
, ngx_http_image_filter_ctx_t
*ctx
,
393 rc
= ngx_http_next_header_filter(r
);
395 if (rc
== NGX_ERROR
|| rc
> NGX_OK
|| r
->header_only
) {
399 rc
= ngx_http_next_body_filter(r
, in
);
401 if (ctx
->phase
== NGX_HTTP_IMAGE_DONE
) {
402 /* NGX_ERROR resets any pending data */
403 return (rc
== NGX_OK
) ? NGX_ERROR
: rc
;
411 ngx_http_image_test(ngx_http_request_t
*r
, ngx_chain_t
*in
)
417 if (in
->buf
->last
- p
< 16) {
418 return NGX_HTTP_IMAGE_NONE
;
421 ngx_log_debug2(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
422 "image filter: \"%c%c\"", p
[0], p
[1]);
424 if (p
[0] == 0xff && p
[1] == 0xd8) {
428 return NGX_HTTP_IMAGE_JPEG
;
430 } else if (p
[0] == 'G' && p
[1] == 'I' && p
[2] == 'F' && p
[3] == '8'
433 if (p
[4] == '9' || p
[4] == '7') {
435 return NGX_HTTP_IMAGE_GIF
;
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)
443 return NGX_HTTP_IMAGE_PNG
;
446 return NGX_HTTP_IMAGE_NONE
;
451 ngx_http_image_read(ngx_http_request_t
*r
, ngx_chain_t
*in
)
457 ngx_http_image_filter_ctx_t
*ctx
;
459 ctx
= ngx_http_get_module_ctx(r
, ngx_http_image_filter_module
);
461 if (ctx
->image
== NULL
) {
462 ctx
->image
= ngx_palloc(r
->pool
, ctx
->length
);
463 if (ctx
->image
== NULL
) {
467 ctx
->last
= ctx
->image
;
472 for (cl
= in
; cl
; cl
= cl
->next
) {
475 size
= b
->last
- b
->pos
;
477 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
478 "image buf: %uz", size
);
480 rest
= ctx
->image
+ ctx
->length
- p
;
481 size
= (rest
< size
) ? rest
: size
;
483 p
= ngx_cpymem(p
, b
->pos
, size
);
493 r
->connection
->buffered
|= NGX_HTTP_IMAGE_BUFFERED
;
500 ngx_http_image_process(ngx_http_request_t
*r
)
503 ngx_http_image_filter_ctx_t
*ctx
;
504 ngx_http_image_filter_conf_t
*conf
;
506 r
->connection
->buffered
&= ~NGX_HTTP_IMAGE_BUFFERED
;
508 ctx
= ngx_http_get_module_ctx(r
, ngx_http_image_filter_module
);
510 rc
= ngx_http_image_size(r
, ctx
);
512 conf
= ngx_http_get_module_loc_conf(r
, ngx_http_image_filter_module
);
514 if (conf
->filter
== NGX_HTTP_IMAGE_SIZE
) {
515 return ngx_http_image_json(r
, rc
== NGX_OK
? ctx
: NULL
);
518 ctx
->angle
= ngx_http_image_filter_get_value(r
, conf
->acv
, conf
->angle
);
520 if (conf
->filter
== NGX_HTTP_IMAGE_ROTATE
) {
522 if (ctx
->angle
!= 90 && ctx
->angle
!= 180 && ctx
->angle
!= 270) {
526 return ngx_http_image_resize(r
, ctx
);
529 ctx
->max_width
= ngx_http_image_filter_get_value(r
, conf
->wcv
, conf
->width
);
530 if (ctx
->max_width
== 0) {
534 ctx
->max_height
= ngx_http_image_filter_get_value(r
, conf
->hcv
,
536 if (ctx
->max_height
== 0) {
541 && ctx
->width
<= ctx
->max_width
542 && ctx
->height
<= ctx
->max_height
546 return ngx_http_image_asis(r
, ctx
);
549 return ngx_http_image_resize(r
, ctx
);
554 ngx_http_image_json(ngx_http_request_t
*r
, ngx_http_image_filter_ctx_t
*ctx
)
559 b
= ngx_pcalloc(r
->pool
, sizeof(ngx_buf_t
));
567 ngx_http_clean_header(r
);
569 r
->headers_out
.status
= NGX_HTTP_OK
;
570 ngx_str_set(&r
->headers_out
.content_type
, "text/plain");
571 r
->headers_out
.content_type_lowcase
= NULL
;
574 b
->pos
= (u_char
*) "{}" CRLF
;
575 b
->last
= b
->pos
+ sizeof("{}" CRLF
) - 1;
577 ngx_http_image_length(r
, b
);
582 len
= sizeof("{ \"img\" : "
583 "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF
) - 1
584 + 2 * NGX_SIZE_T_LEN
;
586 b
->pos
= ngx_pnalloc(r
->pool
, len
);
587 if (b
->pos
== NULL
) {
591 b
->last
= ngx_sprintf(b
->pos
,
595 " \"type\": \"%s\" } }" CRLF
,
596 ctx
->width
, ctx
->height
,
597 ngx_http_image_types
[ctx
->type
- 1].data
+ 6);
599 ngx_http_image_length(r
, b
);
606 ngx_http_image_asis(ngx_http_request_t
*r
, ngx_http_image_filter_ctx_t
*ctx
)
610 b
= ngx_pcalloc(r
->pool
, sizeof(ngx_buf_t
));
620 ngx_http_image_length(r
, b
);
627 ngx_http_image_length(ngx_http_request_t
*r
, ngx_buf_t
*b
)
629 r
->headers_out
.content_length_n
= b
->last
- b
->pos
;
631 if (r
->headers_out
.content_length
) {
632 r
->headers_out
.content_length
->hash
= 0;
635 r
->headers_out
.content_length
= NULL
;
640 ngx_http_image_size(ngx_http_request_t
*r
, ngx_http_image_filter_ctx_t
*ctx
)
644 ngx_uint_t width
, height
;
650 case NGX_HTTP_IMAGE_JPEG
:
653 last
= ctx
->image
+ ctx
->length
- 10;
660 if (p
[0] == 0xff && p
[1] != 0xff) {
662 ngx_log_debug2(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
663 "JPEG: %02xd %02xd", p
[0], p
[1]);
667 if ((*p
== 0xc0 || *p
== 0xc1 || *p
== 0xc2 || *p
== 0xc3
668 || *p
== 0xc9 || *p
== 0xca || *p
== 0xcb)
669 && (width
== 0 || height
== 0))
671 width
= p
[6] * 256 + p
[7];
672 height
= p
[4] * 256 + p
[5];
675 ngx_log_debug2(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
676 "JPEG: %02xd %02xd", p
[1], p
[2]);
678 len
= p
[1] * 256 + p
[2];
680 if (*p
>= 0xe1 && *p
<= 0xef) {
681 /* application data, e.g., EXIF, Adobe XMP, etc. */
693 if (width
== 0 || height
== 0) {
697 if (ctx
->length
/ 20 < app
) {
698 /* force conversion if application data consume more than 5% */
700 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
701 "app data size: %uz", app
);
706 case NGX_HTTP_IMAGE_GIF
:
708 if (ctx
->length
< 10) {
712 width
= p
[7] * 256 + p
[6];
713 height
= p
[9] * 256 + p
[8];
717 case NGX_HTTP_IMAGE_PNG
:
719 if (ctx
->length
< 24) {
723 width
= p
[18] * 256 + p
[19];
724 height
= p
[22] * 256 + p
[23];
733 ngx_log_debug2(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
734 "image size: %d x %d", width
, height
);
737 ctx
->height
= height
;
744 ngx_http_image_resize(ngx_http_request_t
*r
, ngx_http_image_filter_ctx_t
*ctx
)
746 int sx
, sy
, dx
, dy
, ox
, oy
, ax
, ay
, size
,
747 colors
, palette
, transparent
, sharpen
,
753 ngx_pool_cleanup_t
*cln
;
754 ngx_http_image_filter_conf_t
*conf
;
756 src
= ngx_http_image_source(r
, ctx
);
765 conf
= ngx_http_get_module_loc_conf(r
, ngx_http_image_filter_module
);
769 && (ngx_uint_t
) sx
<= ctx
->max_width
770 && (ngx_uint_t
) sy
<= ctx
->max_height
)
773 return ngx_http_image_asis(r
, ctx
);
776 colors
= gdImageColorsTotal(src
);
778 if (colors
&& conf
->transparency
) {
779 transparent
= gdImageGetTransparent(src
);
781 if (transparent
!= -1) {
783 red
= gdImageRed(src
, transparent
);
784 green
= gdImageGreen(src
, transparent
);
785 blue
= gdImageBlue(src
, transparent
);
799 gdImageColorTransparent(src
, -1);
804 if (conf
->filter
== NGX_HTTP_IMAGE_RESIZE
) {
806 if ((ngx_uint_t
) dx
> ctx
->max_width
) {
807 dy
= dy
* ctx
->max_width
/ dx
;
812 if ((ngx_uint_t
) dy
> ctx
->max_height
) {
813 dx
= dx
* ctx
->max_height
/ dy
;
815 dy
= ctx
->max_height
;
820 } else if (conf
->filter
== NGX_HTTP_IMAGE_ROTATE
) {
824 } else { /* NGX_HTTP_IMAGE_CROP */
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
;
837 if ((ngx_uint_t
) dy
> ctx
->max_height
) {
838 dx
= dx
* ctx
->max_height
/ dy
;
840 dy
= ctx
->max_height
;
847 dst
= ngx_http_image_new(r
, dx
, dy
, palette
);
854 gdImageSaveAlpha(dst
, 1);
855 gdImageAlphaBlending(dst
, 0);
858 gdImageCopyResampled(dst
, src
, 0, 0, 0, 0, dx
, dy
, sx
, sy
);
861 gdImageTrueColorToPalette(dst
, 1, 256);
873 ax
= (dx
% 2 == 0) ? 1 : 0;
874 ay
= (dy
% 2 == 0) ? 1 : 0;
876 switch (ctx
->angle
) {
880 dst
= ngx_http_image_new(r
, dy
, dx
, palette
);
885 if (ctx
->angle
== 90) {
894 gdImageCopyRotated(dst
, src
, ox
, oy
, 0, 0,
895 dx
+ ax
, dy
+ ay
, ctx
->angle
);
904 dst
= ngx_http_image_new(r
, dx
, dy
, palette
);
909 gdImageCopyRotated(dst
, src
, dx
/ 2 - ax
, dy
/ 2 - ay
, 0, 0,
910 dx
+ ax
, dy
+ ay
, ctx
->angle
);
916 if (conf
->filter
== NGX_HTTP_IMAGE_CROP
) {
920 if ((ngx_uint_t
) dx
> ctx
->max_width
) {
921 ox
= dx
- ctx
->max_width
;
927 if ((ngx_uint_t
) dy
> ctx
->max_height
) {
928 oy
= dy
- ctx
->max_height
;
936 dst
= ngx_http_image_new(r
, dx
- ox
, dy
- oy
, colors
);
946 ngx_log_debug4(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
947 "image crop: %d x %d @ %d x %d",
951 gdImageSaveAlpha(dst
, 1);
952 gdImageAlphaBlending(dst
, 0);
955 gdImageCopy(dst
, src
, 0, 0, ox
, oy
, dx
- ox
, dy
- oy
);
958 gdImageTrueColorToPalette(dst
, 1, 256);
965 if (transparent
!= -1 && colors
) {
966 gdImageColorTransparent(dst
, gdImageColorExact(dst
, red
, green
, blue
));
969 sharpen
= ngx_http_image_filter_get_value(r
, conf
->shcv
, conf
->sharpen
);
971 gdImageSharpen(dst
, sharpen
);
974 gdImageInterlace(dst
, (int) conf
->interlace
);
976 out
= ngx_http_image_out(r
, ctx
->type
, dst
, &size
);
978 ngx_log_debug3(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
979 "image: %d x %d %d", sx
, sy
, colors
);
982 ngx_pfree(r
->pool
, ctx
->image
);
988 cln
= ngx_pool_cleanup_add(r
->pool
, 0);
994 b
= ngx_pcalloc(r
->pool
, sizeof(ngx_buf_t
));
1000 cln
->handler
= ngx_http_image_cleanup
;
1004 b
->last
= out
+ size
;
1008 ngx_http_image_length(r
, b
);
1015 ngx_http_image_source(ngx_http_request_t
*r
, ngx_http_image_filter_ctx_t
*ctx
)
1022 switch (ctx
->type
) {
1024 case NGX_HTTP_IMAGE_JPEG
:
1025 img
= gdImageCreateFromJpegPtr(ctx
->length
, ctx
->image
);
1026 failed
= "gdImageCreateFromJpegPtr() failed";
1029 case NGX_HTTP_IMAGE_GIF
:
1030 img
= gdImageCreateFromGifPtr(ctx
->length
, ctx
->image
);
1031 failed
= "gdImageCreateFromGifPtr() failed";
1034 case NGX_HTTP_IMAGE_PNG
:
1035 img
= gdImageCreateFromPngPtr(ctx
->length
, ctx
->image
);
1036 failed
= "gdImageCreateFromPngPtr() failed";
1040 failed
= "unknown image type";
1045 ngx_log_error(NGX_LOG_ERR
, r
->connection
->log
, 0, failed
);
1053 ngx_http_image_new(ngx_http_request_t
*r
, int w
, int h
, int colors
)
1058 img
= gdImageCreateTrueColor(w
, h
);
1061 ngx_log_error(NGX_LOG_ERR
, r
->connection
->log
, 0,
1062 "gdImageCreateTrueColor() failed");
1067 img
= gdImageCreate(w
, h
);
1070 ngx_log_error(NGX_LOG_ERR
, r
->connection
->log
, 0,
1071 "gdImageCreate() failed");
1081 ngx_http_image_out(ngx_http_request_t
*r
, ngx_uint_t type
, gdImagePtr img
,
1087 ngx_http_image_filter_conf_t
*conf
;
1093 case NGX_HTTP_IMAGE_JPEG
:
1094 conf
= ngx_http_get_module_loc_conf(r
, ngx_http_image_filter_module
);
1096 jq
= ngx_http_image_filter_get_value(r
, conf
->jqcv
, conf
->jpeg_quality
);
1101 out
= gdImageJpegPtr(img
, size
, jq
);
1102 failed
= "gdImageJpegPtr() failed";
1105 case NGX_HTTP_IMAGE_GIF
:
1106 out
= gdImageGifPtr(img
, size
);
1107 failed
= "gdImageGifPtr() failed";
1110 case NGX_HTTP_IMAGE_PNG
:
1111 out
= gdImagePngPtr(img
, size
);
1112 failed
= "gdImagePngPtr() failed";
1116 failed
= "unknown image type";
1121 ngx_log_error(NGX_LOG_ERR
, r
->connection
->log
, 0, failed
);
1129 ngx_http_image_cleanup(void *data
)
1136 ngx_http_image_filter_get_value(ngx_http_request_t
*r
,
1137 ngx_http_complex_value_t
*cv
, ngx_uint_t v
)
1145 if (ngx_http_complex_value(r
, cv
, &val
) != NGX_OK
) {
1149 return ngx_http_image_filter_value(&val
);
1154 ngx_http_image_filter_value(ngx_str_t
*value
)
1158 if (value
->len
== 1 && value
->data
[0] == '-') {
1159 return (ngx_uint_t
) -1;
1162 n
= ngx_atoi(value
->data
, value
->len
);
1165 return (ngx_uint_t
) n
;
1173 ngx_http_image_filter_create_conf(ngx_conf_t
*cf
)
1175 ngx_http_image_filter_conf_t
*conf
;
1177 conf
= ngx_pcalloc(cf
->pool
, sizeof(ngx_http_image_filter_conf_t
));
1183 * set by ngx_pcalloc():
1191 * conf->jqcv = NULL;
1192 * conf->shcv = NULL;
1195 conf
->filter
= NGX_CONF_UNSET_UINT
;
1196 conf
->jpeg_quality
= NGX_CONF_UNSET_UINT
;
1197 conf
->sharpen
= NGX_CONF_UNSET_UINT
;
1198 conf
->transparency
= NGX_CONF_UNSET
;
1199 conf
->interlace
= NGX_CONF_UNSET
;
1200 conf
->buffer_size
= NGX_CONF_UNSET_SIZE
;
1207 ngx_http_image_filter_merge_conf(ngx_conf_t
*cf
, void *parent
, void *child
)
1209 ngx_http_image_filter_conf_t
*prev
= parent
;
1210 ngx_http_image_filter_conf_t
*conf
= child
;
1212 if (conf
->filter
== NGX_CONF_UNSET_UINT
) {
1214 if (prev
->filter
== NGX_CONF_UNSET_UINT
) {
1215 conf
->filter
= NGX_HTTP_IMAGE_OFF
;
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
;
1228 if (conf
->jpeg_quality
== NGX_CONF_UNSET_UINT
) {
1230 /* 75 is libjpeg default quality */
1231 ngx_conf_merge_uint_value(conf
->jpeg_quality
, prev
->jpeg_quality
, 75);
1233 if (conf
->jqcv
== NULL
) {
1234 conf
->jqcv
= prev
->jqcv
;
1238 if (conf
->sharpen
== NGX_CONF_UNSET_UINT
) {
1239 ngx_conf_merge_uint_value(conf
->sharpen
, prev
->sharpen
, 0);
1241 if (conf
->shcv
== NULL
) {
1242 conf
->shcv
= prev
->shcv
;
1246 ngx_conf_merge_value(conf
->transparency
, prev
->transparency
, 1);
1248 ngx_conf_merge_value(conf
->interlace
, prev
->interlace
, 0);
1250 ngx_conf_merge_size_value(conf
->buffer_size
, prev
->buffer_size
,
1258 ngx_http_image_filter(ngx_conf_t
*cf
, ngx_command_t
*cmd
, void *conf
)
1260 ngx_http_image_filter_conf_t
*imcf
= conf
;
1265 ngx_http_complex_value_t cv
;
1266 ngx_http_compile_complex_value_t ccv
;
1268 value
= cf
->args
->elts
;
1272 if (cf
->args
->nelts
== 2) {
1273 if (ngx_strcmp(value
[i
].data
, "off") == 0) {
1274 imcf
->filter
= NGX_HTTP_IMAGE_OFF
;
1276 } else if (ngx_strcmp(value
[i
].data
, "test") == 0) {
1277 imcf
->filter
= NGX_HTTP_IMAGE_TEST
;
1279 } else if (ngx_strcmp(value
[i
].data
, "size") == 0) {
1280 imcf
->filter
= NGX_HTTP_IMAGE_SIZE
;
1288 } else if (cf
->args
->nelts
== 3) {
1290 if (ngx_strcmp(value
[i
].data
, "rotate") == 0) {
1291 if (imcf
->filter
!= NGX_HTTP_IMAGE_RESIZE
1292 && imcf
->filter
!= NGX_HTTP_IMAGE_CROP
)
1294 imcf
->filter
= NGX_HTTP_IMAGE_ROTATE
;
1297 ngx_memzero(&ccv
, sizeof(ngx_http_compile_complex_value_t
));
1300 ccv
.value
= &value
[++i
];
1301 ccv
.complex_value
= &cv
;
1303 if (ngx_http_compile_complex_value(&ccv
) != NGX_OK
) {
1304 return NGX_CONF_ERROR
;
1307 if (cv
.lengths
== NULL
) {
1308 n
= ngx_http_image_filter_value(&value
[i
]);
1310 if (n
!= 90 && n
!= 180 && n
!= 270) {
1314 imcf
->angle
= (ngx_uint_t
) n
;
1317 imcf
->acv
= ngx_palloc(cf
->pool
,
1318 sizeof(ngx_http_complex_value_t
));
1319 if (imcf
->acv
== NULL
) {
1320 return NGX_CONF_ERROR
;
1333 if (ngx_strcmp(value
[i
].data
, "resize") == 0) {
1334 imcf
->filter
= NGX_HTTP_IMAGE_RESIZE
;
1336 } else if (ngx_strcmp(value
[i
].data
, "crop") == 0) {
1337 imcf
->filter
= NGX_HTTP_IMAGE_CROP
;
1343 ngx_memzero(&ccv
, sizeof(ngx_http_compile_complex_value_t
));
1346 ccv
.value
= &value
[++i
];
1347 ccv
.complex_value
= &cv
;
1349 if (ngx_http_compile_complex_value(&ccv
) != NGX_OK
) {
1350 return NGX_CONF_ERROR
;
1353 if (cv
.lengths
== NULL
) {
1354 n
= ngx_http_image_filter_value(&value
[i
]);
1360 imcf
->width
= (ngx_uint_t
) n
;
1363 imcf
->wcv
= ngx_palloc(cf
->pool
, sizeof(ngx_http_complex_value_t
));
1364 if (imcf
->wcv
== NULL
) {
1365 return NGX_CONF_ERROR
;
1371 ngx_memzero(&ccv
, sizeof(ngx_http_compile_complex_value_t
));
1374 ccv
.value
= &value
[++i
];
1375 ccv
.complex_value
= &cv
;
1377 if (ngx_http_compile_complex_value(&ccv
) != NGX_OK
) {
1378 return NGX_CONF_ERROR
;
1381 if (cv
.lengths
== NULL
) {
1382 n
= ngx_http_image_filter_value(&value
[i
]);
1388 imcf
->height
= (ngx_uint_t
) n
;
1391 imcf
->hcv
= ngx_palloc(cf
->pool
, sizeof(ngx_http_complex_value_t
));
1392 if (imcf
->hcv
== NULL
) {
1393 return NGX_CONF_ERROR
;
1403 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0, "invalid parameter \"%V\"",
1406 return NGX_CONF_ERROR
;
1411 ngx_http_image_filter_jpeg_quality(ngx_conf_t
*cf
, ngx_command_t
*cmd
,
1414 ngx_http_image_filter_conf_t
*imcf
= conf
;
1418 ngx_http_complex_value_t cv
;
1419 ngx_http_compile_complex_value_t ccv
;
1421 value
= cf
->args
->elts
;
1423 ngx_memzero(&ccv
, sizeof(ngx_http_compile_complex_value_t
));
1426 ccv
.value
= &value
[1];
1427 ccv
.complex_value
= &cv
;
1429 if (ngx_http_compile_complex_value(&ccv
) != NGX_OK
) {
1430 return NGX_CONF_ERROR
;
1433 if (cv
.lengths
== NULL
) {
1434 n
= ngx_http_image_filter_value(&value
[1]);
1437 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0,
1438 "invalid value \"%V\"", &value
[1]);
1439 return NGX_CONF_ERROR
;
1442 imcf
->jpeg_quality
= (ngx_uint_t
) n
;
1445 imcf
->jqcv
= ngx_palloc(cf
->pool
, sizeof(ngx_http_complex_value_t
));
1446 if (imcf
->jqcv
== NULL
) {
1447 return NGX_CONF_ERROR
;
1458 ngx_http_image_filter_sharpen(ngx_conf_t
*cf
, ngx_command_t
*cmd
,
1461 ngx_http_image_filter_conf_t
*imcf
= conf
;
1465 ngx_http_complex_value_t cv
;
1466 ngx_http_compile_complex_value_t ccv
;
1468 value
= cf
->args
->elts
;
1470 ngx_memzero(&ccv
, sizeof(ngx_http_compile_complex_value_t
));
1473 ccv
.value
= &value
[1];
1474 ccv
.complex_value
= &cv
;
1476 if (ngx_http_compile_complex_value(&ccv
) != NGX_OK
) {
1477 return NGX_CONF_ERROR
;
1480 if (cv
.lengths
== NULL
) {
1481 n
= ngx_http_image_filter_value(&value
[1]);
1484 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0,
1485 "invalid value \"%V\"", &value
[1]);
1486 return NGX_CONF_ERROR
;
1489 imcf
->sharpen
= (ngx_uint_t
) n
;
1492 imcf
->shcv
= ngx_palloc(cf
->pool
, sizeof(ngx_http_complex_value_t
));
1493 if (imcf
->shcv
== NULL
) {
1494 return NGX_CONF_ERROR
;
1505 ngx_http_image_filter_init(ngx_conf_t
*cf
)
1507 ngx_http_next_header_filter
= ngx_http_top_header_filter
;
1508 ngx_http_top_header_filter
= ngx_http_image_header_filter
;
1510 ngx_http_next_body_filter
= ngx_http_top_body_filter
;
1511 ngx_http_top_body_filter
= ngx_http_image_body_filter
;