Mercurial > hg > nginx
comparison src/http/modules/ngx_http_image_filter_module.c @ 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 | f01ab2dbcfdc |
children | 99934aade555 |
comparison
equal
deleted
inserted
replaced
6778:5e95b9fb33b7 | 6779:e4b00a021cea |
---|---|
29 | 29 |
30 #define NGX_HTTP_IMAGE_NONE 0 | 30 #define NGX_HTTP_IMAGE_NONE 0 |
31 #define NGX_HTTP_IMAGE_JPEG 1 | 31 #define NGX_HTTP_IMAGE_JPEG 1 |
32 #define NGX_HTTP_IMAGE_GIF 2 | 32 #define NGX_HTTP_IMAGE_GIF 2 |
33 #define NGX_HTTP_IMAGE_PNG 3 | 33 #define NGX_HTTP_IMAGE_PNG 3 |
34 #define NGX_HTTP_IMAGE_WEBP 4 | |
34 | 35 |
35 | 36 |
36 #define NGX_HTTP_IMAGE_BUFFERED 0x08 | 37 #define NGX_HTTP_IMAGE_BUFFERED 0x08 |
37 | 38 |
38 | 39 |
40 ngx_uint_t filter; | 41 ngx_uint_t filter; |
41 ngx_uint_t width; | 42 ngx_uint_t width; |
42 ngx_uint_t height; | 43 ngx_uint_t height; |
43 ngx_uint_t angle; | 44 ngx_uint_t angle; |
44 ngx_uint_t jpeg_quality; | 45 ngx_uint_t jpeg_quality; |
46 ngx_uint_t webp_quality; | |
45 ngx_uint_t sharpen; | 47 ngx_uint_t sharpen; |
46 | 48 |
47 ngx_flag_t transparency; | 49 ngx_flag_t transparency; |
48 ngx_flag_t interlace; | 50 ngx_flag_t interlace; |
49 | 51 |
50 ngx_http_complex_value_t *wcv; | 52 ngx_http_complex_value_t *wcv; |
51 ngx_http_complex_value_t *hcv; | 53 ngx_http_complex_value_t *hcv; |
52 ngx_http_complex_value_t *acv; | 54 ngx_http_complex_value_t *acv; |
53 ngx_http_complex_value_t *jqcv; | 55 ngx_http_complex_value_t *jqcv; |
56 ngx_http_complex_value_t *wqcv; | |
54 ngx_http_complex_value_t *shcv; | 57 ngx_http_complex_value_t *shcv; |
55 | 58 |
56 size_t buffer_size; | 59 size_t buffer_size; |
57 } ngx_http_image_filter_conf_t; | 60 } ngx_http_image_filter_conf_t; |
58 | 61 |
107 void *child); | 110 void *child); |
108 static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, | 111 static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, |
109 void *conf); | 112 void *conf); |
110 static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, | 113 static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, |
111 ngx_command_t *cmd, void *conf); | 114 ngx_command_t *cmd, void *conf); |
115 static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf, | |
116 ngx_command_t *cmd, void *conf); | |
112 static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, | 117 static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, |
113 void *conf); | 118 void *conf); |
114 static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf); | 119 static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf); |
115 | 120 |
116 | 121 |
124 NULL }, | 129 NULL }, |
125 | 130 |
126 { ngx_string("image_filter_jpeg_quality"), | 131 { ngx_string("image_filter_jpeg_quality"), |
127 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, | 132 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, |
128 ngx_http_image_filter_jpeg_quality, | 133 ngx_http_image_filter_jpeg_quality, |
134 NGX_HTTP_LOC_CONF_OFFSET, | |
135 0, | |
136 NULL }, | |
137 | |
138 { ngx_string("image_filter_webp_quality"), | |
139 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, | |
140 ngx_http_image_filter_webp_quality, | |
129 NGX_HTTP_LOC_CONF_OFFSET, | 141 NGX_HTTP_LOC_CONF_OFFSET, |
130 0, | 142 0, |
131 NULL }, | 143 NULL }, |
132 | 144 |
133 { ngx_string("image_filter_sharpen"), | 145 { ngx_string("image_filter_sharpen"), |
198 | 210 |
199 | 211 |
200 static ngx_str_t ngx_http_image_types[] = { | 212 static ngx_str_t ngx_http_image_types[] = { |
201 ngx_string("image/jpeg"), | 213 ngx_string("image/jpeg"), |
202 ngx_string("image/gif"), | 214 ngx_string("image/gif"), |
203 ngx_string("image/png") | 215 ngx_string("image/png"), |
216 ngx_string("image/webp") | |
204 }; | 217 }; |
205 | 218 |
206 | 219 |
207 static ngx_int_t | 220 static ngx_int_t |
208 ngx_http_image_header_filter(ngx_http_request_t *r) | 221 ngx_http_image_header_filter(ngx_http_request_t *r) |
439 && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a) | 452 && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a) |
440 { | 453 { |
441 /* PNG */ | 454 /* PNG */ |
442 | 455 |
443 return NGX_HTTP_IMAGE_PNG; | 456 return NGX_HTTP_IMAGE_PNG; |
457 | |
458 } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F' | |
459 && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P') | |
460 { | |
461 /* WebP */ | |
462 | |
463 return NGX_HTTP_IMAGE_WEBP; | |
444 } | 464 } |
445 | 465 |
446 return NGX_HTTP_IMAGE_NONE; | 466 return NGX_HTTP_IMAGE_NONE; |
447 } | 467 } |
448 | 468 |
726 return NGX_DECLINED; | 746 return NGX_DECLINED; |
727 } | 747 } |
728 | 748 |
729 width = p[18] * 256 + p[19]; | 749 width = p[18] * 256 + p[19]; |
730 height = p[22] * 256 + p[23]; | 750 height = p[22] * 256 + p[23]; |
751 | |
752 break; | |
753 | |
754 case NGX_HTTP_IMAGE_WEBP: | |
755 | |
756 if (ctx->length < 30) { | |
757 return NGX_DECLINED; | |
758 } | |
759 | |
760 if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') { | |
761 return NGX_DECLINED; | |
762 } | |
763 | |
764 switch (p[15]) { | |
765 | |
766 case ' ': | |
767 if (p[20] & 1) { | |
768 /* not a key frame */ | |
769 return NGX_DECLINED; | |
770 } | |
771 | |
772 if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) { | |
773 /* invalid start code */ | |
774 return NGX_DECLINED; | |
775 } | |
776 | |
777 width = (p[26] | p[27] << 8) & 0x3fff; | |
778 height = (p[28] | p[29] << 8) & 0x3fff; | |
779 | |
780 break; | |
781 | |
782 case 'L': | |
783 if (p[20] != 0x2f) { | |
784 /* invalid signature */ | |
785 return NGX_DECLINED; | |
786 } | |
787 | |
788 width = ((p[21] | p[22] << 8) & 0x3fff) + 1; | |
789 height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1; | |
790 | |
791 break; | |
792 | |
793 case 'X': | |
794 width = (p[24] | p[25] << 8 | p[26] << 16) + 1; | |
795 height = (p[27] | p[28] << 8 | p[29] << 16) + 1; | |
796 break; | |
797 | |
798 default: | |
799 return NGX_DECLINED; | |
800 } | |
731 | 801 |
732 break; | 802 break; |
733 | 803 |
734 default: | 804 default: |
735 | 805 |
1041 case NGX_HTTP_IMAGE_PNG: | 1111 case NGX_HTTP_IMAGE_PNG: |
1042 img = gdImageCreateFromPngPtr(ctx->length, ctx->image); | 1112 img = gdImageCreateFromPngPtr(ctx->length, ctx->image); |
1043 failed = "gdImageCreateFromPngPtr() failed"; | 1113 failed = "gdImageCreateFromPngPtr() failed"; |
1044 break; | 1114 break; |
1045 | 1115 |
1116 case NGX_HTTP_IMAGE_WEBP: | |
1117 #if (NGX_HAVE_GD_WEBP) | |
1118 img = gdImageCreateFromWebpPtr(ctx->length, ctx->image); | |
1119 failed = "gdImageCreateFromWebpPtr() failed"; | |
1120 #else | |
1121 failed = "nginx was built without GD WebP support"; | |
1122 #endif | |
1123 break; | |
1124 | |
1046 default: | 1125 default: |
1047 failed = "unknown image type"; | 1126 failed = "unknown image type"; |
1048 break; | 1127 break; |
1049 } | 1128 } |
1050 | 1129 |
1088 ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, | 1167 ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img, |
1089 int *size) | 1168 int *size) |
1090 { | 1169 { |
1091 char *failed; | 1170 char *failed; |
1092 u_char *out; | 1171 u_char *out; |
1093 ngx_int_t jq; | 1172 ngx_int_t q; |
1094 ngx_http_image_filter_conf_t *conf; | 1173 ngx_http_image_filter_conf_t *conf; |
1095 | 1174 |
1096 out = NULL; | 1175 out = NULL; |
1097 | 1176 |
1098 switch (type) { | 1177 switch (type) { |
1099 | 1178 |
1100 case NGX_HTTP_IMAGE_JPEG: | 1179 case NGX_HTTP_IMAGE_JPEG: |
1101 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); | 1180 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); |
1102 | 1181 |
1103 jq = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); | 1182 q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); |
1104 if (jq <= 0) { | 1183 if (q <= 0) { |
1105 return NULL; | 1184 return NULL; |
1106 } | 1185 } |
1107 | 1186 |
1108 out = gdImageJpegPtr(img, size, jq); | 1187 out = gdImageJpegPtr(img, size, q); |
1109 failed = "gdImageJpegPtr() failed"; | 1188 failed = "gdImageJpegPtr() failed"; |
1110 break; | 1189 break; |
1111 | 1190 |
1112 case NGX_HTTP_IMAGE_GIF: | 1191 case NGX_HTTP_IMAGE_GIF: |
1113 out = gdImageGifPtr(img, size); | 1192 out = gdImageGifPtr(img, size); |
1115 break; | 1194 break; |
1116 | 1195 |
1117 case NGX_HTTP_IMAGE_PNG: | 1196 case NGX_HTTP_IMAGE_PNG: |
1118 out = gdImagePngPtr(img, size); | 1197 out = gdImagePngPtr(img, size); |
1119 failed = "gdImagePngPtr() failed"; | 1198 failed = "gdImagePngPtr() failed"; |
1199 break; | |
1200 | |
1201 case NGX_HTTP_IMAGE_WEBP: | |
1202 #if (NGX_HAVE_GD_WEBP) | |
1203 conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); | |
1204 | |
1205 q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality); | |
1206 if (q <= 0) { | |
1207 return NULL; | |
1208 } | |
1209 | |
1210 out = gdImageWebpPtrEx(img, size, q); | |
1211 failed = "gdImageWebpPtrEx() failed"; | |
1212 #else | |
1213 failed = "nginx was built without GD WebP support"; | |
1214 #endif | |
1120 break; | 1215 break; |
1121 | 1216 |
1122 default: | 1217 default: |
1123 failed = "unknown image type"; | 1218 failed = "unknown image type"; |
1124 break; | 1219 break; |
1194 * conf->angle = 0; | 1289 * conf->angle = 0; |
1195 * conf->wcv = NULL; | 1290 * conf->wcv = NULL; |
1196 * conf->hcv = NULL; | 1291 * conf->hcv = NULL; |
1197 * conf->acv = NULL; | 1292 * conf->acv = NULL; |
1198 * conf->jqcv = NULL; | 1293 * conf->jqcv = NULL; |
1294 * conf->wqcv = NULL; | |
1199 * conf->shcv = NULL; | 1295 * conf->shcv = NULL; |
1200 */ | 1296 */ |
1201 | 1297 |
1202 conf->filter = NGX_CONF_UNSET_UINT; | 1298 conf->filter = NGX_CONF_UNSET_UINT; |
1203 conf->jpeg_quality = NGX_CONF_UNSET_UINT; | 1299 conf->jpeg_quality = NGX_CONF_UNSET_UINT; |
1300 conf->webp_quality = NGX_CONF_UNSET_UINT; | |
1204 conf->sharpen = NGX_CONF_UNSET_UINT; | 1301 conf->sharpen = NGX_CONF_UNSET_UINT; |
1205 conf->transparency = NGX_CONF_UNSET; | 1302 conf->transparency = NGX_CONF_UNSET; |
1206 conf->interlace = NGX_CONF_UNSET; | 1303 conf->interlace = NGX_CONF_UNSET; |
1207 conf->buffer_size = NGX_CONF_UNSET_SIZE; | 1304 conf->buffer_size = NGX_CONF_UNSET_SIZE; |
1208 | 1305 |
1240 if (conf->jqcv == NULL) { | 1337 if (conf->jqcv == NULL) { |
1241 conf->jqcv = prev->jqcv; | 1338 conf->jqcv = prev->jqcv; |
1242 } | 1339 } |
1243 } | 1340 } |
1244 | 1341 |
1342 if (conf->webp_quality == NGX_CONF_UNSET_UINT) { | |
1343 | |
1344 /* 80 is libwebp default quality */ | |
1345 ngx_conf_merge_uint_value(conf->webp_quality, prev->webp_quality, 80); | |
1346 | |
1347 if (conf->wqcv == NULL) { | |
1348 conf->wqcv = prev->wqcv; | |
1349 } | |
1350 } | |
1351 | |
1245 if (conf->sharpen == NGX_CONF_UNSET_UINT) { | 1352 if (conf->sharpen == NGX_CONF_UNSET_UINT) { |
1246 ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0); | 1353 ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0); |
1247 | 1354 |
1248 if (conf->shcv == NULL) { | 1355 if (conf->shcv == NULL) { |
1249 conf->shcv = prev->shcv; | 1356 conf->shcv = prev->shcv; |
1460 return NGX_CONF_OK; | 1567 return NGX_CONF_OK; |
1461 } | 1568 } |
1462 | 1569 |
1463 | 1570 |
1464 static char * | 1571 static char * |
1465 ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, | 1572 ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd, |
1466 void *conf) | 1573 void *conf) |
1467 { | 1574 { |
1468 ngx_http_image_filter_conf_t *imcf = conf; | 1575 ngx_http_image_filter_conf_t *imcf = conf; |
1469 | 1576 |
1470 ngx_str_t *value; | 1577 ngx_str_t *value; |
1485 } | 1592 } |
1486 | 1593 |
1487 if (cv.lengths == NULL) { | 1594 if (cv.lengths == NULL) { |
1488 n = ngx_http_image_filter_value(&value[1]); | 1595 n = ngx_http_image_filter_value(&value[1]); |
1489 | 1596 |
1597 if (n <= 0) { | |
1598 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | |
1599 "invalid value \"%V\"", &value[1]); | |
1600 return NGX_CONF_ERROR; | |
1601 } | |
1602 | |
1603 imcf->webp_quality = (ngx_uint_t) n; | |
1604 | |
1605 } else { | |
1606 imcf->wqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); | |
1607 if (imcf->wqcv == NULL) { | |
1608 return NGX_CONF_ERROR; | |
1609 } | |
1610 | |
1611 *imcf->wqcv = cv; | |
1612 } | |
1613 | |
1614 return NGX_CONF_OK; | |
1615 } | |
1616 | |
1617 | |
1618 static char * | |
1619 ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, | |
1620 void *conf) | |
1621 { | |
1622 ngx_http_image_filter_conf_t *imcf = conf; | |
1623 | |
1624 ngx_str_t *value; | |
1625 ngx_int_t n; | |
1626 ngx_http_complex_value_t cv; | |
1627 ngx_http_compile_complex_value_t ccv; | |
1628 | |
1629 value = cf->args->elts; | |
1630 | |
1631 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); | |
1632 | |
1633 ccv.cf = cf; | |
1634 ccv.value = &value[1]; | |
1635 ccv.complex_value = &cv; | |
1636 | |
1637 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { | |
1638 return NGX_CONF_ERROR; | |
1639 } | |
1640 | |
1641 if (cv.lengths == NULL) { | |
1642 n = ngx_http_image_filter_value(&value[1]); | |
1643 | |
1490 if (n < 0) { | 1644 if (n < 0) { |
1491 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 1645 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, |
1492 "invalid value \"%V\"", &value[1]); | 1646 "invalid value \"%V\"", &value[1]); |
1493 return NGX_CONF_ERROR; | 1647 return NGX_CONF_ERROR; |
1494 } | 1648 } |