Klaus Demo nginx / e2ada61
ngx_http_image_filter_module Igor Sysoev 13 years ago
6 changed file(s) with 1124 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
4444 . auto/lib/libxslt/conf
4545 fi
4646
47 if [ $USE_LIBGD = YES ]; then
48 . auto/lib/libgd/conf
49 fi
50
4751 if [ $USE_PERL = YES ]; then
4852 . auto/lib/perl/conf
4953 fi
0
1 # Copyright (C) Igor Sysoev
2
3
4 ngx_feature="GD library"
5 ngx_feature_name=
6 ngx_feature_run=no
7 ngx_feature_incs="#include <gd.h>"
8 ngx_feature_path=
9 ngx_feature_libs="-lgd"
10 ngx_feature_test="gdImagePtr img = gdImageCreateFromGifPtr(1, NULL);"
11 . auto/feature
12
13
14 if [ $ngx_found = no ]; then
15
16 # FreeBSD port
17
18 ngx_feature="GD library in /usr/local/"
19 ngx_feature_path="/usr/local/include"
20
21 if [ $NGX_RPATH = YES ]; then
22 ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lgd"
23 else
24 ngx_feature_libs="-L/usr/local/lib -lgd"
25 fi
26
27 . auto/feature
28 fi
29
30
31 if [ $ngx_found = no ]; then
32
33 # NetBSD port
34
35 ngx_feature="GD library in /usr/pkg/"
36 ngx_feature_path="/usr/pkg/include/"
37
38 if [ $NGX_RPATH = YES ]; then
39 ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lgd"
40 else
41 ngx_feature_libs="-L/usr/pkg/lib -lgd"
42 fi
43
44 . auto/feature
45 fi
46
47
48 if [ $ngx_found = no ]; then
49
50 # MacPorts
51
52 ngx_feature="GD library in /opt/local/"
53 ngx_feature_path="/opt/local/include"
54
55 if [ $NGX_RPATH = YES ]; then
56 ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lgd"
57 else
58 ngx_feature_libs="-L/opt/local/lib -lgd"
59 fi
60
61 . auto/feature
62 fi
63
64
65 if [ $ngx_found = yes ]; then
66
67 CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
68
69 else
70
71 cat << END
72
73 $0: error: the HTTP image filter module requires the GD library.
74 You can either do not enable the module or install the libraries.
75
76 END
77
78 exit 1
79
80 fi
147147 HTTP_SRCS="$HTTP_SRCS $HTTP_XSLT_SRCS"
148148 fi
149149
150 if [ $HTTP_IMAGE_FILTER = YES ]; then
151 USE_LIBGD=YES
152 HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_IMAGE_FILTER_MODULE"
153 HTTP_SRCS="$HTTP_SRCS $HTTP_IMAGE_SRCS"
154 fi
155
150156 if [ $HTTP_SUB = YES ]; then
151157 HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_SUB_FILTER_MODULE"
152158 HTTP_SRCS="$HTTP_SRCS $HTTP_SUB_SRCS"
5959 HTTP_POSTPONE=NO
6060 HTTP_REALIP=NO
6161 HTTP_XSLT=NO
62 HTTP_IMAGE_FILTER=NO
6263 HTTP_SUB=NO
6364 HTTP_ADDITION=NO
6465 HTTP_DAV=NO
122123 NGX_PERL=perl
123124
124125 USE_LIBXSLT=NO
126 USE_LIBGD=NO
125127
126128 NGX_GOOGLE_PERFTOOLS=NO
127129 NGX_CPP_TEST=NO
180182 --with-http_realip_module) HTTP_REALIP=YES ;;
181183 --with-http_addition_module) HTTP_ADDITION=YES ;;
182184 --with-http_xslt_module) HTTP_XSLT=YES ;;
185 --with-http_image_filter_module) HTTP_IMAGE_FILTER=YES ;;
183186 --with-http_sub_module) HTTP_SUB=YES ;;
184187 --with-http_dav_module) HTTP_DAV=YES ;;
185188 --with-http_flv_module) HTTP_FLV=YES ;;
327327 HTTP_XSLT_SRCS=src/http/modules/ngx_http_xslt_filter_module.c
328328
329329
330 HTTP_IMAGE_FILTER_MODULE=ngx_http_image_filter_module
331 HTTP_IMAGE_SRCS=src/http/modules/ngx_http_image_filter_module.c
332
333
330334 HTTP_SUB_FILTER_MODULE=ngx_http_sub_filter_module
331335 HTTP_SUB_SRCS=src/http/modules/ngx_http_sub_filter_module.c
332336
0
1 /*
2 * Copyright (C) Igor Sysoev
3 */
4
5
6 #include <ngx_config.h>
7 #include <ngx_core.h>
8 #include <ngx_http.h>
9 #include "gd.h"
10
11
12 #define NGX_HTTP_IMAGE_OFF 0
13 #define NGX_HTTP_IMAGE_TEST 1
14 #define NGX_HTTP_IMAGE_SIZE 2
15 #define NGX_HTTP_IMAGE_RESIZE 3
16 #define NGX_HTTP_IMAGE_CROP 4
17
18
19 #define NGX_HTTP_IMAGE_START 0
20 #define NGX_HTTP_IMAGE_READ 1
21 #define NGX_HTTP_IMAGE_PROCESS 2
22 #define NGX_HTTP_IMAGE_DONE 3
23
24
25 #define NGX_HTTP_IMAGE_NONE 0
26 #define NGX_HTTP_IMAGE_JPEG 1
27 #define NGX_HTTP_IMAGE_GIF 2
28 #define NGX_HTTP_IMAGE_PNG 3
29
30
31 #define NGX_HTTP_IMAGE_BUFFERED 0x08
32
33
34 typedef struct {
35 ngx_uint_t filter;
36 ngx_uint_t width;
37 ngx_uint_t height;
38
39 size_t buffer_size;
40 } ngx_http_image_filter_conf_t;
41
42
43 typedef struct {
44 u_char *image;
45 u_char *last;
46
47 size_t length;
48
49 ngx_uint_t width;
50 ngx_uint_t height;
51
52 ngx_uint_t phase;
53 ngx_uint_t type;
54 } ngx_http_image_filter_ctx_t;
55
56
57 static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
58 static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
59 static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
60 static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
61 ngx_http_image_filter_ctx_t *ctx);
62 static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
63 ngx_http_image_filter_ctx_t *ctx);
64 static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
65 static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
66 ngx_http_image_filter_ctx_t *ctx);
67
68 static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
69 ngx_http_image_filter_ctx_t *ctx);
70 static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
71 ngx_http_image_filter_ctx_t *ctx);
72 static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
73 int colors);
74 static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
75 gdImagePtr img, int *size);
76 static void ngx_http_image_cleanup(void *data);
77
78
79 static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
80 static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
81 void *child);
82 static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
83 void *conf);
84 static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);
85
86
87 static ngx_command_t ngx_http_image_filter_commands[] = {
88
89 { ngx_string("image_filter"),
90 NGX_HTTP_LOC_CONF|NGX_CONF_TAKE13,
91 ngx_http_image_filter,
92 NGX_HTTP_LOC_CONF_OFFSET,
93 0,
94 NULL },
95
96 { ngx_string("image_filter_buffer"),
97 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
98 ngx_conf_set_size_slot,
99 NGX_HTTP_LOC_CONF_OFFSET,
100 offsetof(ngx_http_image_filter_conf_t, buffer_size),
101 NULL },
102
103 ngx_null_command
104 };
105
106
107 static ngx_http_module_t ngx_http_image_filter_module_ctx = {
108 NULL, /* preconfiguration */
109 ngx_http_image_filter_init, /* postconfiguration */
110
111 NULL, /* create main configuration */
112 NULL, /* init main configuration */
113
114 NULL, /* create server configuration */
115 NULL, /* merge server configuration */
116
117 ngx_http_image_filter_create_conf, /* create location configuration */
118 ngx_http_image_filter_merge_conf /* merge location configuration */
119 };
120
121
122 ngx_module_t ngx_http_image_filter_module = {
123 NGX_MODULE_V1,
124 &ngx_http_image_filter_module_ctx, /* module context */
125 ngx_http_image_filter_commands, /* module directives */
126 NGX_HTTP_MODULE, /* module type */
127 NULL, /* init master */
128 NULL, /* init module */
129 NULL, /* init process */
130 NULL, /* init thread */
131 NULL, /* exit thread */
132 NULL, /* exit process */
133 NULL, /* exit master */
134 NGX_MODULE_V1_PADDING
135 };
136
137
138 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
139 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
140
141
142 static ngx_str_t ngx_http_image_types[] = {
143 ngx_string("image/jpeg"),
144 ngx_string("image/gif"),
145 ngx_string("image/png")
146 };
147
148
149 static ngx_int_t
150 ngx_http_image_header_filter(ngx_http_request_t *r)
151 {
152 off_t len;
153 ngx_http_image_filter_ctx_t *ctx;
154 ngx_http_image_filter_conf_t *conf;
155
156 if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
157 return ngx_http_next_header_filter(r);
158 }
159
160 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
161
162 if (conf->filter == NGX_HTTP_IMAGE_OFF) {
163 return ngx_http_next_header_filter(r);
164 }
165
166 if (r->headers_out.content_type.len
167 >= sizeof("multipart/x-mixed-replace") - 1
168 && ngx_strncasecmp(r->headers_out.content_type.data,
169 (u_char *) "multipart/x-mixed-replace",
170 sizeof("multipart/x-mixed-replace") - 1)
171 == 0)
172 {
173 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
174 "image filter: multipart/x-mixed-replace response");
175
176 return NGX_ERROR;
177 }
178
179 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
180 if (ctx == NULL) {
181 return NGX_ERROR;
182 }
183
184 ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);
185
186 len = r->headers_out.content_length_n;
187
188 if (len != -1 && len > conf->buffer_size) {
189 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
190 "image filter: too big response: %O", len);
191
192 return NGX_ERROR;
193 }
194
195 if (len == -1) {
196 ctx->length = conf->buffer_size;
197
198 } else {
199 ctx->length = (size_t) len;
200 }
201
202 if (r->headers_out.refresh) {
203 r->headers_out.refresh->hash = 0;
204 }
205
206 r->main_filter_need_in_memory = 1;
207 r->allow_ranges = 0;
208
209 return NGX_OK;
210 }
211
212
213 static ngx_int_t
214 ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
215 {
216 ngx_int_t rc;
217 ngx_str_t *ct;
218 ngx_chain_t out;
219 ngx_http_image_filter_ctx_t *ctx;
220 ngx_http_image_filter_conf_t *conf;
221
222 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");
223
224 if (in == NULL) {
225 return ngx_http_next_body_filter(r, in);
226 }
227
228 ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
229
230 if (ctx == NULL) {
231 return ngx_http_next_body_filter(r, in);
232 }
233
234 switch (ctx->phase) {
235
236 case NGX_HTTP_IMAGE_START:
237
238 ctx->type = ngx_http_image_test(r, in);
239
240 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
241
242 if (ctx->type == NGX_HTTP_IMAGE_NONE) {
243
244 if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
245 out.buf = ngx_http_image_json(r, NULL);
246
247 if (out.buf) {
248 out.next = NULL;
249 in = &out;
250
251 break;
252 }
253 }
254
255 return ngx_http_filter_finalize_request(r,
256 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
257 }
258
259 /* override content type */
260
261 ct = &ngx_http_image_types[ctx->type - 1];
262 r->headers_out.content_type_len = ct->len;
263 r->headers_out.content_type = *ct;
264
265 if (conf->filter == NGX_HTTP_IMAGE_TEST) {
266 break;
267 }
268
269 ctx->phase = NGX_HTTP_IMAGE_READ;
270
271 /* fall through */
272
273 case NGX_HTTP_IMAGE_READ:
274
275 rc = ngx_http_image_read(r, in);
276
277 if (rc == NGX_AGAIN) {
278 return NGX_OK;
279 }
280
281 if (rc == NGX_ERROR) {
282 return ngx_http_filter_finalize_request(r,
283 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
284 }
285
286 /* fall through */
287
288 case NGX_HTTP_IMAGE_PROCESS:
289
290 out.buf = ngx_http_image_process(r);
291
292 if (out.buf == NULL) {
293 return ngx_http_filter_finalize_request(r,
294 NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
295 }
296
297 out.next = NULL;
298 in = &out;
299
300 break;
301
302 default: /* NGX_HTTP_IMAGE_DONE */
303
304 return ngx_http_next_body_filter(r, in);
305 }
306
307 ctx->phase = NGX_HTTP_IMAGE_DONE;
308
309 rc = ngx_http_next_header_filter(r);
310
311 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
312 return rc;
313 }
314
315 return ngx_http_next_body_filter(r, in);
316 }
317
318
319 static ngx_uint_t
320 ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
321 {
322 u_char *p;
323
324 p = in->buf->pos;
325
326 if (in->buf->last - p < 16) {
327 return NGX_HTTP_IMAGE_NONE;
328 }
329
330 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
331 "image filter: \"%c%c\"", p[0], p[1]);
332
333 if (p[0] == 0xff && p[1] == 0xd8) {
334
335 /* JPEG */
336
337 return NGX_HTTP_IMAGE_JPEG;
338
339 } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
340 && p[4] == '9' && p[5] == 'a')
341 {
342 /* GIF */
343
344 return NGX_HTTP_IMAGE_GIF;
345
346 } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
347 && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
348 {
349 /* PNG */
350
351 return NGX_HTTP_IMAGE_PNG;
352 }
353
354 return NGX_HTTP_IMAGE_NONE;
355 }
356
357
358 static ngx_int_t
359 ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
360 {
361 u_char *p;
362 size_t size, rest;
363 ngx_buf_t *b;
364 ngx_chain_t *cl;
365 ngx_http_image_filter_ctx_t *ctx;
366
367 ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
368
369 if (ctx->image == NULL) {
370 ctx->image = ngx_palloc(r->pool, ctx->length);
371 if (ctx->image == NULL) {
372 return NGX_ERROR;
373 }
374
375 ctx->last = ctx->image;
376 }
377
378 p = ctx->last;
379
380 for (cl = in; cl; cl = cl->next) {
381
382 b = cl->buf;
383 size = b->last - b->pos;
384
385 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
386 "image buf: %uz", size);
387
388 rest = ctx->image + ctx->length - p;
389 size = (rest < size) ? rest : size;
390
391 p = ngx_cpymem(p, b->pos, size);
392 b->pos += size;
393
394 if (b->last_buf) {
395 ctx->last = p;
396 return NGX_OK;
397 }
398 }
399
400 ctx->last = p;
401 r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED;
402
403 return NGX_AGAIN;
404 }
405
406
407 static ngx_buf_t *
408 ngx_http_image_process(ngx_http_request_t *r)
409 {
410 ngx_buf_t *b;
411 ngx_int_t rc;
412 ngx_http_image_filter_ctx_t *ctx;
413 ngx_http_image_filter_conf_t *conf;
414
415 r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED;
416
417 ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
418
419 rc = ngx_http_image_size(r, ctx);
420
421 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
422
423 if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
424
425 b = ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
426
427 } else if (rc == NGX_OK
428 && ctx->width <= conf->width
429 && ctx->height <= conf->height)
430 {
431 b = ngx_http_image_asis(r, ctx);
432
433 } else {
434 b = ngx_http_image_resize(r, ctx);
435 }
436
437 return b;
438 }
439
440
441 static ngx_buf_t *
442 ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
443 {
444 size_t len;
445 ngx_buf_t *b;
446
447 b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
448 if (b == NULL) {
449 return NULL;
450 }
451
452 b->memory = 1;
453 b->last_buf = 1;
454
455 ngx_http_clean_header(r);
456
457 r->headers_out.status = NGX_HTTP_OK;
458 r->headers_out.content_type.len = sizeof("text/plain") - 1;
459 r->headers_out.content_type.data = (u_char *) "text/plain";
460
461 if (ctx == NULL) {
462 b->pos = (u_char *) "{}" CRLF;
463 b->last = b->pos + sizeof("{}" CRLF) - 1;
464
465 ngx_http_image_length(r, b);
466
467 return b;
468 }
469
470 len = sizeof("{ \"img\" : "
471 "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
472 + 2 * NGX_SIZE_T_LEN;
473
474 b->pos = ngx_pnalloc(r->pool, len);
475 if (b->pos == NULL) {
476 return NULL;
477 }
478
479 b->last = ngx_sprintf(b->pos,
480 "{ \"img\" : "
481 "{ \"width\": %uz,"
482 " \"height\": %uz,"
483 " \"type\": \"%s\" } }" CRLF,
484 ctx->width, ctx->height,
485 ngx_http_image_types[ctx->type - 1].data + 6);
486
487 ngx_http_image_length(r, b);
488
489 return b;
490 }
491
492
493 static ngx_buf_t *
494 ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
495 {
496 ngx_buf_t *b;
497
498 b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
499 if (b == NULL) {
500 return NULL;
501 }
502
503 b->pos = ctx->image;
504 b->last = ctx->last;
505 b->memory = 1;
506 b->last_buf = 1;
507
508 ngx_http_image_length(r, b);
509
510 return b;
511 }
512
513
514 static void
515 ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
516 {
517 r->headers_out.content_length_n = b->last - b->pos;
518
519 if (r->headers_out.content_length) {
520 r->headers_out.content_length->hash = 0;
521 }
522
523 r->headers_out.content_length = NULL;
524 }
525
526
527 static ngx_int_t
528 ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
529 {
530 u_char *p, *last;
531 ngx_uint_t width, height;
532
533 p = ctx->image;
534
535 switch (ctx->type) {
536
537 case NGX_HTTP_IMAGE_JPEG:
538
539 p += 2;
540 last = ctx->image + ctx->length - 10;
541
542 while (p < last) {
543
544 if (p[0] == 0xff && p[1] != 0xff) {
545
546 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
547 "JPEG: %02xd %02xd", *p, *(p + 1));
548
549 p++;
550
551 if (*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
552 || *p == 0xc9 || *p == 0xca || *p == 0xcb)
553 {
554 goto found;
555 }
556
557 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
558 "JPEG: %02xd %02xd", p[1], p[2]);
559
560 p += p[1] * 256 + p[2];
561
562 continue;
563 }
564
565 p++;
566 }
567
568 return NGX_DECLINED;
569
570 found:
571
572 width = p[6] * 256 + p[7];
573 height = p[4] * 256 + p[5];
574
575 break;
576
577 case NGX_HTTP_IMAGE_GIF:
578
579 if (ctx->length < 10) {
580 return NGX_DECLINED;
581 }
582
583 width = p[7] * 256 + p[6];
584 height = p[9] * 256 + p[8];
585
586 break;
587
588 case NGX_HTTP_IMAGE_PNG:
589
590 if (ctx->length < 24) {
591 return NGX_DECLINED;
592 }
593
594 width = p[18] * 256 + p[19];
595 height = p[22] * 256 + p[23];
596
597 break;
598
599 default:
600
601 return NGX_DECLINED;
602 }
603
604 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
605 "image size: %d x %d", width, height);
606
607 ctx->width = width;
608 ctx->height = height;
609
610 return NGX_OK;
611 }
612
613
614 static ngx_buf_t *
615 ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
616 {
617 int sx, sy, dx, dy, ox, oy,
618 colors, transparent, size;
619 u_char *out;
620 ngx_buf_t *b;
621 ngx_uint_t resize;
622 gdImagePtr src, dst;
623 ngx_pool_cleanup_t *cln;
624 ngx_http_image_filter_conf_t *conf;
625
626 src = ngx_http_image_source(r, ctx);
627
628 if (src == NULL) {
629 return NULL;
630 }
631
632 sx = gdImageSX(src);
633 sy = gdImageSY(src);
634
635 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
636
637 if ((ngx_uint_t) sx <= conf->width && (ngx_uint_t) sy <= conf->height) {
638 gdImageDestroy(src);
639 return ngx_http_image_asis(r, ctx);
640 }
641
642 colors = gdImageColorsTotal(src);
643 transparent = gdImageGetTransparent(src);
644
645 dx = sx;
646 dy = sy;
647
648 if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {
649
650 if ((ngx_uint_t) dx > conf->width) {
651 dy = dy * conf->width / dx;
652 dy = dy ? dy : 1;
653 dx = conf->width;
654 }
655
656 if ((ngx_uint_t) dy > conf->height) {
657 dx = dx * conf->height / dy;
658 dx = dx ? dx : 1;
659 dy = conf->height;
660 }
661
662 resize = 1;
663
664 } else { /* NGX_HTTP_IMAGE_CROP */
665
666 resize = 0;
667
668 if ((ngx_uint_t) (dx * 100 / dy) < conf->width * 100 / conf->height) {
669
670 if ((ngx_uint_t) dx > conf->width) {
671 dy = dy * conf->width / dx;
672 dy = dy ? dy : 1;
673 dx = conf->width;
674 resize = 1;
675 }
676
677 } else {
678 if ((ngx_uint_t) dy > conf->height) {
679 dx = dx * conf->height / dy;
680 dx = dx ? dx : 1;
681 dy = conf->height;
682 resize = 1;
683 }
684 }
685 }
686
687 if (resize) {
688 dst = ngx_http_image_new(r, dx, dy, colors);
689 if (dst == NULL) {
690 gdImageDestroy(src);
691 return NULL;
692 }
693
694 gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);
695
696 gdImageDestroy(src);
697
698 } else {
699 dst = src;
700 }
701
702 if (conf->filter == NGX_HTTP_IMAGE_CROP) {
703
704 src = dst;
705
706 if ((ngx_uint_t) dx > conf->width) {
707 ox = dx - conf->width;
708
709 } else {
710 ox = 0;
711 }
712
713 if ((ngx_uint_t) dy > conf->height) {
714 oy = dy - conf->height;
715
716 } else {
717 oy = 0;
718 }
719
720 if (ox || oy) {
721
722 dst = ngx_http_image_new(r, dx - ox, dy - oy, colors);
723
724 if (dst == NULL) {
725 gdImageDestroy(src);
726 return NULL;
727 }
728
729 ox /= 2;
730 oy /= 2;
731
732 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
733 "image crop: %d x %d @ %d x %d",
734 dx, dy, ox, oy);
735
736 gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy);
737
738 gdImageDestroy(src);
739 }
740 }
741
742 gdImageColorTransparent(dst, transparent);
743
744 out = ngx_http_image_out(r, ctx->type, dst, &size);
745
746 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
747 "image: %d x %d %d", sx, sy, colors);
748
749 gdImageDestroy(dst);
750 ngx_pfree(r->pool, ctx->image);
751
752 if (out == NULL) {
753 return NULL;
754 }
755
756 cln = ngx_pool_cleanup_add(r->pool, 0);
757 if (cln == NULL) {
758 gdFree(out);
759 return NULL;
760 }
761
762 b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
763 if (b == NULL) {
764 gdFree(out);
765 return NULL;
766 }
767
768 cln->handler = ngx_http_image_cleanup;
769 cln->data = out;
770
771 b->pos = out;
772 b->last = out + size;
773 b->memory = 1;
774 b->last_buf = 1;
775
776 ngx_http_image_length(r, b);
777
778 return b;
779 }
780
781
782 static gdImagePtr
783 ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
784 {
785 char *failed;
786 gdImagePtr img;
787
788 img = NULL;
789
790 switch (ctx->type) {
791
792 case NGX_HTTP_IMAGE_JPEG:
793 img = gdImageCreateFromJpegPtr(ctx->length, ctx->image);
794 failed = "gdImageCreateFromJpegPtr() failed";
795 break;
796
797 case NGX_HTTP_IMAGE_GIF:
798 img = gdImageCreateFromGifPtr(ctx->length, ctx->image);
799 failed = "gdImageCreateFromGifPtr() failed";
800 break;
801
802 case NGX_HTTP_IMAGE_PNG:
803 img = gdImageCreateFromPngPtr(ctx->length, ctx->image);
804 failed = "gdImageCreateFromPngPtr() failed";
805 break;
806
807 default:
808 failed = "unknown image type";
809 break;
810 }
811
812 if (img == NULL) {
813 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
814 }
815
816 return img;
817 }
818
819
820 static gdImagePtr
821 ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors)
822 {
823 gdImagePtr img;
824
825 if (colors == 0) {
826 img = gdImageCreateTrueColor(w, h);
827
828 if (img == NULL) {
829 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
830 "gdImageCreateTrueColor() failed");
831 return NULL;
832 }
833
834 } else {
835 img = gdImageCreate(w, h);
836
837 if (img == NULL) {
838 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
839 "gdImageCreate() failed");
840 return NULL;
841 }
842 }
843
844 return img;
845 }
846
847
848 static u_char *
849 ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img,
850 int *size)
851 {
852 char *failed;
853 u_char *out;
854
855 out = NULL;
856
857 switch (type) {
858
859 case NGX_HTTP_IMAGE_JPEG:
860 out = gdImageJpegPtr(img, size, /* default quality */ -1);
861 failed = "gdImageJpegPtr() failed";
862 break;
863
864 case NGX_HTTP_IMAGE_GIF:
865 out = gdImageGifPtr(img, size);
866 failed = "gdImageGifPtr() failed";
867 break;
868
869 case NGX_HTTP_IMAGE_PNG:
870 out = gdImagePngPtr(img, size);
871 failed = "gdImagePngPtr() failed";
872 break;
873
874 default:
875 failed = "unknown image type";
876 break;
877 }
878
879 if (out == NULL) {
880 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
881 }
882
883 return out;
884 }
885
886
887 static void
888 ngx_http_image_cleanup(void *data)
889 {
890 gdFree(data);
891 }
892
893
894 static void *
895 ngx_http_image_filter_create_conf(ngx_conf_t *cf)
896 {
897 ngx_http_image_filter_conf_t *conf;
898
899 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t));
900 if (conf == NULL) {
901 return NGX_CONF_ERROR;
902 }
903
904 conf->filter = NGX_CONF_UNSET_UINT;
905 conf->buffer_size = NGX_CONF_UNSET_SIZE;
906
907 return conf;
908 }
909
910
911 static char *
912 ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
913 {
914 ngx_http_image_filter_conf_t *prev = parent;
915 ngx_http_image_filter_conf_t *conf = child;
916
917 if (conf->filter == NGX_CONF_UNSET_UINT) {
918
919 if (prev->filter == NGX_CONF_UNSET_UINT) {
920 conf->filter = NGX_HTTP_IMAGE_OFF;
921
922 } else {
923 conf->filter = prev->filter;
924 conf->width = prev->width;
925 conf->height = prev->height;
926 }
927 }
928
929 ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
930 1 * 1024 * 1024);
931
932 return NGX_CONF_OK;
933 }
934
935
936 static char *
937 ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
938 {
939 ngx_http_image_filter_conf_t *imcf = conf;
940
941 ngx_str_t *value;
942 ngx_int_t n;
943 ngx_uint_t i;
944
945 value = cf->args->elts;
946
947 i = 1;
948
949 if (cf->args->nelts == 2) {
950 if (ngx_strcmp(value[i].data, "off") == 0) {
951 imcf->filter = NGX_HTTP_IMAGE_OFF;
952
953 } else if (ngx_strcmp(value[i].data, "test") == 0) {
954 imcf->filter = NGX_HTTP_IMAGE_TEST;
955
956 } else if (ngx_strcmp(value[i].data, "size") == 0) {
957 imcf->filter = NGX_HTTP_IMAGE_SIZE;
958
959 } else {
960 goto failed;
961 }
962
963 return NGX_CONF_OK;
964 }
965
966 if (ngx_strcmp(value[i].data, "resize") == 0) {
967 imcf->filter = NGX_HTTP_IMAGE_RESIZE;
968
969 } else if (ngx_strcmp(value[i].data, "crop") == 0) {
970 imcf->filter = NGX_HTTP_IMAGE_CROP;
971
972 } else {
973 goto failed;
974 }
975
976 i++;
977
978 if (value[i].len == 1 && value[i].data[0] == '-') {
979 imcf->width = (ngx_uint_t) -1;
980
981 } else {
982 n = ngx_atoi(value[i].data, value[i].len);
983 if (n == NGX_ERROR) {
984 goto failed;
985 }
986
987 imcf->width = (ngx_uint_t) n;
988 }
989
990 i++;
991
992 if (value[i].len == 1 && value[i].data[0] == '-') {
993 imcf->height = (ngx_uint_t) -1;
994
995 } else {
996 n = ngx_atoi(value[i].data, value[i].len);
997 if (n == NGX_ERROR) {
998 goto failed;
999 }
1000
1001 imcf->height = (ngx_uint_t) n;
1002 }
1003
1004 return NGX_CONF_OK;
1005
1006 failed:
1007
1008 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
1009 &value[i]);
1010
1011 return NGX_CONF_ERROR;
1012 }
1013
1014
1015 static ngx_int_t
1016 ngx_http_image_filter_init(ngx_conf_t *cf)
1017 {
1018 ngx_http_next_header_filter = ngx_http_top_header_filter;
1019 ngx_http_top_header_filter = ngx_http_image_header_filter;
1020
1021 ngx_http_next_body_filter = ngx_http_top_body_filter;
1022 ngx_http_top_body_filter = ngx_http_image_body_filter;
1023
1024 return NGX_OK;
1025 }