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
34 #define NGX_HTTP_IMAGE_WEBP 4
37 #define NGX_HTTP_IMAGE_BUFFERED 0x08
45 ngx_uint_t jpeg_quality;
46 ngx_uint_t webp_quality;
49 ngx_flag_t transparency;
52 ngx_http_complex_value_t *wcv;
53 ngx_http_complex_value_t *hcv;
54 ngx_http_complex_value_t *acv;
55 ngx_http_complex_value_t *jqcv;
56 ngx_http_complex_value_t *wqcv;
57 ngx_http_complex_value_t *shcv;
60 } ngx_http_image_filter_conf_t;
72 ngx_uint_t max_height;
78 } ngx_http_image_filter_ctx_t;
81 static ngx_int_t ngx_http_image_send(ngx_http_request_t *r,
82 ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in);
83 static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
84 static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
85 static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
86 static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
87 ngx_http_image_filter_ctx_t *ctx);
88 static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
89 ngx_http_image_filter_ctx_t *ctx);
90 static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
91 static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
92 ngx_http_image_filter_ctx_t *ctx);
94 static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
95 ngx_http_image_filter_ctx_t *ctx);
96 static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
97 ngx_http_image_filter_ctx_t *ctx);
98 static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
100 static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
101 gdImagePtr img, int *size);
102 static void ngx_http_image_cleanup(void *data);
103 static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r,
104 ngx_http_complex_value_t *cv, ngx_uint_t v);
105 static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value);
108 static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
109 static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
111 static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
113 static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf,
114 ngx_command_t *cmd, void *conf);
115 static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf,
116 ngx_command_t *cmd, void *conf);
117 static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
119 static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);
122 static ngx_command_t ngx_http_image_filter_commands[] = {
124 { ngx_string("image_filter"),
125 NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
126 ngx_http_image_filter,
127 NGX_HTTP_LOC_CONF_OFFSET,
131 { ngx_string("image_filter_jpeg_quality"),
132 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
133 ngx_http_image_filter_jpeg_quality,
134 NGX_HTTP_LOC_CONF_OFFSET,
138 { ngx_string("image_filter_webp_quality"),
139 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
140 ngx_http_image_filter_webp_quality,
141 NGX_HTTP_LOC_CONF_OFFSET,
145 { ngx_string("image_filter_sharpen"),
146 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
147 ngx_http_image_filter_sharpen,
148 NGX_HTTP_LOC_CONF_OFFSET,
152 { ngx_string("image_filter_transparency"),
153 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
154 ngx_conf_set_flag_slot,
155 NGX_HTTP_LOC_CONF_OFFSET,
156 offsetof(ngx_http_image_filter_conf_t, transparency),
159 { ngx_string("image_filter_interlace"),
160 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
161 ngx_conf_set_flag_slot,
162 NGX_HTTP_LOC_CONF_OFFSET,
163 offsetof(ngx_http_image_filter_conf_t, interlace),
166 { ngx_string("image_filter_buffer"),
167 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
168 ngx_conf_set_size_slot,
169 NGX_HTTP_LOC_CONF_OFFSET,
170 offsetof(ngx_http_image_filter_conf_t, buffer_size),
177 static ngx_http_module_t ngx_http_image_filter_module_ctx = {
178 NULL, /* preconfiguration */
179 ngx_http_image_filter_init, /* postconfiguration */
181 NULL, /* create main configuration */
182 NULL, /* init main configuration */
184 NULL, /* create server configuration */
185 NULL, /* merge server configuration */
187 ngx_http_image_filter_create_conf, /* create location configuration */
188 ngx_http_image_filter_merge_conf /* merge location configuration */
192 ngx_module_t ngx_http_image_filter_module = {
194 &ngx_http_image_filter_module_ctx, /* module context */
195 ngx_http_image_filter_commands, /* module directives */
196 NGX_HTTP_MODULE, /* module type */
197 NULL, /* init master */
198 NULL, /* init module */
199 NULL, /* init process */
200 NULL, /* init thread */
201 NULL, /* exit thread */
202 NULL, /* exit process */
203 NULL, /* exit master */
204 NGX_MODULE_V1_PADDING
208 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
209 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
212 static ngx_str_t ngx_http_image_types[] = {
213 ngx_string("image/jpeg"),
214 ngx_string("image/gif"),
215 ngx_string("image/png"),
216 ngx_string("image/webp")
221 ngx_http_image_header_filter(ngx_http_request_t *r)
224 ngx_http_image_filter_ctx_t *ctx;
225 ngx_http_image_filter_conf_t *conf;
227 if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
228 return ngx_http_next_header_filter(r);
231 ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
234 ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module);
235 return ngx_http_next_header_filter(r);
238 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
240 if (conf->filter == NGX_HTTP_IMAGE_OFF) {
241 return ngx_http_next_header_filter(r);
244 if (r->headers_out.content_type.len
245 >= sizeof("multipart/x-mixed-replace") - 1
246 && ngx_strncasecmp(r->headers_out.content_type.data,
247 (u_char *) "multipart/x-mixed-replace",
248 sizeof("multipart/x-mixed-replace") - 1)
251 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
252 "image filter: multipart/x-mixed-replace response");
257 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
262 ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);
264 len = r->headers_out.content_length_n;
266 if (len != -1 && len > (off_t) conf->buffer_size) {
267 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
268 "image filter: too big response: %O", len);
270 return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
274 ctx->length = conf->buffer_size;
277 ctx->length = (size_t) len;
280 if (r->headers_out.refresh) {
281 r->headers_out.refresh->hash = 0;
284 r->main_filter_need_in_memory = 1;
292 ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
297 ngx_http_image_filter_ctx_t *ctx;
298 ngx_http_image_filter_conf_t *conf;
300 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");
303 return ngx_http_next_body_filter(r, in);
306 ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
309 return ngx_http_next_body_filter(r, in);
312 switch (ctx->phase) {
314 case NGX_HTTP_IMAGE_START:
316 ctx->type = ngx_http_image_test(r, in);
318 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
320 if (ctx->type == NGX_HTTP_IMAGE_NONE) {
322 if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
323 out.buf = ngx_http_image_json(r, NULL);
327 ctx->phase = NGX_HTTP_IMAGE_DONE;
329 return ngx_http_image_send(r, ctx, &out);
333 return ngx_http_filter_finalize_request(r,
334 &ngx_http_image_filter_module,
335 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
338 /* override content type */
340 ct = &ngx_http_image_types[ctx->type - 1];
341 r->headers_out.content_type_len = ct->len;
342 r->headers_out.content_type = *ct;
343 r->headers_out.content_type_lowcase = NULL;
345 if (conf->filter == NGX_HTTP_IMAGE_TEST) {
346 ctx->phase = NGX_HTTP_IMAGE_PASS;
348 return ngx_http_image_send(r, ctx, in);
351 ctx->phase = NGX_HTTP_IMAGE_READ;
355 case NGX_HTTP_IMAGE_READ:
357 rc = ngx_http_image_read(r, in);
359 if (rc == NGX_AGAIN) {
363 if (rc == NGX_ERROR) {
364 return ngx_http_filter_finalize_request(r,
365 &ngx_http_image_filter_module,
366 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
371 case NGX_HTTP_IMAGE_PROCESS:
373 out.buf = ngx_http_image_process(r);
375 if (out.buf == NULL) {
376 return ngx_http_filter_finalize_request(r,
377 &ngx_http_image_filter_module,
378 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
382 ctx->phase = NGX_HTTP_IMAGE_PASS;
384 return ngx_http_image_send(r, ctx, &out);
386 case NGX_HTTP_IMAGE_PASS:
388 return ngx_http_next_body_filter(r, in);
390 default: /* NGX_HTTP_IMAGE_DONE */
392 rc = ngx_http_next_body_filter(r, NULL);
394 /* NGX_ERROR resets any pending data */
395 return (rc == NGX_OK) ? NGX_ERROR : rc;
401 ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx,
406 rc = ngx_http_next_header_filter(r);
408 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
412 rc = ngx_http_next_body_filter(r, in);
414 if (ctx->phase == NGX_HTTP_IMAGE_DONE) {
415 /* NGX_ERROR resets any pending data */
416 return (rc == NGX_OK) ? NGX_ERROR : rc;
424 ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
430 if (in->buf->last - p < 16) {
431 return NGX_HTTP_IMAGE_NONE;
434 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
435 "image filter: \"%c%c\"", p[0], p[1]);
437 if (p[0] == 0xff && p[1] == 0xd8) {
441 return NGX_HTTP_IMAGE_JPEG;
443 } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
446 if (p[4] == '9' || p[4] == '7') {
448 return NGX_HTTP_IMAGE_GIF;
451 } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
452 && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
456 return NGX_HTTP_IMAGE_PNG;
458 } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F'
459 && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P')
463 return NGX_HTTP_IMAGE_WEBP;
466 return NGX_HTTP_IMAGE_NONE;
471 ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
477 ngx_http_image_filter_ctx_t *ctx;
479 ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
481 if (ctx->image == NULL) {
482 ctx->image = ngx_palloc(r->pool, ctx->length);
483 if (ctx->image == NULL) {
487 ctx->last = ctx->image;
492 for (cl = in; cl; cl = cl->next) {
495 size = b->last - b->pos;
497 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
498 "image buf: %uz", size);
500 rest = ctx->image + ctx->length - p;
503 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
504 "image filter: too big response");
508 p = ngx_cpymem(p, b->pos, size);
518 r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED;
525 ngx_http_image_process(ngx_http_request_t *r)
528 ngx_http_image_filter_ctx_t *ctx;
529 ngx_http_image_filter_conf_t *conf;
531 r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED;
533 ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
535 rc = ngx_http_image_size(r, ctx);
537 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
539 if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
540 return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
543 ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle);
545 if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
547 if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) {
551 return ngx_http_image_resize(r, ctx);
554 ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width);
555 if (ctx->max_width == 0) {
559 ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv,
561 if (ctx->max_height == 0) {
566 && ctx->width <= ctx->max_width
567 && ctx->height <= ctx->max_height
571 return ngx_http_image_asis(r, ctx);
574 return ngx_http_image_resize(r, ctx);
579 ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
584 b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
592 ngx_http_clean_header(r);
594 r->headers_out.status = NGX_HTTP_OK;
595 r->headers_out.content_type_len = sizeof("application/json") - 1;
596 ngx_str_set(&r->headers_out.content_type, "application/json");
597 r->headers_out.content_type_lowcase = NULL;
600 b->pos = (u_char *) "{}" CRLF;
601 b->last = b->pos + sizeof("{}" CRLF) - 1;
603 ngx_http_image_length(r, b);
608 len = sizeof("{ \"img\" : "
609 "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
610 + 2 * NGX_SIZE_T_LEN;
612 b->pos = ngx_pnalloc(r->pool, len);
613 if (b->pos == NULL) {
617 b->last = ngx_sprintf(b->pos,
621 " \"type\": \"%s\" } }" CRLF,
622 ctx->width, ctx->height,
623 ngx_http_image_types[ctx->type - 1].data + 6);
625 ngx_http_image_length(r, b);
632 ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
636 b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
646 ngx_http_image_length(r, b);
653 ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
655 r->headers_out.content_length_n = b->last - b->pos;
657 if (r->headers_out.content_length) {
658 r->headers_out.content_length->hash = 0;
661 r->headers_out.content_length = NULL;
666 ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
670 ngx_uint_t width, height;
676 case NGX_HTTP_IMAGE_JPEG:
679 last = ctx->image + ctx->length - 10;
686 if (p[0] == 0xff && p[1] != 0xff) {
688 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
689 "JPEG: %02xd %02xd", p[0], p[1]);
693 if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
694 || *p == 0xc9 || *p == 0xca || *p == 0xcb)
695 && (width == 0 || height == 0))
697 width = p[6] * 256 + p[7];
698 height = p[4] * 256 + p[5];
701 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
702 "JPEG: %02xd %02xd", p[1], p[2]);
704 len = p[1] * 256 + p[2];
706 if (*p >= 0xe1 && *p <= 0xef) {
707 /* application data, e.g., EXIF, Adobe XMP, etc. */
719 if (width == 0 || height == 0) {
723 if (ctx->length / 20 < app) {
724 /* force conversion if application data consume more than 5% */
726 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
727 "app data size: %uz", app);
732 case NGX_HTTP_IMAGE_GIF:
734 if (ctx->length < 10) {
738 width = p[7] * 256 + p[6];
739 height = p[9] * 256 + p[8];
743 case NGX_HTTP_IMAGE_PNG:
745 if (ctx->length < 24) {
749 width = p[18] * 256 + p[19];
750 height = p[22] * 256 + p[23];
754 case NGX_HTTP_IMAGE_WEBP:
756 if (ctx->length < 30) {
760 if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') {
768 /* not a key frame */
772 if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) {
773 /* invalid start code */
777 width = (p[26] | p[27] << 8) & 0x3fff;
778 height = (p[28] | p[29] << 8) & 0x3fff;
784 /* invalid signature */
788 width = ((p[21] | p[22] << 8) & 0x3fff) + 1;
789 height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1;
794 width = (p[24] | p[25] << 8 | p[26] << 16) + 1;
795 height = (p[27] | p[28] << 8 | p[29] << 16) + 1;
809 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
810 "image size: %d x %d", (int) width, (int) height);
813 ctx->height = height;
820 ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
822 int sx, sy, dx, dy, ox, oy, ax, ay, size,
823 colors, palette, transparent, sharpen,
829 ngx_pool_cleanup_t *cln;
830 ngx_http_image_filter_conf_t *conf;
832 src = ngx_http_image_source(r, ctx);
841 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
845 && (ngx_uint_t) sx <= ctx->max_width
846 && (ngx_uint_t) sy <= ctx->max_height)
849 return ngx_http_image_asis(r, ctx);
852 colors = gdImageColorsTotal(src);
854 if (colors && conf->transparency) {
855 transparent = gdImageGetTransparent(src);
857 if (transparent != -1) {
859 red = gdImageRed(src, transparent);
860 green = gdImageGreen(src, transparent);
861 blue = gdImageBlue(src, transparent);
875 gdImageColorTransparent(src, -1);
880 if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {
882 if ((ngx_uint_t) dx > ctx->max_width) {
883 dy = dy * ctx->max_width / dx;
888 if ((ngx_uint_t) dy > ctx->max_height) {
889 dx = dx * ctx->max_height / dy;
891 dy = ctx->max_height;
896 } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
900 } else { /* NGX_HTTP_IMAGE_CROP */
904 if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) {
905 if ((ngx_uint_t) dx > ctx->max_width) {
906 dy = dy * ctx->max_width / dx;
913 if ((ngx_uint_t) dy > ctx->max_height) {
914 dx = dx * ctx->max_height / dy;
916 dy = ctx->max_height;
923 dst = ngx_http_image_new(r, dx, dy, palette);
930 gdImageSaveAlpha(dst, 1);
931 gdImageAlphaBlending(dst, 0);
934 gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);
937 gdImageTrueColorToPalette(dst, 1, 256);
949 ax = (dx % 2 == 0) ? 1 : 0;
950 ay = (dy % 2 == 0) ? 1 : 0;
952 switch (ctx->angle) {
956 dst = ngx_http_image_new(r, dy, dx, palette);
961 if (ctx->angle == 90) {
970 gdImageCopyRotated(dst, src, ox, oy, 0, 0,
971 dx + ax, dy + ay, ctx->angle);
980 dst = ngx_http_image_new(r, dx, dy, palette);
985 gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0,
986 dx + ax, dy + ay, ctx->angle);
992 if (conf->filter == NGX_HTTP_IMAGE_CROP) {
996 if ((ngx_uint_t) dx > ctx->max_width) {
997 ox = dx - ctx->max_width;
1003 if ((ngx_uint_t) dy > ctx->max_height) {
1004 oy = dy - ctx->max_height;
1012 dst = ngx_http_image_new(r, dx - ox, dy - oy, colors);
1015 gdImageDestroy(src);
1022 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1023 "image crop: %d x %d @ %d x %d",
1027 gdImageSaveAlpha(dst, 1);
1028 gdImageAlphaBlending(dst, 0);
1031 gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy);
1034 gdImageTrueColorToPalette(dst, 1, 256);
1037 gdImageDestroy(src);
1041 if (transparent != -1 && colors) {
1042 gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue));
1045 sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen);
1047 gdImageSharpen(dst, sharpen);
1050 gdImageInterlace(dst, (int) conf->interlace);
1052 out = ngx_http_image_out(r, ctx->type, dst, &size);
1054 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1055 "image: %d x %d %d", sx, sy, colors);
1057 gdImageDestroy(dst);
1058 ngx_pfree(r->pool, ctx->image);
1064 cln = ngx_pool_cleanup_add(r->pool, 0);
1070 b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
1076 cln->handler = ngx_http_image_cleanup;
1080 b->last = out + size;
1084 ngx_http_image_length(r, b);
1085 ngx_http_weak_etag(r);
1092 ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
1099 switch (ctx->type) {
1101 case NGX_HTTP_IMAGE_JPEG:
1102 img = gdImageCreateFromJpegPtr(ctx->length, ctx->image);
1103 failed = "gdImageCreateFromJpegPtr() failed";
1106 case NGX_HTTP_IMAGE_GIF:
1107 img = gdImageCreateFromGifPtr(ctx->length, ctx->image);
1108 failed = "gdImageCreateFromGifPtr() failed";
1111 case NGX_HTTP_IMAGE_PNG:
1112 img = gdImageCreateFromPngPtr(ctx->length, ctx->image);
1113 failed = "gdImageCreateFromPngPtr() failed";
1116 case NGX_HTTP_IMAGE_WEBP:
1117 #if (NGX_HAVE_GD_WEBP)
1118 img = gdImageCreateFromWebpPtr(ctx->length, ctx->image);
1119 failed = "gdImageCreateFromWebpPtr() failed";
1121 failed = "nginx was built without GD WebP support";
1126 failed = "unknown image type";
1131 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
1139 ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors)
1144 img = gdImageCreateTrueColor(w, h);
1147 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
1148 "gdImageCreateTrueColor() failed");
1153 img = gdImageCreate(w, h);
1156 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
1157 "gdImageCreate() failed");
1167 ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img,
1173 ngx_http_image_filter_conf_t *conf;
1179 case NGX_HTTP_IMAGE_JPEG:
1180 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
1182 q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality);
1187 out = gdImageJpegPtr(img, size, q);
1188 failed = "gdImageJpegPtr() failed";
1191 case NGX_HTTP_IMAGE_GIF:
1192 out = gdImageGifPtr(img, size);
1193 failed = "gdImageGifPtr() failed";
1196 case NGX_HTTP_IMAGE_PNG:
1197 out = gdImagePngPtr(img, size);
1198 failed = "gdImagePngPtr() failed";
1201 case NGX_HTTP_IMAGE_WEBP:
1202 #if (NGX_HAVE_GD_WEBP)
1203 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
1205 q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality);
1210 out = gdImageWebpPtrEx(img, size, q);
1211 failed = "gdImageWebpPtrEx() failed";
1213 failed = "nginx was built without GD WebP support";
1218 failed = "unknown image type";
1223 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
1231 ngx_http_image_cleanup(void *data)
1238 ngx_http_image_filter_get_value(ngx_http_request_t *r,
1239 ngx_http_complex_value_t *cv, ngx_uint_t v)
1247 if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
1251 return ngx_http_image_filter_value(&val);
1256 ngx_http_image_filter_value(ngx_str_t *value)
1260 if (value->len == 1 && value->data[0] == '-') {
1261 return (ngx_uint_t) -1;
1264 n = ngx_atoi(value->data, value->len);
1267 return (ngx_uint_t) n;
1275 ngx_http_image_filter_create_conf(ngx_conf_t *cf)
1277 ngx_http_image_filter_conf_t *conf;
1279 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t));
1285 * set by ngx_pcalloc():
1293 * conf->jqcv = NULL;
1294 * conf->wqcv = NULL;
1295 * conf->shcv = NULL;
1298 conf->filter = NGX_CONF_UNSET_UINT;
1299 conf->jpeg_quality = NGX_CONF_UNSET_UINT;
1300 conf->webp_quality = NGX_CONF_UNSET_UINT;
1301 conf->sharpen = NGX_CONF_UNSET_UINT;
1302 conf->transparency = NGX_CONF_UNSET;
1303 conf->interlace = NGX_CONF_UNSET;
1304 conf->buffer_size = NGX_CONF_UNSET_SIZE;
1311 ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
1313 ngx_http_image_filter_conf_t *prev = parent;
1314 ngx_http_image_filter_conf_t *conf = child;
1316 if (conf->filter == NGX_CONF_UNSET_UINT) {
1318 if (prev->filter == NGX_CONF_UNSET_UINT) {
1319 conf->filter = NGX_HTTP_IMAGE_OFF;
1322 conf->filter = prev->filter;
1323 conf->width = prev->width;
1324 conf->height = prev->height;
1325 conf->angle = prev->angle;
1326 conf->wcv = prev->wcv;
1327 conf->hcv = prev->hcv;
1328 conf->acv = prev->acv;
1332 if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) {
1334 /* 75 is libjpeg default quality */
1335 ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75);
1337 if (conf->jqcv == NULL) {
1338 conf->jqcv = prev->jqcv;
1342 if (conf->webp_quality == NGX_CONF_UNSET_UINT) {
1344 /* 80 is libwebp default quality */
1345 ngx_conf_merge_uint_value(conf->webp_quality, prev->webp_quality, 80);
1347 if (conf->wqcv == NULL) {
1348 conf->wqcv = prev->wqcv;
1352 if (conf->sharpen == NGX_CONF_UNSET_UINT) {
1353 ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0);
1355 if (conf->shcv == NULL) {
1356 conf->shcv = prev->shcv;
1360 ngx_conf_merge_value(conf->transparency, prev->transparency, 1);
1362 ngx_conf_merge_value(conf->interlace, prev->interlace, 0);
1364 ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
1372 ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1374 ngx_http_image_filter_conf_t *imcf = conf;
1379 ngx_http_complex_value_t cv;
1380 ngx_http_compile_complex_value_t ccv;
1382 value = cf->args->elts;
1386 if (cf->args->nelts == 2) {
1387 if (ngx_strcmp(value[i].data, "off") == 0) {
1388 imcf->filter = NGX_HTTP_IMAGE_OFF;
1390 } else if (ngx_strcmp(value[i].data, "test") == 0) {
1391 imcf->filter = NGX_HTTP_IMAGE_TEST;
1393 } else if (ngx_strcmp(value[i].data, "size") == 0) {
1394 imcf->filter = NGX_HTTP_IMAGE_SIZE;
1402 } else if (cf->args->nelts == 3) {
1404 if (ngx_strcmp(value[i].data, "rotate") == 0) {
1405 if (imcf->filter != NGX_HTTP_IMAGE_RESIZE
1406 && imcf->filter != NGX_HTTP_IMAGE_CROP)
1408 imcf->filter = NGX_HTTP_IMAGE_ROTATE;
1411 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1414 ccv.value = &value[++i];
1415 ccv.complex_value = &cv;
1417 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1418 return NGX_CONF_ERROR;
1421 if (cv.lengths == NULL) {
1422 n = ngx_http_image_filter_value(&value[i]);
1424 if (n != 90 && n != 180 && n != 270) {
1428 imcf->angle = (ngx_uint_t) n;
1431 imcf->acv = ngx_palloc(cf->pool,
1432 sizeof(ngx_http_complex_value_t));
1433 if (imcf->acv == NULL) {
1434 return NGX_CONF_ERROR;
1447 if (ngx_strcmp(value[i].data, "resize") == 0) {
1448 imcf->filter = NGX_HTTP_IMAGE_RESIZE;
1450 } else if (ngx_strcmp(value[i].data, "crop") == 0) {
1451 imcf->filter = NGX_HTTP_IMAGE_CROP;
1457 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1460 ccv.value = &value[++i];
1461 ccv.complex_value = &cv;
1463 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1464 return NGX_CONF_ERROR;
1467 if (cv.lengths == NULL) {
1468 n = ngx_http_image_filter_value(&value[i]);
1474 imcf->width = (ngx_uint_t) n;
1477 imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1478 if (imcf->wcv == NULL) {
1479 return NGX_CONF_ERROR;
1485 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1488 ccv.value = &value[++i];
1489 ccv.complex_value = &cv;
1491 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1492 return NGX_CONF_ERROR;
1495 if (cv.lengths == NULL) {
1496 n = ngx_http_image_filter_value(&value[i]);
1502 imcf->height = (ngx_uint_t) n;
1505 imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1506 if (imcf->hcv == NULL) {
1507 return NGX_CONF_ERROR;
1517 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
1520 return NGX_CONF_ERROR;
1525 ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd,
1528 ngx_http_image_filter_conf_t *imcf = conf;
1532 ngx_http_complex_value_t cv;
1533 ngx_http_compile_complex_value_t ccv;
1535 value = cf->args->elts;
1537 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1540 ccv.value = &value[1];
1541 ccv.complex_value = &cv;
1543 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1544 return NGX_CONF_ERROR;
1547 if (cv.lengths == NULL) {
1548 n = ngx_http_image_filter_value(&value[1]);
1551 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1552 "invalid value \"%V\"", &value[1]);
1553 return NGX_CONF_ERROR;
1556 imcf->jpeg_quality = (ngx_uint_t) n;
1559 imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1560 if (imcf->jqcv == NULL) {
1561 return NGX_CONF_ERROR;
1572 ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd,
1575 ngx_http_image_filter_conf_t *imcf = conf;
1579 ngx_http_complex_value_t cv;
1580 ngx_http_compile_complex_value_t ccv;
1582 value = cf->args->elts;
1584 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1587 ccv.value = &value[1];
1588 ccv.complex_value = &cv;
1590 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1591 return NGX_CONF_ERROR;
1594 if (cv.lengths == NULL) {
1595 n = ngx_http_image_filter_value(&value[1]);
1598 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1599 "invalid value \"%V\"", &value[1]);
1600 return NGX_CONF_ERROR;
1603 imcf->webp_quality = (ngx_uint_t) n;
1606 imcf->wqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1607 if (imcf->wqcv == NULL) {
1608 return NGX_CONF_ERROR;
1619 ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
1622 ngx_http_image_filter_conf_t *imcf = conf;
1626 ngx_http_complex_value_t cv;
1627 ngx_http_compile_complex_value_t ccv;
1629 value = cf->args->elts;
1631 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1634 ccv.value = &value[1];
1635 ccv.complex_value = &cv;
1637 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1638 return NGX_CONF_ERROR;
1641 if (cv.lengths == NULL) {
1642 n = ngx_http_image_filter_value(&value[1]);
1645 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1646 "invalid value \"%V\"", &value[1]);
1647 return NGX_CONF_ERROR;
1650 imcf->sharpen = (ngx_uint_t) n;
1653 imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1654 if (imcf->shcv == NULL) {
1655 return NGX_CONF_ERROR;
1666 ngx_http_image_filter_init(ngx_conf_t *cf)
1668 ngx_http_next_header_filter = ngx_http_top_header_filter;
1669 ngx_http_top_header_filter = ngx_http_image_header_filter;
1671 ngx_http_next_body_filter = ngx_http_top_body_filter;
1672 ngx_http_top_body_filter = ngx_http_image_body_filter;