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