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 }