changeset 6779:e4b00a021cea

Image filter: support for WebP. In collaboration with Ivan Poluyanov.
author Valentin Bartenev <vbart@nginx.com>
date Fri, 21 Oct 2016 15:18:44 +0300
parents 5e95b9fb33b7
children 56d6bfe6b609
files auto/lib/libgd/conf src/http/modules/ngx_http_image_filter_module.c
diffstat 2 files changed, 164 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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_c
     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_fil
       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   
 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 *
         /* 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 *
 
         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
         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
 {
     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
     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
         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_co
      *     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_con
         }
     }
 
+    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);
 
@@ -1462,6 +1569,53 @@ ngx_http_image_filter_jpeg_quality(ngx_c
 
 
 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)
 {