diff --git a/.dockerignore b/.dockerignore index 97aca2e..063dbb5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ .env -node_modules \ No newline at end of file +node_modules +build/src/http/ diff --git a/Dockerfile b/Dockerfile index 450dcc2..014e966 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM ubuntu:18.04 AS buildstep ENV TERM=xterm container=docker DEBIAN_FRONTEND=noninteractive \ NGINX_DEVEL_KIT_VERSION=0.3.0 NGINX_SET_MISC_MODULE_VERSION=0.32 \ NGINX_VERSION=1.16.1 -ADD ./build/src/ /tmp/ +ADD ./build/ /tmp/ RUN bash /tmp/ubuntu.sh diff --git a/README.md b/README.md index 1f9bd26..903fa67 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,13 @@ image cropping with gravity, resize and compress on the fly with nginx **image_filter** module. A tiny docker container to build your own Cloudinary-like service. -Nginx module - https://github.com/niiknow/docker-nginx-image-proxy/blob/master/build/src/ngx_http_image_filter_module.c +Nginx module - https://github.com/niiknow/docker-nginx-image-proxy/blob/master/build/ngx_http_image_filter_module.c + +Original File - https://github.com/niiknow/docker-nginx-image-proxy/blob/master/build/src/http/modules/ngx_http_image_filter_module.c + +Patch creation: `diff -u src/http/modules/ngx_http_image_filter_module.c ngx_http_image_filter_module.c > image_filter.patch` + +Patch apply with: `patch src/http/modules/ngx_http_image_filter_module.c image_filter.patch` Features: - [x] image crop offset, credit: https://github.com/bobrik/nginx_image_filter diff --git a/build/image_filter.patch b/build/image_filter.patch new file mode 100644 index 0000000..330a220 --- /dev/null +++ b/build/image_filter.patch @@ -0,0 +1,818 @@ +--- src/http/modules/ngx_http_image_filter_module.c 2019-09-24 15:35:32.000000000 -0500 ++++ ngx_http_image_filter_module.c 2019-09-24 15:38:46.000000000 -0500 +@@ -10,7 +10,7 @@ + #include + + #include +- ++#include + + #define NGX_HTTP_IMAGE_OFF 0 + #define NGX_HTTP_IMAGE_TEST 1 +@@ -18,6 +18,8 @@ + #define NGX_HTTP_IMAGE_RESIZE 3 + #define NGX_HTTP_IMAGE_CROP 4 + #define NGX_HTTP_IMAGE_ROTATE 5 ++#define NGX_HTTP_IMAGE_CROP_KEEPX 6 ++#define NGX_HTTP_IMAGE_CROP_KEEPY 7 + + + #define NGX_HTTP_IMAGE_START 0 +@@ -32,10 +34,16 @@ + #define NGX_HTTP_IMAGE_GIF 2 + #define NGX_HTTP_IMAGE_PNG 3 + #define NGX_HTTP_IMAGE_WEBP 4 +- ++#define NGX_HTTP_IMAGE_BMP 5 ++#define NGX_HTTP_IMAGE_TIFF 6 + + #define NGX_HTTP_IMAGE_BUFFERED 0x08 + ++#define NGX_HTTP_IMAGE_OFFSET_CENTER 0 ++#define NGX_HTTP_IMAGE_OFFSET_LEFT 1 ++#define NGX_HTTP_IMAGE_OFFSET_RIGHT 2 ++#define NGX_HTTP_IMAGE_OFFSET_TOP 3 ++#define NGX_HTTP_IMAGE_OFFSET_BOTTOM 4 + + typedef struct { + ngx_uint_t filter; +@@ -45,12 +53,20 @@ + ngx_uint_t jpeg_quality; + ngx_uint_t webp_quality; + ngx_uint_t sharpen; ++ ngx_uint_t offset_x; ++ ngx_uint_t offset_y; ++ ngx_uint_t scale_max; + + ngx_flag_t transparency; + ngx_flag_t interlace; ++ ngx_str_t water_image; ++ ngx_str_t water_pos; ++ ngx_http_complex_value_t *output; + + ngx_http_complex_value_t *wcv; + ngx_http_complex_value_t *hcv; ++ ngx_http_complex_value_t *oxcv; ++ ngx_http_complex_value_t *oycv; + ngx_http_complex_value_t *acv; + ngx_http_complex_value_t *jqcv; + ngx_http_complex_value_t *wqcv; +@@ -70,7 +86,10 @@ + ngx_uint_t height; + ngx_uint_t max_width; + ngx_uint_t max_height; ++ ngx_uint_t offset_x; ++ ngx_uint_t offset_y; + ngx_uint_t angle; ++ ngx_uint_t scale_max; + + ngx_uint_t phase; + ngx_uint_t type; +@@ -116,60 +135,100 @@ + ngx_command_t *cmd, void *conf); + static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); ++static char *ngx_http_image_filter_offset(ngx_conf_t *cf, ngx_command_t *cmd, ++ void *conf); + static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf); +- ++static void ngx_http_image_watermark(ngx_http_request_t *r, ++ ngx_http_image_filter_conf_t *conf, gdImagePtr src); ++static int my_offset(int calculatedLength, int requestedLength); ++static void my_resize(gdImagePtr original, gdImagePtr destination); + + static ngx_command_t ngx_http_image_filter_commands[] = { + + { ngx_string("image_filter"), +- NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, ++ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE123, + ngx_http_image_filter, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("image_filter_jpeg_quality"), +- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, + ngx_http_image_filter_jpeg_quality, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("image_filter_webp_quality"), +- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, + ngx_http_image_filter_webp_quality, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("image_filter_sharpen"), +- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, + ngx_http_image_filter_sharpen, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("image_filter_transparency"), +- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_image_filter_conf_t, transparency), + NULL }, + + { ngx_string("image_filter_interlace"), +- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_image_filter_conf_t, interlace), + NULL }, + + { ngx_string("image_filter_buffer"), +- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_image_filter_conf_t, buffer_size), + NULL }, + ++ { ngx_string("image_filter_output"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ++ ngx_http_set_complex_value_slot, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ offsetof(ngx_http_image_filter_conf_t, output), ++ NULL }, ++ ++ { ngx_string("image_filter_crop_offset"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE2, ++ ngx_http_image_filter_offset, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ 0, ++ NULL }, ++ ++ { ngx_string("image_filter_scale_max"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_num_slot, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ offsetof(ngx_http_image_filter_conf_t, scale_max), ++ NULL }, ++ ++ { ngx_string("image_filter_water_image"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_str_slot, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ offsetof(ngx_http_image_filter_conf_t, water_image), ++ NULL }, ++ ++ { ngx_string("image_filter_water_pos"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_str_slot, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ offsetof(ngx_http_image_filter_conf_t, water_pos), ++ NULL }, ++ + ngx_null_command + }; + +@@ -213,7 +272,9 @@ + ngx_string("image/jpeg"), + ngx_string("image/gif"), + ngx_string("image/png"), +- ngx_string("image/webp") ++ ngx_string("image/webp"), ++ ngx_string("image/bmp"), ++ ngx_string("image/tiff") + }; + + +@@ -296,6 +357,7 @@ + ngx_chain_t out; + ngx_http_image_filter_ctx_t *ctx; + ngx_http_image_filter_conf_t *conf; ++ ngx_str_t ofmt = ngx_null_string; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter"); + +@@ -338,6 +400,28 @@ + /* override content type */ + + ct = &ngx_http_image_types[ctx->type - 1]; ++ if (conf->output != NULL && ngx_http_complex_value(r, conf->output, &ofmt) == NGX_OK) { ++ if (ngx_strncmp(ofmt.data, "jpg", 3) == 0 || ngx_strncmp(ofmt.data, "jpeg", 4) == 0) { ++ ct = &ngx_http_image_types[NGX_HTTP_IMAGE_JPEG - 1]; ++ ++ } else if (ngx_strncmp(ofmt.data, "gif", 3) == 0){ ++ ct = &ngx_http_image_types[NGX_HTTP_IMAGE_GIF - 1]; ++ ++ } else if (ngx_strncmp(ofmt.data, "png", 3) == 0){ ++ ct = &ngx_http_image_types[NGX_HTTP_IMAGE_PNG - 1]; ++ ++ } else if (ngx_strncmp(ofmt.data, "webp", 4) == 0){ ++ ct = &ngx_http_image_types[NGX_HTTP_IMAGE_WEBP - 1]; ++ ++ } else if (ngx_strncmp(ofmt.data, "bmp", 3) == 0){ ++ ct = &ngx_http_image_types[NGX_HTTP_IMAGE_BMP - 1]; ++ ++ } else if (ngx_strncmp(ofmt.data, "tiff", 4) == 0){ ++ ct = &ngx_http_image_types[NGX_HTTP_IMAGE_TIFF - 1]; ++ ++ } ++ } ++ + r->headers_out.content_type_len = ct->len; + r->headers_out.content_type = *ct; + r->headers_out.content_type_lowcase = NULL; +@@ -461,6 +545,17 @@ + /* WebP */ + + return NGX_HTTP_IMAGE_WEBP; ++ } else if (p[0] == 'B' && p[1] == 'M') { ++ /* BMP */ ++ ++ return NGX_HTTP_IMAGE_BMP; ++ ++ } else if ((p[0] == 'I' && p[1] == 'I' && p[2] == 42) || (p[0] == 'M' && p[1] == 'M' && p[3] == 42)) ++ { ++ /* TIFF */ ++ ++ return NGX_HTTP_IMAGE_TIFF; ++ + } + + return NGX_HTTP_IMAGE_NONE; +@@ -527,6 +622,7 @@ + ngx_int_t rc; + ngx_http_image_filter_ctx_t *ctx; + ngx_http_image_filter_conf_t *conf; ++ ngx_str_t ofmt = ngx_null_string; + + r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED; + +@@ -536,6 +632,14 @@ + + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + ++ /* always transform when convert */ ++ if (conf->output != NULL && ngx_http_complex_value(r, conf->output, &ofmt) == NGX_OK) { ++ if (ofmt.len > 2) { ++ ctx->force = 1; ++ } ++ } ++ ++ + if (conf->filter == NGX_HTTP_IMAGE_SIZE) { + return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL); + } +@@ -562,6 +666,11 @@ + return NULL; + } + ++ // scale would force to resize image ++ if (conf->scale_max > 1) { ++ ctx->force = 1; ++ } ++ + if (rc == NGX_OK + && ctx->width <= ctx->max_width + && ctx->height <= ctx->max_height +@@ -801,6 +910,23 @@ + + break; + ++ case NGX_HTTP_IMAGE_BMP: ++ if (ctx->length < 24) { ++ return NGX_DECLINED; ++ } ++ ++ width = p[18] * 256 + p[19]; ++ height = p[22] * 256 + p[23]; ++ ++ break; ++ ++ case NGX_HTTP_IMAGE_TIFF: ++ /* todo: figure out image size calculation for tiff */ ++ width = 0; ++ height = 0; ++ ++ break; ++ + default: + + return NGX_DECLINED; +@@ -821,8 +947,10 @@ + { + int sx, sy, dx, dy, ox, oy, ax, ay, size, + colors, palette, transparent, sharpen, +- red, green, blue, t; ++ red, green, blue, t, scale_max, ++ offset_x, offset_y; + u_char *out; ++ double ratio, ratio_h; + ngx_buf_t *b; + ngx_uint_t resize; + gdImagePtr src, dst; +@@ -840,6 +968,9 @@ + + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + ++ scale_max = (int) conf->scale_max; ++ ratio = 1; ++ + if (!ctx->force + && ctx->angle == 0 + && (ngx_uint_t) sx <= ctx->max_width +@@ -872,6 +1003,63 @@ + + transparent: + ++ if ((int)ctx->max_width > 0) { ++ ratio = ((double) ctx->max_width / (double) sx); ++ } ++ ++ if ((int)ctx->max_height > 0) { ++ ratio_h = ((double) ctx->max_height / (double) sy); ++ if (ratio_h > ratio) { ++ ratio = ratio_h; ++ } ++ } ++ ++ // pre-resize if using scale and required a larger image ++ if (scale_max > 1) { ++ if (ratio > 1) { ++ // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scale max = %d, %d \n", scale_max, scale_max); ++ // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scale ratio = %d, %d \n", ratio, ratio); ++ ++ if (ratio > (double) scale_max) { ++ ratio = (double) scale_max; ++ } ++ ++ /* ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "width max = %d, %d \n", ctx->max_width, ctx->max_width); ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "width img = %d, %d \n", sx, sx); ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "height max = %d, %d \n", ctx->max_height, ctx->max_height); ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "height img = %d, %d \n", sy, sy); ++ */ ++ ++ // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scale %d, %d \n", ratio, ratio); ++ dst = ngx_http_image_new(r, sx * ratio, sy * ratio, palette); ++ ++ if (dst == NULL) { ++ gdImageDestroy(src); ++ return NULL; ++ } ++ ++ if (transparent == -1) { ++ gdImageSaveAlpha(src, 1); ++ gdImageColorTransparent(src, -1); ++ ++ if(colors == 0) { ++ gdImageAlphaBlending(dst, 0); ++ gdImageSaveAlpha(dst, 1); ++ } else { ++ gdImageTrueColorToPalette(dst, 1, 256); ++ } ++ } ++ ++ my_resize(src, dst); ++ // set the new original ++ gdImageDestroy(src); ++ src = dst; ++ sx = gdImageSX(src); ++ sy = gdImageSY(src); ++ } ++ } ++ + gdImageColorTransparent(src, -1); + + dx = sx; +@@ -901,7 +1089,23 @@ + + resize = 0; + +- if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) { ++ if (conf->filter == NGX_HTTP_IMAGE_CROP_KEEPX) { ++ if ((ngx_uint_t) dx > ctx->max_width) { ++ dy = dy * ctx->max_width / dx; ++ dy = dy ? dy : 1; ++ dx = ctx->max_width; ++ resize = 1; ++ } ++ ++ } else if (conf->filter == NGX_HTTP_IMAGE_CROP_KEEPY) { ++ if ((ngx_uint_t) dy > ctx->max_height) { ++ dx = dx * ctx->max_height / dy; ++ dx = dx ? dx : 1; ++ dy = ctx->max_height; ++ resize = 1; ++ } ++ ++ } else if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) { + if ((ngx_uint_t) dx > ctx->max_width) { + dy = dy * ctx->max_width / dx; + dy = dy ? dy : 1; +@@ -989,7 +1193,9 @@ + } + } + +- if (conf->filter == NGX_HTTP_IMAGE_CROP) { ++ if (conf->filter == NGX_HTTP_IMAGE_CROP ++ || conf->filter == NGX_HTTP_IMAGE_CROP_KEEPX ++ || conf->filter == NGX_HTTP_IMAGE_CROP_KEEPY) { + + src = dst; + +@@ -1016,8 +1222,24 @@ + return NULL; + } + +- ox /= 2; +- oy /= 2; ++ offset_x = ngx_http_image_filter_get_value(r, conf->oxcv, ++ conf->offset_x); ++ offset_y = ngx_http_image_filter_get_value(r, conf->oycv, ++ conf->offset_y); ++ ++ if (offset_x == NGX_HTTP_IMAGE_OFFSET_LEFT) { ++ ox = 0; ++ ++ } else if (offset_x == NGX_HTTP_IMAGE_OFFSET_CENTER) { ++ ox /= 2; ++ } ++ ++ if (offset_y == NGX_HTTP_IMAGE_OFFSET_TOP) { ++ oy = 0; ++ ++ } else if (offset_y == NGX_HTTP_IMAGE_OFFSET_CENTER) { ++ oy /= 2; ++ } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "image crop: %d x %d @ %d x %d", +@@ -1048,6 +1270,7 @@ + } + + gdImageInterlace(dst, (int) conf->interlace); ++ ngx_http_image_watermark(r, conf, dst); + + out = ngx_http_image_out(r, ctx->type, dst, &size); + +@@ -1122,6 +1345,20 @@ + #endif + break; + ++ case NGX_HTTP_IMAGE_BMP: ++ img = gdImageCreateFromBmpPtr(ctx->length, ctx->image); ++ ctx->width = img->sx; ++ ctx->height = img->sy; ++ failed = "gdImageCreateFromBmpPtr() failed"; ++ break; ++ ++ case NGX_HTTP_IMAGE_TIFF: ++ img = gdImageCreateFromTiffPtr(ctx->length, ctx->image); ++ ctx->width = img->sx; ++ ctx->height = img->sy; ++ failed = "gdImageCreateFromTiffPtr() failed"; ++ break; ++ + default: + failed = "unknown image type"; + break; +@@ -1171,14 +1408,37 @@ + u_char *out; + ngx_int_t q; + ngx_http_image_filter_conf_t *conf; ++ ngx_str_t ofmt = ngx_null_string; + + out = NULL; + ++ conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); ++ ++ if (conf->output != NULL && ngx_http_complex_value(r, conf->output, &ofmt) == NGX_OK) { ++ if (ngx_strncmp(ofmt.data, "jpg", 3) == 0 || ngx_strncmp(ofmt.data, "jpeg", 4) == 0){ ++ type = NGX_HTTP_IMAGE_JPEG; ++ ++ } else if (ngx_strncmp(ofmt.data, "gif", 3) == 0){ ++ type = NGX_HTTP_IMAGE_GIF; ++ ++ } else if (ngx_strncmp(ofmt.data, "png", 3) == 0){ ++ type = NGX_HTTP_IMAGE_PNG; ++ ++ } else if (ngx_strncmp(ofmt.data, "webp", 4) == 0){ ++ type = NGX_HTTP_IMAGE_WEBP; ++ ++ } else if (ngx_strncmp(ofmt.data, "bmp", 3) == 0){ ++ type = NGX_HTTP_IMAGE_BMP; ++ ++ } else if (ngx_strncmp(ofmt.data, "tiff", 4) == 0){ ++ type = NGX_HTTP_IMAGE_TIFF; ++ ++ } ++ } ++ + switch (type) { + + case NGX_HTTP_IMAGE_JPEG: +- conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); +- + q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); + if (q <= 0) { + return NULL; +@@ -1195,13 +1455,11 @@ + + case NGX_HTTP_IMAGE_PNG: + out = gdImagePngPtr(img, size); +- failed = "gdImagePngPtr() failed"; ++ failed = "gdImagePngPtr() failed"; + break; + + case NGX_HTTP_IMAGE_WEBP: + #if (NGX_HAVE_GD_WEBP) +- conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); +- + q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality); + if (q <= 0) { + return NULL; +@@ -1214,6 +1472,22 @@ + #endif + break; + ++ case NGX_HTTP_IMAGE_BMP: ++ /* reuse jpeg quality value */ ++ q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); ++ if (q <= 0) { ++ return NULL; ++ } ++ ++ out = gdImageBmpPtr(img, size, q); ++ failed = "gdImageBmpPtr() failed"; ++ break; ++ ++ case NGX_HTTP_IMAGE_TIFF: ++ out = gdImageTiffPtr(img, size); ++ failed = "gdImageTiffPtr() failed"; ++ break; ++ + default: + failed = "unknown image type"; + break; +@@ -1263,7 +1537,27 @@ + + n = ngx_atoi(value->data, value->len); + +- if (n > 0) { ++ if (n == NGX_ERROR) { ++ if (value->len == sizeof("left") - 1 ++ && ngx_strncmp(value->data, "left", value->len) == 0) ++ { ++ return NGX_HTTP_IMAGE_OFFSET_LEFT; ++ } else if (value->len == sizeof("right") - 1 ++ && ngx_strncmp(value->data, "right", sizeof("right") - 1) == 0) ++ { ++ return NGX_HTTP_IMAGE_OFFSET_RIGHT; ++ } else if (value->len == sizeof("top") - 1 ++ && ngx_strncmp(value->data, "top", sizeof("top") - 1) == 0) ++ { ++ return NGX_HTTP_IMAGE_OFFSET_TOP; ++ } else if (value->len == sizeof("bottom") - 1 ++ && ngx_strncmp(value->data, "bottom", sizeof("bottom") - 1) == 0) ++ { ++ return NGX_HTTP_IMAGE_OFFSET_BOTTOM; ++ } else { ++ return NGX_HTTP_IMAGE_OFFSET_CENTER; ++ } ++ } else if (n > 0) { + return (ngx_uint_t) n; + } + +@@ -1299,9 +1593,13 @@ + conf->jpeg_quality = NGX_CONF_UNSET_UINT; + conf->webp_quality = NGX_CONF_UNSET_UINT; + conf->sharpen = NGX_CONF_UNSET_UINT; ++ conf->angle = NGX_CONF_UNSET_UINT; + conf->transparency = NGX_CONF_UNSET; + conf->interlace = NGX_CONF_UNSET; + conf->buffer_size = NGX_CONF_UNSET_SIZE; ++ conf->offset_x = NGX_CONF_UNSET_UINT; ++ conf->offset_y = NGX_CONF_UNSET_UINT; ++ conf->scale_max = NGX_CONF_UNSET_UINT; + + return conf; + } +@@ -1364,6 +1662,37 @@ + ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, + 1 * 1024 * 1024); + ++ ngx_conf_merge_str_value(conf->water_image, prev->water_image, ""); ++ ++ ngx_conf_merge_str_value(conf->water_pos, prev->water_pos, "bottom-right"); ++ ++ if (conf->offset_x == NGX_CONF_UNSET_UINT) { ++ ngx_conf_merge_uint_value(conf->offset_x, prev->offset_x, ++ NGX_HTTP_IMAGE_OFFSET_CENTER); ++ ++ if (conf->oxcv == NULL) { ++ conf->oxcv = prev->oxcv; ++ } ++ } ++ ++ if (conf->offset_y == NGX_CONF_UNSET_UINT) { ++ ngx_conf_merge_uint_value(conf->offset_y, prev->offset_y, ++ NGX_HTTP_IMAGE_OFFSET_CENTER); ++ ++ if (conf->oycv == NULL) { ++ conf->oycv = prev->oycv; ++ } ++ } ++ ++ if (conf->scale_max == NGX_CONF_UNSET_UINT) { ++ /* 2 is the default max ratio */ ++ ngx_conf_merge_uint_value(conf->scale_max, prev->scale_max, 1); ++ } ++ ++ if (conf->output == NULL) { ++ conf->output = prev->output; ++ } ++ + return NGX_CONF_OK; + } + +@@ -1662,6 +1991,66 @@ + } + + ++static char * ++ngx_http_image_filter_offset(ngx_conf_t *cf, ngx_command_t *cmd, ++ void *conf) ++{ ++ ngx_http_image_filter_conf_t *imcf = conf; ++ ++ ngx_str_t *value; ++ ngx_http_complex_value_t cv; ++ ngx_http_compile_complex_value_t ccv; ++ ++ value = cf->args->elts; ++ ++ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ++ ++ ccv.cf = cf; ++ ccv.value = &value[1]; ++ ccv.complex_value = &cv; ++ ++ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { ++ return NGX_CONF_ERROR; ++ } ++ ++ if (cv.lengths == NULL) { ++ imcf->offset_x = ngx_http_image_filter_value(&value[1]); ++ ++ } else { ++ imcf->oxcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); ++ if (imcf->oxcv == NULL) { ++ return NGX_CONF_ERROR; ++ } ++ ++ *imcf->oxcv = cv; ++ } ++ ++ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ++ ++ ccv.cf = cf; ++ ccv.value = &value[2]; ++ ccv.complex_value = &cv; ++ ++ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { ++ return NGX_CONF_ERROR; ++ } ++ ++ if (cv.lengths == NULL) { ++ imcf->offset_y = ngx_http_image_filter_value(&value[2]); ++ ++ } else { ++ imcf->oycv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); ++ if (imcf->oycv == NULL) { ++ return NGX_CONF_ERROR; ++ } ++ ++ *imcf->oycv = cv; ++ } ++ ++ return NGX_CONF_OK; ++} ++ ++ + static ngx_int_t + ngx_http_image_filter_init(ngx_conf_t *cf) + { +@@ -1673,3 +2062,109 @@ + + return NGX_OK; + } ++ ++ ++static void ++ngx_http_image_watermark(ngx_http_request_t *r, ngx_http_image_filter_conf_t *conf, gdImagePtr src) ++{ ++ // apply watermark ++ if (ngx_strcmp(conf->water_image.data, "") == 0) { ++ return; ++ } ++ ++ int dx, dy; ++ ++ dx = gdImageSX(src); ++ dy = gdImageSY(src); ++ ++ FILE *watermark_file = fopen((const char *)conf->water_image.data, "r"); ++ ++ if (watermark_file) { ++ gdImagePtr watermark, watermark_mix; ++ ngx_int_t wdx = 0, wdy = 0; ++ ++ watermark = gdImageCreateFromPng(watermark_file); ++ fclose(watermark_file); ++ ++ if(watermark != NULL) { ++ watermark_mix = gdImageCreateTrueColor(watermark->sx, watermark->sy); ++ ++ if (ngx_strcmp(conf->water_pos.data, "top-left") == 0) { ++ wdx = wdy = 10; ++ } else if (ngx_strcmp(conf->water_pos.data, "top-right") == 0) { ++ wdx = dx - watermark->sx - 10; ++ wdy = 10; ++ } else if (ngx_strcmp(conf->water_pos.data, "center") == 0) { ++ wdx = dx / 2 - watermark->sx / 2; ++ wdy = dy / 2 - watermark->sy / 2; ++ } else if (ngx_strcmp(conf->water_pos.data, "bottom-left") == 0) { ++ wdx = 10; ++ wdy = dy - watermark->sy - 10; ++ } else { // default bottom-right ++ wdx = dx - watermark->sx - 10; ++ wdy = dy - watermark->sy - 10; ++ } ++ ++ // if watermark is larger than image set start to 0 ++ if (wdx < 0) { ++ wdx = 0; ++ } ++ if (wdy < 0) { ++ wdy = 0; ++ } ++ ++ gdImageCopy(watermark_mix, src, 0, 0, wdx, wdy, watermark->sx, watermark->sy); ++ gdImageCopy(watermark_mix, watermark, 0, 0, 0, 0, watermark->sx, watermark->sy); ++ gdImageCopyMerge(src, watermark_mix, wdx, wdy, 0, 0, watermark->sx, watermark->sy, 75); ++ gdImageDestroy(watermark); ++ gdImageDestroy(watermark_mix); ++ } else { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "watermark file '%s' is not PNG", conf->water_image.data); ++ } ++ } else { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "watermark file '%s' not found", conf->water_image.data); ++ } ++} ++ ++ ++static int my_offset(int calculatedLength, int requestedLength) { ++ int offset = 0; ++ ++ if (calculatedLength > requestedLength) { ++ offset = (int)((calculatedLength - requestedLength) / 2); ++ } ++ else { ++ offset = (int)((requestedLength - calculatedLength) / 2); ++ } ++ ++ return (offset); ++} ++ ++ ++static void my_resize (gdImagePtr original, gdImagePtr destination) { ++ float originalRatio = (float)original->sx / original->sy; ++ float destinationRatio = (float)destination->sx / destination->sy; ++ ++ int destinationX = destination->sx; ++ int destinationY = destination->sy; ++ ++ if (destinationRatio > originalRatio) { ++ destinationX = floor(destination->sy * originalRatio); ++ } ++ else { ++ destinationY = floor(destination->sx / originalRatio); ++ } ++ ++ gdImageCopyResampled( ++ destination, ++ original, ++ my_offset(destinationX, destination->sx), ++ my_offset(destinationY, destination->sy), ++ 0, ++ 0, ++ destinationX, ++ destinationY, ++ original->sx, ++ original->sy ++ ); ++} diff --git a/build/src/ngx_http_image_filter_module.c b/build/ngx_http_image_filter_module.c similarity index 99% rename from build/src/ngx_http_image_filter_module.c rename to build/ngx_http_image_filter_module.c index 8e6adb5..38dd9b0 100644 --- a/build/src/ngx_http_image_filter_module.c +++ b/build/ngx_http_image_filter_module.c @@ -37,6 +37,7 @@ #define NGX_HTTP_IMAGE_BMP 5 #define NGX_HTTP_IMAGE_TIFF 6 +#define NGX_HTTP_IMAGE_BUFFERED 0x08 #define NGX_HTTP_IMAGE_OFFSET_CENTER 0 #define NGX_HTTP_IMAGE_OFFSET_LEFT 1 @@ -44,8 +45,6 @@ #define NGX_HTTP_IMAGE_OFFSET_TOP 3 #define NGX_HTTP_IMAGE_OFFSET_BOTTOM 4 -#define NGX_HTTP_IMAGE_BUFFERED 0x08 - typedef struct { ngx_uint_t filter; ngx_uint_t width; @@ -278,6 +277,7 @@ static ngx_str_t ngx_http_image_types[] = { ngx_string("image/tiff") }; + static ngx_int_t ngx_http_image_header_filter(ngx_http_request_t *r) { @@ -397,6 +397,8 @@ ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in) NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); } + /* override content type */ + ct = &ngx_http_image_types[ctx->type - 1]; if (conf->output != NULL && ngx_http_complex_value(r, conf->output, &ofmt) == NGX_OK) { if (ngx_strncmp(ofmt.data, "jpg", 3) == 0 || ngx_strncmp(ofmt.data, "jpeg", 4) == 0) { @@ -658,7 +660,8 @@ ngx_http_image_process(ngx_http_request_t *r) return NULL; } - ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv, conf->height); + ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv, + conf->height); if (ctx->max_height == 0) { return NULL; } @@ -826,6 +829,13 @@ ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) return NGX_DECLINED; } + if (ctx->length / 20 < app) { + /* force conversion if application data consume more than 5% */ + ctx->force = 1; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "app data size: %uz", app); + } + break; case NGX_HTTP_IMAGE_GIF: @@ -931,6 +941,7 @@ ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) return NGX_OK; } + static ngx_buf_t * ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) { @@ -952,9 +963,11 @@ ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) return NULL; } - sx = gdImageSX(src); - sy = gdImageSY(src); - conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + sx = gdImageSX(src); + sy = gdImageSY(src); + + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + scale_max = (int) conf->scale_max; ratio = 1; @@ -1053,6 +1066,7 @@ ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) dy = sy; if (conf->filter == NGX_HTTP_IMAGE_RESIZE) { + if ((ngx_uint_t) dx > ctx->max_width) { dy = dy * ctx->max_width / dx; dy = dy ? dy : 1; @@ -1320,8 +1334,8 @@ ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) case NGX_HTTP_IMAGE_PNG: img = gdImageCreateFromPngPtr(ctx->length, ctx->image); failed = "gdImageCreateFromPngPtr() failed"; - break; + case NGX_HTTP_IMAGE_WEBP: #if (NGX_HAVE_GD_WEBP) img = gdImageCreateFromWebpPtr(ctx->length, ctx->image); diff --git a/build/src/http/modules/ngx_http_image_filter_module.c b/build/src/http/modules/ngx_http_image_filter_module.c new file mode 100644 index 0000000..6c03e8a --- /dev/null +++ b/build/src/http/modules/ngx_http_image_filter_module.c @@ -0,0 +1,1675 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + +#include + + +#define NGX_HTTP_IMAGE_OFF 0 +#define NGX_HTTP_IMAGE_TEST 1 +#define NGX_HTTP_IMAGE_SIZE 2 +#define NGX_HTTP_IMAGE_RESIZE 3 +#define NGX_HTTP_IMAGE_CROP 4 +#define NGX_HTTP_IMAGE_ROTATE 5 + + +#define NGX_HTTP_IMAGE_START 0 +#define NGX_HTTP_IMAGE_READ 1 +#define NGX_HTTP_IMAGE_PROCESS 2 +#define NGX_HTTP_IMAGE_PASS 3 +#define NGX_HTTP_IMAGE_DONE 4 + + +#define NGX_HTTP_IMAGE_NONE 0 +#define NGX_HTTP_IMAGE_JPEG 1 +#define NGX_HTTP_IMAGE_GIF 2 +#define NGX_HTTP_IMAGE_PNG 3 +#define NGX_HTTP_IMAGE_WEBP 4 + + +#define NGX_HTTP_IMAGE_BUFFERED 0x08 + + +typedef struct { + ngx_uint_t filter; + ngx_uint_t width; + ngx_uint_t height; + ngx_uint_t angle; + ngx_uint_t jpeg_quality; + ngx_uint_t webp_quality; + ngx_uint_t sharpen; + + ngx_flag_t transparency; + ngx_flag_t interlace; + + ngx_http_complex_value_t *wcv; + ngx_http_complex_value_t *hcv; + ngx_http_complex_value_t *acv; + ngx_http_complex_value_t *jqcv; + ngx_http_complex_value_t *wqcv; + ngx_http_complex_value_t *shcv; + + size_t buffer_size; +} ngx_http_image_filter_conf_t; + + +typedef struct { + u_char *image; + u_char *last; + + size_t length; + + ngx_uint_t width; + ngx_uint_t height; + ngx_uint_t max_width; + ngx_uint_t max_height; + ngx_uint_t angle; + + ngx_uint_t phase; + ngx_uint_t type; + ngx_uint_t force; +} ngx_http_image_filter_ctx_t; + + +static ngx_int_t ngx_http_image_send(ngx_http_request_t *r, + ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in); +static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in); +static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in); +static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r); +static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r, + ngx_http_image_filter_ctx_t *ctx); +static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r, + ngx_http_image_filter_ctx_t *ctx); +static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b); +static ngx_int_t ngx_http_image_size(ngx_http_request_t *r, + ngx_http_image_filter_ctx_t *ctx); + +static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r, + ngx_http_image_filter_ctx_t *ctx); +static gdImagePtr ngx_http_image_source(ngx_http_request_t *r, + ngx_http_image_filter_ctx_t *ctx); +static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h, + int colors); +static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, + gdImagePtr img, int *size); +static void ngx_http_image_cleanup(void *data); +static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r, + ngx_http_complex_value_t *cv, ngx_uint_t v); +static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value); + + +static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf); +static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); +static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); +static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf); + + +static ngx_command_t ngx_http_image_filter_commands[] = { + + { ngx_string("image_filter"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, + ngx_http_image_filter, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("image_filter_jpeg_quality"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_image_filter_jpeg_quality, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("image_filter_webp_quality"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_image_filter_webp_quality, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("image_filter_sharpen"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_image_filter_sharpen, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("image_filter_transparency"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_image_filter_conf_t, transparency), + NULL }, + + { ngx_string("image_filter_interlace"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_image_filter_conf_t, interlace), + NULL }, + + { ngx_string("image_filter_buffer"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_image_filter_conf_t, buffer_size), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_image_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_image_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_image_filter_create_conf, /* create location configuration */ + ngx_http_image_filter_merge_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_image_filter_module = { + NGX_MODULE_V1, + &ngx_http_image_filter_module_ctx, /* module context */ + ngx_http_image_filter_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static ngx_str_t ngx_http_image_types[] = { + ngx_string("image/jpeg"), + ngx_string("image/gif"), + ngx_string("image/png"), + ngx_string("image/webp") +}; + + +static ngx_int_t +ngx_http_image_header_filter(ngx_http_request_t *r) +{ + off_t len; + ngx_http_image_filter_ctx_t *ctx; + ngx_http_image_filter_conf_t *conf; + + if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { + return ngx_http_next_header_filter(r); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); + + if (ctx) { + ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module); + return ngx_http_next_header_filter(r); + } + + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + + if (conf->filter == NGX_HTTP_IMAGE_OFF) { + return ngx_http_next_header_filter(r); + } + + if (r->headers_out.content_type.len + >= sizeof("multipart/x-mixed-replace") - 1 + && ngx_strncasecmp(r->headers_out.content_type.data, + (u_char *) "multipart/x-mixed-replace", + sizeof("multipart/x-mixed-replace") - 1) + == 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "image filter: multipart/x-mixed-replace response"); + + return NGX_ERROR; + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module); + + len = r->headers_out.content_length_n; + + if (len != -1 && len > (off_t) conf->buffer_size) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "image filter: too big response: %O", len); + + return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; + } + + if (len == -1) { + ctx->length = conf->buffer_size; + + } else { + ctx->length = (size_t) len; + } + + if (r->headers_out.refresh) { + r->headers_out.refresh->hash = 0; + } + + r->main_filter_need_in_memory = 1; + r->allow_ranges = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + ngx_int_t rc; + ngx_str_t *ct; + ngx_chain_t out; + ngx_http_image_filter_ctx_t *ctx; + ngx_http_image_filter_conf_t *conf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter"); + + if (in == NULL) { + return ngx_http_next_body_filter(r, in); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); + + if (ctx == NULL) { + return ngx_http_next_body_filter(r, in); + } + + switch (ctx->phase) { + + case NGX_HTTP_IMAGE_START: + + ctx->type = ngx_http_image_test(r, in); + + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + + if (ctx->type == NGX_HTTP_IMAGE_NONE) { + + if (conf->filter == NGX_HTTP_IMAGE_SIZE) { + out.buf = ngx_http_image_json(r, NULL); + + if (out.buf) { + out.next = NULL; + ctx->phase = NGX_HTTP_IMAGE_DONE; + + return ngx_http_image_send(r, ctx, &out); + } + } + + return ngx_http_filter_finalize_request(r, + &ngx_http_image_filter_module, + NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); + } + + /* override content type */ + + ct = &ngx_http_image_types[ctx->type - 1]; + r->headers_out.content_type_len = ct->len; + r->headers_out.content_type = *ct; + r->headers_out.content_type_lowcase = NULL; + + if (conf->filter == NGX_HTTP_IMAGE_TEST) { + ctx->phase = NGX_HTTP_IMAGE_PASS; + + return ngx_http_image_send(r, ctx, in); + } + + ctx->phase = NGX_HTTP_IMAGE_READ; + + /* fall through */ + + case NGX_HTTP_IMAGE_READ: + + rc = ngx_http_image_read(r, in); + + if (rc == NGX_AGAIN) { + return NGX_OK; + } + + if (rc == NGX_ERROR) { + return ngx_http_filter_finalize_request(r, + &ngx_http_image_filter_module, + NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); + } + + /* fall through */ + + case NGX_HTTP_IMAGE_PROCESS: + + out.buf = ngx_http_image_process(r); + + if (out.buf == NULL) { + return ngx_http_filter_finalize_request(r, + &ngx_http_image_filter_module, + NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); + } + + out.next = NULL; + ctx->phase = NGX_HTTP_IMAGE_PASS; + + return ngx_http_image_send(r, ctx, &out); + + case NGX_HTTP_IMAGE_PASS: + + return ngx_http_next_body_filter(r, in); + + default: /* NGX_HTTP_IMAGE_DONE */ + + rc = ngx_http_next_body_filter(r, NULL); + + /* NGX_ERROR resets any pending data */ + return (rc == NGX_OK) ? NGX_ERROR : rc; + } +} + + +static ngx_int_t +ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx, + ngx_chain_t *in) +{ + ngx_int_t rc; + + rc = ngx_http_next_header_filter(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return NGX_ERROR; + } + + rc = ngx_http_next_body_filter(r, in); + + if (ctx->phase == NGX_HTTP_IMAGE_DONE) { + /* NGX_ERROR resets any pending data */ + return (rc == NGX_OK) ? NGX_ERROR : rc; + } + + return rc; +} + + +static ngx_uint_t +ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in) +{ + u_char *p; + + p = in->buf->pos; + + if (in->buf->last - p < 16) { + return NGX_HTTP_IMAGE_NONE; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "image filter: \"%c%c\"", p[0], p[1]); + + if (p[0] == 0xff && p[1] == 0xd8) { + + /* JPEG */ + + return NGX_HTTP_IMAGE_JPEG; + + } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8' + && p[5] == 'a') + { + if (p[4] == '9' || p[4] == '7') { + /* GIF */ + return NGX_HTTP_IMAGE_GIF; + } + + } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G' + && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a) + { + /* PNG */ + + return NGX_HTTP_IMAGE_PNG; + + } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F' + && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P') + { + /* WebP */ + + return NGX_HTTP_IMAGE_WEBP; + } + + return NGX_HTTP_IMAGE_NONE; +} + + +static ngx_int_t +ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in) +{ + u_char *p; + size_t size, rest; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_http_image_filter_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); + + if (ctx->image == NULL) { + ctx->image = ngx_palloc(r->pool, ctx->length); + if (ctx->image == NULL) { + return NGX_ERROR; + } + + ctx->last = ctx->image; + } + + p = ctx->last; + + for (cl = in; cl; cl = cl->next) { + + b = cl->buf; + size = b->last - b->pos; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "image buf: %uz", size); + + rest = ctx->image + ctx->length - p; + + if (size > rest) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "image filter: too big response"); + return NGX_ERROR; + } + + p = ngx_cpymem(p, b->pos, size); + b->pos += size; + + if (b->last_buf) { + ctx->last = p; + return NGX_OK; + } + } + + ctx->last = p; + r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED; + + return NGX_AGAIN; +} + + +static ngx_buf_t * +ngx_http_image_process(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_image_filter_ctx_t *ctx; + ngx_http_image_filter_conf_t *conf; + + r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED; + + ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module); + + rc = ngx_http_image_size(r, ctx); + + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + + if (conf->filter == NGX_HTTP_IMAGE_SIZE) { + return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL); + } + + ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle); + + if (conf->filter == NGX_HTTP_IMAGE_ROTATE) { + + if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) { + return NULL; + } + + return ngx_http_image_resize(r, ctx); + } + + ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width); + if (ctx->max_width == 0) { + return NULL; + } + + ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv, + conf->height); + if (ctx->max_height == 0) { + return NULL; + } + + if (rc == NGX_OK + && ctx->width <= ctx->max_width + && ctx->height <= ctx->max_height + && ctx->angle == 0 + && !ctx->force) + { + return ngx_http_image_asis(r, ctx); + } + + return ngx_http_image_resize(r, ctx); +} + + +static ngx_buf_t * +ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) +{ + size_t len; + ngx_buf_t *b; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NULL; + } + + b->memory = 1; + b->last_buf = 1; + + ngx_http_clean_header(r); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_type_len = sizeof("application/json") - 1; + ngx_str_set(&r->headers_out.content_type, "application/json"); + r->headers_out.content_type_lowcase = NULL; + + if (ctx == NULL) { + b->pos = (u_char *) "{}" CRLF; + b->last = b->pos + sizeof("{}" CRLF) - 1; + + ngx_http_image_length(r, b); + + return b; + } + + len = sizeof("{ \"img\" : " + "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1 + + 2 * NGX_SIZE_T_LEN; + + b->pos = ngx_pnalloc(r->pool, len); + if (b->pos == NULL) { + return NULL; + } + + b->last = ngx_sprintf(b->pos, + "{ \"img\" : " + "{ \"width\": %uz," + " \"height\": %uz," + " \"type\": \"%s\" } }" CRLF, + ctx->width, ctx->height, + ngx_http_image_types[ctx->type - 1].data + 6); + + ngx_http_image_length(r, b); + + return b; +} + + +static ngx_buf_t * +ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) +{ + ngx_buf_t *b; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NULL; + } + + b->pos = ctx->image; + b->last = ctx->last; + b->memory = 1; + b->last_buf = 1; + + ngx_http_image_length(r, b); + + return b; +} + + +static void +ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b) +{ + r->headers_out.content_length_n = b->last - b->pos; + + if (r->headers_out.content_length) { + r->headers_out.content_length->hash = 0; + } + + r->headers_out.content_length = NULL; +} + + +static ngx_int_t +ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) +{ + u_char *p, *last; + size_t len, app; + ngx_uint_t width, height; + + p = ctx->image; + + switch (ctx->type) { + + case NGX_HTTP_IMAGE_JPEG: + + p += 2; + last = ctx->image + ctx->length - 10; + width = 0; + height = 0; + app = 0; + + while (p < last) { + + if (p[0] == 0xff && p[1] != 0xff) { + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "JPEG: %02xd %02xd", p[0], p[1]); + + p++; + + if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3 + || *p == 0xc9 || *p == 0xca || *p == 0xcb) + && (width == 0 || height == 0)) + { + width = p[6] * 256 + p[7]; + height = p[4] * 256 + p[5]; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "JPEG: %02xd %02xd", p[1], p[2]); + + len = p[1] * 256 + p[2]; + + if (*p >= 0xe1 && *p <= 0xef) { + /* application data, e.g., EXIF, Adobe XMP, etc. */ + app += len; + } + + p += len; + + continue; + } + + p++; + } + + if (width == 0 || height == 0) { + return NGX_DECLINED; + } + + if (ctx->length / 20 < app) { + /* force conversion if application data consume more than 5% */ + ctx->force = 1; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "app data size: %uz", app); + } + + break; + + case NGX_HTTP_IMAGE_GIF: + + if (ctx->length < 10) { + return NGX_DECLINED; + } + + width = p[7] * 256 + p[6]; + height = p[9] * 256 + p[8]; + + break; + + case NGX_HTTP_IMAGE_PNG: + + if (ctx->length < 24) { + return NGX_DECLINED; + } + + width = p[18] * 256 + p[19]; + height = p[22] * 256 + p[23]; + + break; + + case NGX_HTTP_IMAGE_WEBP: + + if (ctx->length < 30) { + return NGX_DECLINED; + } + + if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') { + return NGX_DECLINED; + } + + switch (p[15]) { + + case ' ': + if (p[20] & 1) { + /* not a key frame */ + return NGX_DECLINED; + } + + if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) { + /* invalid start code */ + return NGX_DECLINED; + } + + width = (p[26] | p[27] << 8) & 0x3fff; + height = (p[28] | p[29] << 8) & 0x3fff; + + break; + + case 'L': + if (p[20] != 0x2f) { + /* invalid signature */ + return NGX_DECLINED; + } + + width = ((p[21] | p[22] << 8) & 0x3fff) + 1; + height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1; + + break; + + case 'X': + width = (p[24] | p[25] << 8 | p[26] << 16) + 1; + height = (p[27] | p[28] << 8 | p[29] << 16) + 1; + break; + + default: + return NGX_DECLINED; + } + + break; + + default: + + return NGX_DECLINED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "image size: %d x %d", (int) width, (int) height); + + ctx->width = width; + ctx->height = height; + + return NGX_OK; +} + + +static ngx_buf_t * +ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) +{ + int sx, sy, dx, dy, ox, oy, ax, ay, size, + colors, palette, transparent, sharpen, + red, green, blue, t; + u_char *out; + ngx_buf_t *b; + ngx_uint_t resize; + gdImagePtr src, dst; + ngx_pool_cleanup_t *cln; + ngx_http_image_filter_conf_t *conf; + + src = ngx_http_image_source(r, ctx); + + if (src == NULL) { + return NULL; + } + + sx = gdImageSX(src); + sy = gdImageSY(src); + + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + + if (!ctx->force + && ctx->angle == 0 + && (ngx_uint_t) sx <= ctx->max_width + && (ngx_uint_t) sy <= ctx->max_height) + { + gdImageDestroy(src); + return ngx_http_image_asis(r, ctx); + } + + colors = gdImageColorsTotal(src); + + if (colors && conf->transparency) { + transparent = gdImageGetTransparent(src); + + if (transparent != -1) { + palette = colors; + red = gdImageRed(src, transparent); + green = gdImageGreen(src, transparent); + blue = gdImageBlue(src, transparent); + + goto transparent; + } + } + + palette = 0; + transparent = -1; + red = 0; + green = 0; + blue = 0; + +transparent: + + gdImageColorTransparent(src, -1); + + dx = sx; + dy = sy; + + if (conf->filter == NGX_HTTP_IMAGE_RESIZE) { + + if ((ngx_uint_t) dx > ctx->max_width) { + dy = dy * ctx->max_width / dx; + dy = dy ? dy : 1; + dx = ctx->max_width; + } + + if ((ngx_uint_t) dy > ctx->max_height) { + dx = dx * ctx->max_height / dy; + dx = dx ? dx : 1; + dy = ctx->max_height; + } + + resize = 1; + + } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) { + + resize = 0; + + } else { /* NGX_HTTP_IMAGE_CROP */ + + resize = 0; + + if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) { + if ((ngx_uint_t) dx > ctx->max_width) { + dy = dy * ctx->max_width / dx; + dy = dy ? dy : 1; + dx = ctx->max_width; + resize = 1; + } + + } else { + if ((ngx_uint_t) dy > ctx->max_height) { + dx = dx * ctx->max_height / dy; + dx = dx ? dx : 1; + dy = ctx->max_height; + resize = 1; + } + } + } + + if (resize) { + dst = ngx_http_image_new(r, dx, dy, palette); + if (dst == NULL) { + gdImageDestroy(src); + return NULL; + } + + if (colors == 0) { + gdImageSaveAlpha(dst, 1); + gdImageAlphaBlending(dst, 0); + } + + gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy); + + if (colors) { + gdImageTrueColorToPalette(dst, 1, 256); + } + + gdImageDestroy(src); + + } else { + dst = src; + } + + if (ctx->angle) { + src = dst; + + ax = (dx % 2 == 0) ? 1 : 0; + ay = (dy % 2 == 0) ? 1 : 0; + + switch (ctx->angle) { + + case 90: + case 270: + dst = ngx_http_image_new(r, dy, dx, palette); + if (dst == NULL) { + gdImageDestroy(src); + return NULL; + } + if (ctx->angle == 90) { + ox = dy / 2 + ay; + oy = dx / 2 - ax; + + } else { + ox = dy / 2 - ay; + oy = dx / 2 + ax; + } + + gdImageCopyRotated(dst, src, ox, oy, 0, 0, + dx + ax, dy + ay, ctx->angle); + gdImageDestroy(src); + + t = dx; + dx = dy; + dy = t; + break; + + case 180: + dst = ngx_http_image_new(r, dx, dy, palette); + if (dst == NULL) { + gdImageDestroy(src); + return NULL; + } + gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0, + dx + ax, dy + ay, ctx->angle); + gdImageDestroy(src); + break; + } + } + + if (conf->filter == NGX_HTTP_IMAGE_CROP) { + + src = dst; + + if ((ngx_uint_t) dx > ctx->max_width) { + ox = dx - ctx->max_width; + + } else { + ox = 0; + } + + if ((ngx_uint_t) dy > ctx->max_height) { + oy = dy - ctx->max_height; + + } else { + oy = 0; + } + + if (ox || oy) { + + dst = ngx_http_image_new(r, dx - ox, dy - oy, colors); + + if (dst == NULL) { + gdImageDestroy(src); + return NULL; + } + + ox /= 2; + oy /= 2; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "image crop: %d x %d @ %d x %d", + dx, dy, ox, oy); + + if (colors == 0) { + gdImageSaveAlpha(dst, 1); + gdImageAlphaBlending(dst, 0); + } + + gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy); + + if (colors) { + gdImageTrueColorToPalette(dst, 1, 256); + } + + gdImageDestroy(src); + } + } + + if (transparent != -1 && colors) { + gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue)); + } + + sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen); + if (sharpen > 0) { + gdImageSharpen(dst, sharpen); + } + + gdImageInterlace(dst, (int) conf->interlace); + + out = ngx_http_image_out(r, ctx->type, dst, &size); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "image: %d x %d %d", sx, sy, colors); + + gdImageDestroy(dst); + ngx_pfree(r->pool, ctx->image); + + if (out == NULL) { + return NULL; + } + + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + gdFree(out); + return NULL; + } + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + gdFree(out); + return NULL; + } + + cln->handler = ngx_http_image_cleanup; + cln->data = out; + + b->pos = out; + b->last = out + size; + b->memory = 1; + b->last_buf = 1; + + ngx_http_image_length(r, b); + ngx_http_weak_etag(r); + + return b; +} + + +static gdImagePtr +ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) +{ + char *failed; + gdImagePtr img; + + img = NULL; + + switch (ctx->type) { + + case NGX_HTTP_IMAGE_JPEG: + img = gdImageCreateFromJpegPtr(ctx->length, ctx->image); + failed = "gdImageCreateFromJpegPtr() failed"; + break; + + case NGX_HTTP_IMAGE_GIF: + img = gdImageCreateFromGifPtr(ctx->length, ctx->image); + failed = "gdImageCreateFromGifPtr() failed"; + break; + + case NGX_HTTP_IMAGE_PNG: + img = gdImageCreateFromPngPtr(ctx->length, ctx->image); + failed = "gdImageCreateFromPngPtr() failed"; + break; + + case NGX_HTTP_IMAGE_WEBP: +#if (NGX_HAVE_GD_WEBP) + img = gdImageCreateFromWebpPtr(ctx->length, ctx->image); + failed = "gdImageCreateFromWebpPtr() failed"; +#else + failed = "nginx was built without GD WebP support"; +#endif + break; + + default: + failed = "unknown image type"; + break; + } + + if (img == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed); + } + + return img; +} + + +static gdImagePtr +ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors) +{ + gdImagePtr img; + + if (colors == 0) { + img = gdImageCreateTrueColor(w, h); + + if (img == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "gdImageCreateTrueColor() failed"); + return NULL; + } + + } else { + img = gdImageCreate(w, h); + + if (img == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "gdImageCreate() failed"); + return NULL; + } + } + + return img; +} + + +static u_char * +ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, + int *size) +{ + char *failed; + u_char *out; + ngx_int_t q; + ngx_http_image_filter_conf_t *conf; + + out = NULL; + + switch (type) { + + case NGX_HTTP_IMAGE_JPEG: + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + + q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); + if (q <= 0) { + return NULL; + } + + out = gdImageJpegPtr(img, size, q); + failed = "gdImageJpegPtr() failed"; + break; + + case NGX_HTTP_IMAGE_GIF: + out = gdImageGifPtr(img, size); + failed = "gdImageGifPtr() failed"; + break; + + case NGX_HTTP_IMAGE_PNG: + out = gdImagePngPtr(img, size); + failed = "gdImagePngPtr() failed"; + break; + + case NGX_HTTP_IMAGE_WEBP: +#if (NGX_HAVE_GD_WEBP) + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); + + q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality); + if (q <= 0) { + return NULL; + } + + out = gdImageWebpPtrEx(img, size, q); + failed = "gdImageWebpPtrEx() failed"; +#else + failed = "nginx was built without GD WebP support"; +#endif + break; + + default: + failed = "unknown image type"; + break; + } + + if (out == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed); + } + + return out; +} + + +static void +ngx_http_image_cleanup(void *data) +{ + gdFree(data); +} + + +static ngx_uint_t +ngx_http_image_filter_get_value(ngx_http_request_t *r, + ngx_http_complex_value_t *cv, ngx_uint_t v) +{ + ngx_str_t val; + + if (cv == NULL) { + return v; + } + + if (ngx_http_complex_value(r, cv, &val) != NGX_OK) { + return 0; + } + + return ngx_http_image_filter_value(&val); +} + + +static ngx_uint_t +ngx_http_image_filter_value(ngx_str_t *value) +{ + ngx_int_t n; + + if (value->len == 1 && value->data[0] == '-') { + return (ngx_uint_t) -1; + } + + n = ngx_atoi(value->data, value->len); + + if (n > 0) { + return (ngx_uint_t) n; + } + + return 0; +} + + +static void * +ngx_http_image_filter_create_conf(ngx_conf_t *cf) +{ + ngx_http_image_filter_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->width = 0; + * conf->height = 0; + * conf->angle = 0; + * conf->wcv = NULL; + * conf->hcv = NULL; + * conf->acv = NULL; + * conf->jqcv = NULL; + * conf->wqcv = NULL; + * conf->shcv = NULL; + */ + + conf->filter = NGX_CONF_UNSET_UINT; + conf->jpeg_quality = NGX_CONF_UNSET_UINT; + conf->webp_quality = NGX_CONF_UNSET_UINT; + conf->sharpen = NGX_CONF_UNSET_UINT; + conf->transparency = NGX_CONF_UNSET; + conf->interlace = NGX_CONF_UNSET; + conf->buffer_size = NGX_CONF_UNSET_SIZE; + + return conf; +} + + +static char * +ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_image_filter_conf_t *prev = parent; + ngx_http_image_filter_conf_t *conf = child; + + if (conf->filter == NGX_CONF_UNSET_UINT) { + + if (prev->filter == NGX_CONF_UNSET_UINT) { + conf->filter = NGX_HTTP_IMAGE_OFF; + + } else { + conf->filter = prev->filter; + conf->width = prev->width; + conf->height = prev->height; + conf->angle = prev->angle; + conf->wcv = prev->wcv; + conf->hcv = prev->hcv; + conf->acv = prev->acv; + } + } + + if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) { + + /* 75 is libjpeg default quality */ + ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75); + + if (conf->jqcv == NULL) { + conf->jqcv = prev->jqcv; + } + } + + if (conf->webp_quality == NGX_CONF_UNSET_UINT) { + + /* 80 is libwebp default quality */ + ngx_conf_merge_uint_value(conf->webp_quality, prev->webp_quality, 80); + + if (conf->wqcv == NULL) { + conf->wqcv = prev->wqcv; + } + } + + if (conf->sharpen == NGX_CONF_UNSET_UINT) { + ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0); + + if (conf->shcv == NULL) { + conf->shcv = prev->shcv; + } + } + + ngx_conf_merge_value(conf->transparency, prev->transparency, 1); + + ngx_conf_merge_value(conf->interlace, prev->interlace, 0); + + ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, + 1 * 1024 * 1024); + + return NGX_CONF_OK; +} + + +static char * +ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_image_filter_conf_t *imcf = conf; + + ngx_str_t *value; + ngx_int_t n; + ngx_uint_t i; + ngx_http_complex_value_t cv; + ngx_http_compile_complex_value_t ccv; + + value = cf->args->elts; + + i = 1; + + if (cf->args->nelts == 2) { + if (ngx_strcmp(value[i].data, "off") == 0) { + imcf->filter = NGX_HTTP_IMAGE_OFF; + + } else if (ngx_strcmp(value[i].data, "test") == 0) { + imcf->filter = NGX_HTTP_IMAGE_TEST; + + } else if (ngx_strcmp(value[i].data, "size") == 0) { + imcf->filter = NGX_HTTP_IMAGE_SIZE; + + } else { + goto failed; + } + + return NGX_CONF_OK; + + } else if (cf->args->nelts == 3) { + + if (ngx_strcmp(value[i].data, "rotate") == 0) { + if (imcf->filter != NGX_HTTP_IMAGE_RESIZE + && imcf->filter != NGX_HTTP_IMAGE_CROP) + { + imcf->filter = NGX_HTTP_IMAGE_ROTATE; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[++i]; + ccv.complex_value = &cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths == NULL) { + n = ngx_http_image_filter_value(&value[i]); + + if (n != 90 && n != 180 && n != 270) { + goto failed; + } + + imcf->angle = (ngx_uint_t) n; + + } else { + imcf->acv = ngx_palloc(cf->pool, + sizeof(ngx_http_complex_value_t)); + if (imcf->acv == NULL) { + return NGX_CONF_ERROR; + } + + *imcf->acv = cv; + } + + return NGX_CONF_OK; + + } else { + goto failed; + } + } + + if (ngx_strcmp(value[i].data, "resize") == 0) { + imcf->filter = NGX_HTTP_IMAGE_RESIZE; + + } else if (ngx_strcmp(value[i].data, "crop") == 0) { + imcf->filter = NGX_HTTP_IMAGE_CROP; + + } else { + goto failed; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[++i]; + ccv.complex_value = &cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths == NULL) { + n = ngx_http_image_filter_value(&value[i]); + + if (n == 0) { + goto failed; + } + + imcf->width = (ngx_uint_t) n; + + } else { + imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (imcf->wcv == NULL) { + return NGX_CONF_ERROR; + } + + *imcf->wcv = cv; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[++i]; + ccv.complex_value = &cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths == NULL) { + n = ngx_http_image_filter_value(&value[i]); + + if (n == 0) { + goto failed; + } + + imcf->height = (ngx_uint_t) n; + + } else { + imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (imcf->hcv == NULL) { + return NGX_CONF_ERROR; + } + + *imcf->hcv = cv; + } + + return NGX_CONF_OK; + +failed: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", + &value[i]); + + return NGX_CONF_ERROR; +} + + +static char * +ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_image_filter_conf_t *imcf = conf; + + ngx_str_t *value; + ngx_int_t n; + ngx_http_complex_value_t cv; + ngx_http_compile_complex_value_t ccv; + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths == NULL) { + n = ngx_http_image_filter_value(&value[1]); + + if (n <= 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid value \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + imcf->jpeg_quality = (ngx_uint_t) n; + + } else { + imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (imcf->jqcv == NULL) { + return NGX_CONF_ERROR; + } + + *imcf->jqcv = cv; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_image_filter_conf_t *imcf = conf; + + ngx_str_t *value; + ngx_int_t n; + ngx_http_complex_value_t cv; + ngx_http_compile_complex_value_t ccv; + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths == NULL) { + n = ngx_http_image_filter_value(&value[1]); + + if (n <= 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid value \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + imcf->webp_quality = (ngx_uint_t) n; + + } else { + imcf->wqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (imcf->wqcv == NULL) { + return NGX_CONF_ERROR; + } + + *imcf->wqcv = cv; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_image_filter_conf_t *imcf = conf; + + ngx_str_t *value; + ngx_int_t n; + ngx_http_complex_value_t cv; + ngx_http_compile_complex_value_t ccv; + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths == NULL) { + n = ngx_http_image_filter_value(&value[1]); + + if (n < 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid value \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + imcf->sharpen = (ngx_uint_t) n; + + } else { + imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (imcf->shcv == NULL) { + return NGX_CONF_ERROR; + } + + *imcf->shcv = cv; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_image_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_image_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_image_body_filter; + + return NGX_OK; +} diff --git a/build/src/ubuntu.sh b/build/ubuntu.sh similarity index 90% rename from build/src/ubuntu.sh rename to build/ubuntu.sh index 18fcf73..12e68e6 100644 --- a/build/src/ubuntu.sh +++ b/build/ubuntu.sh @@ -35,9 +35,8 @@ apt-get source nginx=${NGINX_VERSION} -y pwd ls -la -cd ${NGINX_BUILD_DIR}/src/http/modules/ -mv ngx_http_image_filter_module.c ngx_http_image_filter_module.bak -mv /tmp/ngx_http_image_filter_module.c ./ngx_http_image_filter_module.c +cd ${NGINX_BUILD_DIR} +patch src/http/modules/ngx_http_image_filter_module.c /tmp/image_filter.patch sed -i "s/--with-http_ssl_module/--with-http_ssl_module --with-http_image_filter_module --add-module=\/usr\/src\/nginx\/ngx_devel_kit --add-module=\/usr\/src\/nginx\/set-misc-nginx-module /g" \ ${NGINX_BUILD_DIR}/debian/rules