From 9ec0b1fe12f0df121ccea2097c27f66997110ac2 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 21 Oct 2016 15:18:44 +0300 Subject: [PATCH] Image filter: support for WebP. In collaboration with Ivan Poluyanov. --- auto/lib/libgd/conf | 5 + .../modules/ngx_http_image_filter_module.c | 164 +++++++++++++++++- 2 files changed, 164 insertions(+), 5 deletions(-) diff --git a/auto/lib/libgd/conf b/auto/lib/libgd/conf index 6e4e91cf0..87761f14d 100644 --- a/auto/lib/libgd/conf +++ b/auto/lib/libgd/conf @@ -74,6 +74,11 @@ if [ $ngx_found = yes ]; then NGX_LIB_LIBGD=$ngx_feature_libs + ngx_feature="GD WebP support" + ngx_feature_name="NGX_HAVE_GD_WEBP" + ngx_feature_test="gdImagePtr img = gdImageCreateFromWebpPtr(1, NULL);" + . auto/feature + else cat << END diff --git a/src/http/modules/ngx_http_image_filter_module.c b/src/http/modules/ngx_http_image_filter_module.c index b608de13f..dbec5d8e5 100644 --- a/src/http/modules/ngx_http_image_filter_module.c +++ b/src/http/modules/ngx_http_image_filter_module.c @@ -31,6 +31,7 @@ #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 @@ -42,6 +43,7 @@ typedef struct { 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; @@ -51,6 +53,7 @@ typedef struct { 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; @@ -109,6 +112,8 @@ 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); @@ -130,6 +135,13 @@ static ngx_command_t ngx_http_image_filter_commands[] = { 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, @@ -200,7 +212,8 @@ 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/png"), + ngx_string("image/webp") }; @@ -441,6 +454,13 @@ ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in) /* 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; @@ -731,6 +751,56 @@ ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) 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; @@ -1043,6 +1113,15 @@ ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx) 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; @@ -1090,7 +1169,7 @@ ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, { char *failed; u_char *out; - ngx_int_t jq; + ngx_int_t q; ngx_http_image_filter_conf_t *conf; out = NULL; @@ -1100,12 +1179,12 @@ ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, case NGX_HTTP_IMAGE_JPEG: conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); - jq = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); - if (jq <= 0) { + q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); + if (q <= 0) { return NULL; } - out = gdImageJpegPtr(img, size, jq); + out = gdImageJpegPtr(img, size, q); failed = "gdImageJpegPtr() failed"; break; @@ -1119,6 +1198,22 @@ ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, 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; @@ -1196,11 +1291,13 @@ ngx_http_image_filter_create_conf(ngx_conf_t *cf) * 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; @@ -1242,6 +1339,16 @@ ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) } } + 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); @@ -1461,6 +1568,53 @@ ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd, } +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)