├── .gitmodules ├── .gitignore ├── config ├── conf ├── store.conf └── dev.conf ├── LICENSE ├── README.md └── src └── ngx_http_pngquant_module.c /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/pngquant"] 2 | path = deps/pngquant 3 | url = https://github.com/pornel/pngquant 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | #IDE 32 | nbproject 33 | .idea 34 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_pngquant_module 2 | USE_LIBGD=YES 3 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_pngquant_module" 4 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/deps/pngquant/lib/*.c" 5 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_pngquant_module.c" 6 | CFLAGS="$CFLAGS -std=c99 -DNDEBUG -Wno-error=sign-compare -Wno-error=unknown-pragmas" 7 | CORE_LIBS="$CORE_LIBS -lm" 8 | -------------------------------------------------------------------------------- /conf/store.conf: -------------------------------------------------------------------------------- 1 | master_process off; 2 | daemon off; 3 | 4 | error_log stderr debug; 5 | 6 | events { 7 | worker_connections 1024; 8 | } 9 | 10 | http { 11 | access_log off; 12 | 13 | server { 14 | listen 8082; 15 | 16 | set $store_path /tmp/pngquant; 17 | 18 | root /var/www; 19 | 20 | location ~ \.png$ { 21 | root $store_path; 22 | try_files $uri @pngquant; 23 | } 24 | 25 | location @pngquant { 26 | 27 | pngquant on; 28 | pngquant_store $store_path$uri; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /conf/dev.conf: -------------------------------------------------------------------------------- 1 | master_process off; 2 | daemon off; 3 | 4 | error_log stderr debug; 5 | 6 | events { 7 | worker_connections 1024; 8 | } 9 | 10 | http { 11 | access_log off; 12 | 13 | #sendfile on; 14 | #tcp_nopush on; 15 | 16 | keepalive_timeout 0; 17 | #keepalive_timeout 65; 18 | 19 | #gzip on; 20 | 21 | pngquant_temp_path /tmp/temp 1 2; 22 | 23 | server { 24 | listen 8082; 25 | server_name localhost; 26 | 27 | root /tmp/; 28 | 29 | location ~ big.png$ { 30 | 31 | pngquant on; 32 | pngquant_store /dev/null; 33 | } 34 | 35 | location ~ small.png$ { 36 | 37 | pngquant on; 38 | pngquant_store /dev/null; 39 | } 40 | 41 | location ~ \.png$ { 42 | 43 | try_files /store$uri @pngquant; 44 | } 45 | 46 | location @pngquant { 47 | 48 | pngquant on; 49 | pngquant_buffer_size 1M; 50 | pngquant_colors 256; 51 | pngquant_dither on; 52 | pngquant_speed 1; 53 | 54 | pngquant_store /tmp/store$uri; 55 | pngquant_store_access user:rw group:rw all:rw; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Module ngx_pngquant 2 | ============ 3 | 4 | The ``ngx_pngquant`` module is a filter for lossy compression of PNG images. 5 | 6 | ## Configuration 7 | 8 | ```nginx 9 | server { 10 | 11 | set $store_path /tmp/pngquant; 12 | 13 | root /var/www; 14 | 15 | location ~ \.png$ { 16 | root $store_path; 17 | try_files $uri @pngquant; 18 | } 19 | 20 | location @pngquant { 21 | pngquant on; 22 | 23 | pngquant_buffer_size 1M; 24 | pngquant_colors 256; 25 | pngquant_dither on; 26 | pngquant_speed 1; 27 | 28 | pngquant_store $store_path$uri; 29 | pngquant_store_access user:rw group:rw all:r; 30 | } 31 | } 32 | ``` 33 | 34 | ## How to build 35 | 36 | Install module dependencies: 37 | 38 | **Ubuntu or Debian** 39 | 40 | ```sh 41 | sudo apt-get install build-essential libgd-dev 42 | ``` 43 | 44 | **RedHat, CentOS, or Fedora** 45 | 46 | ```sh 47 | sudo yum install gcc-c++ gd-devel pcre-devel make 48 | ``` 49 | 50 | Download ``ngx_pngquant`` and install ``libimagequant`` submodule: 51 | 52 | ```sh 53 | cd 54 | git clone https://github.com/x25/ngx_pngquant 55 | cd ngx_pngquant 56 | git submodule update --init 57 | ``` 58 | 59 | Download and build **nginx**/**openresty**/**tengine** with support for ``ngx_pngquant``: 60 | 61 | ```sh 62 | cd 63 | # check http://nginx.org/en/download.html for the latest version 64 | wget http://nginx.org/download/nginx-1.6.2.tar.gz 65 | tar -xvzf nginx-1.6.2.tar.gz 66 | cd nginx-1.6.2/ 67 | ./configure --prefix=/tmp/nginx --add-module=$HOME/ngx_pngquant 68 | make 69 | sudo make install 70 | ``` 71 | 72 | If you want to have debug logs available: 73 | 74 | ```sh 75 | ./configure --prefix=/tmp/nginx --add-module=$HOME/ngx_pngquant --with-debug 76 | ``` 77 | 78 | Start nginx with pngquant module: 79 | 80 | ```sh 81 | /tmp/nginx/sbin/nginx -c /path/to/nginx.conf 82 | ``` 83 | 84 | ## Directives 85 | 86 | 87 | 88 | 89 | 90 |
Syntax:pngquant on | off;
Default:pngquant off;
Context:location
91 | 92 | Turns on/off module processing in a surrounding location. 93 | 94 | --- 95 | 96 | 97 | 98 | 99 | 100 |
Syntax:pngquant_buffer_size size;
Default:pngquant_buffer_size 1M;
Context:http, server, location
101 | 102 | Sets the maximum size of the buffer used for reading images. When the size is exceeded the server returns error **415 (Unsupported Media Type)**. 103 | 104 | --- 105 | 106 | 107 | 108 | 109 | 110 |
Syntax:pngquant_colors colors;
Default:pngquant_colors 256;
Context:http, server, location
111 | 112 | Sets the maximum number of palette entries in images. 113 | 114 | --- 115 | 116 | 117 | 118 | 119 | 120 |
Syntax:pngquant_dither on | off;
Default:pngquant_dither on;
Context:http, server, location
121 | 122 | If dither is set, the image will be dithered to approximate colors better, at the expense of some obvious "speckling." 123 | 124 | --- 125 | 126 | 127 | 128 | 129 | 130 |
Syntax:pngquant_speed speed;
Default:pngquant_speed 0;
Context:http, server, location
131 | 132 | Speed is from 1 (highest quality) to 10 (fastest). Speed 0 selects library-specific default (recommended). 133 | 134 | --- 135 | 136 | 137 | 138 | 139 | 140 |
Syntax:pngquant_store string;
Default:none
Context:http, server, location
141 | 142 | Enables saving of processed images to a disk. The file name can be set explicitly using the string with variables: 143 | 144 | ``` 145 | pngquant_store /data/www$uri; 146 | ``` 147 | 148 | An example of caching: 149 | 150 | ```nginx 151 | server { 152 | root /var/www; 153 | 154 | location ~ \.png$ { 155 | root /tmp/pngquant; 156 | try_files $uri @pngquant; 157 | } 158 | 159 | location @pngquant { 160 | pngquant on; 161 | pngquant_store /tmp/pngquant$uri; 162 | } 163 | } 164 | ``` 165 | 166 | --- 167 | 168 | 169 | 170 | 171 | 172 |
Syntax:pngquant_temp_path path [level1] [level2] [level3];
Default:pngquant_temp_path /tmp 1 2;
Context:http
173 | 174 | Sets temporary area where files are stored before they are moved to ``pngquant_store`` area. 175 | 176 | --- 177 | 178 | 179 | 180 | 181 | 182 |
Syntax:pngquant_store_access users:permissions ...;
Default:pngquant_store_access user:rw;
Context:http, server, location
183 | 184 | Sets access permissions for newly created files and directories, e.g.: 185 | 186 | ``` 187 | pngquant_store_access user:rw group:rw all:r; 188 | ``` 189 | 190 | If any ``group`` or ``all`` access permissions are specified then user permissions may be omitted: 191 | 192 | ``` 193 | pngquant_store_access group:rw all:r; 194 | ``` 195 | 196 | ## Status 197 | 198 | This module is experimental and it's compatible with following web servers: 199 | 200 | - nginx 1.6.x (tested with 1.6.2). 201 | - nginx 1.7.x (tested with 1.7.9). 202 | 203 | - openresty 1.7.x (tested with 1.7.7.1). 204 | - tengine 2.1.x (tested with 2.1.0). 205 | -------------------------------------------------------------------------------- /src/ngx_http_pngquant_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Igor Sysoev 3 | * Copyright (C) Nginx, Inc. 4 | * Copyright (C) Kornel Lesiński (libimagequant) 5 | * Copyright (C) Thomas G. Lane. (libgd) 6 | * Copyright (C) x25 7 | */ 8 | 9 | /* 10 | * Based on nginx/ngx_http_image_filter_module.c 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include "../deps/pngquant/lib/libimagequant.h" 22 | 23 | 24 | #define NGX_HTTP_PNGQUANT_START 0 25 | #define NGX_HTTP_PNGQUANT_READ 1 26 | #define NGX_HTTP_PNGQUANT_PROCESS 2 27 | #define NGX_HTTP_PNGQUANT_PASS 3 28 | #define NGX_HTTP_PNGQUANT_DONE 4 29 | 30 | 31 | #define NGX_HTTP_PNGQUANT_BUFFERED 0x08 32 | 33 | 34 | typedef struct { 35 | ngx_flag_t enabled; 36 | size_t buffer_size; 37 | ngx_flag_t dither; 38 | ngx_uint_t colors; 39 | ngx_uint_t speed; 40 | ngx_http_complex_value_t *store; 41 | ngx_uint_t store_access; 42 | ngx_path_t *temp_path; 43 | } ngx_http_pngquant_conf_t; 44 | 45 | 46 | typedef struct { 47 | u_char *image; 48 | u_char *last; 49 | size_t length; 50 | ngx_uint_t phase; 51 | } ngx_http_pngquant_ctx_t; 52 | 53 | 54 | ngx_module_t ngx_http_pngquant_module; 55 | 56 | static ngx_int_t ngx_http_pngquant_header_filter(ngx_http_request_t *r); 57 | static void *ngx_http_pngquant_create_conf(ngx_conf_t *cf); 58 | static char *ngx_http_pngquant_merge_conf(ngx_conf_t *cf, void *parent, 59 | void *child); 60 | static ngx_int_t ngx_http_pngquant_init(ngx_conf_t *cf); 61 | static char * 62 | ngx_http_pngquant_store_command(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 63 | 64 | 65 | static ngx_command_t ngx_http_pngquant_commands[] = { 66 | 67 | { ngx_string("pngquant"), 68 | NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 69 | ngx_conf_set_flag_slot, 70 | NGX_HTTP_LOC_CONF_OFFSET, 71 | offsetof(ngx_http_pngquant_conf_t, enabled), 72 | NULL }, 73 | 74 | { ngx_string("pngquant_buffer_size"), 75 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 76 | ngx_conf_set_size_slot, 77 | NGX_HTTP_LOC_CONF_OFFSET, 78 | offsetof(ngx_http_pngquant_conf_t, buffer_size), 79 | NULL }, 80 | 81 | { ngx_string("pngquant_dither"), 82 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 83 | ngx_conf_set_flag_slot, 84 | NGX_HTTP_LOC_CONF_OFFSET, 85 | offsetof(ngx_http_pngquant_conf_t, dither), 86 | NULL }, 87 | 88 | { ngx_string("pngquant_colors"), 89 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 90 | ngx_conf_set_num_slot, 91 | NGX_HTTP_LOC_CONF_OFFSET, 92 | offsetof(ngx_http_pngquant_conf_t, colors), 93 | NULL }, 94 | 95 | { ngx_string("pngquant_speed"), 96 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 97 | ngx_conf_set_num_slot, 98 | NGX_HTTP_LOC_CONF_OFFSET, 99 | offsetof(ngx_http_pngquant_conf_t, speed), 100 | NULL }, 101 | 102 | { ngx_string("pngquant_store"), 103 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 104 | ngx_http_pngquant_store_command, 105 | NGX_HTTP_LOC_CONF_OFFSET, 106 | 0, 107 | NULL }, 108 | 109 | { ngx_string("pngquant_temp_path"), 110 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234, 111 | ngx_conf_set_path_slot, 112 | NGX_HTTP_LOC_CONF_OFFSET, 113 | offsetof(ngx_http_pngquant_conf_t, temp_path), 114 | NULL }, 115 | 116 | { ngx_string("pngquant_store_access"), 117 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, 118 | ngx_conf_set_access_slot, 119 | NGX_HTTP_LOC_CONF_OFFSET, 120 | offsetof(ngx_http_pngquant_conf_t, store_access), 121 | NULL }, 122 | 123 | ngx_null_command 124 | }; 125 | 126 | 127 | static ngx_http_module_t ngx_http_pngquant_module_ctx = { 128 | NULL, /* preconfiguration */ 129 | ngx_http_pngquant_init, /* postconfiguration */ 130 | 131 | NULL, /* create main configuration */ 132 | NULL, /* init main configuration */ 133 | 134 | NULL, /* create server configuration */ 135 | NULL, /* merge server configuration */ 136 | 137 | ngx_http_pngquant_create_conf, /* create location configuration */ 138 | ngx_http_pngquant_merge_conf /* merge location configuration */ 139 | }; 140 | 141 | 142 | ngx_module_t ngx_http_pngquant_module = { 143 | NGX_MODULE_V1, 144 | &ngx_http_pngquant_module_ctx, /* module context */ 145 | ngx_http_pngquant_commands, /* module directives */ 146 | NGX_HTTP_MODULE, /* module type */ 147 | NULL, /* init master */ 148 | NULL, /* init module */ 149 | NULL, /* init process */ 150 | NULL, /* init thread */ 151 | NULL, /* exit thread */ 152 | NULL, /* exit process */ 153 | NULL, /* exit master */ 154 | NGX_MODULE_V1_PADDING 155 | }; 156 | 157 | 158 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 159 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 160 | 161 | 162 | static ngx_path_init_t ngx_http_pngquant_temp_path = { 163 | ngx_string("/tmp"), { 1, 2, 0 } 164 | }; 165 | 166 | 167 | static ngx_str_t ngx_http_pngquant_content_type[] = { 168 | ngx_string("image/png") 169 | }; 170 | 171 | 172 | static ngx_int_t 173 | ngx_http_pngquant_send(ngx_http_request_t *r, ngx_http_pngquant_ctx_t *ctx, 174 | ngx_chain_t *in) 175 | { 176 | ngx_int_t rc; 177 | 178 | rc = ngx_http_next_header_filter(r); 179 | 180 | if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { 181 | 182 | return NGX_ERROR; 183 | } 184 | 185 | rc = ngx_http_next_body_filter(r, in); 186 | 187 | if (ctx->phase == NGX_HTTP_PNGQUANT_DONE) { 188 | /* NGX_ERROR resets any pending data */ 189 | return (rc == NGX_OK) ? NGX_ERROR : rc; 190 | } 191 | 192 | return rc; 193 | } 194 | 195 | 196 | static ngx_uint_t 197 | ngx_http_pngquant_is_png(ngx_http_request_t *r, ngx_chain_t *in) 198 | { 199 | u_char *p; 200 | 201 | p = in->buf->pos; 202 | 203 | if (in->buf->last - p < 16) { 204 | 205 | return NGX_ERROR; 206 | } 207 | 208 | if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G' && 209 | p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a) 210 | { 211 | return NGX_OK; 212 | } 213 | 214 | return NGX_ERROR; 215 | } 216 | 217 | 218 | static ngx_int_t 219 | ngx_http_pngquant_read(ngx_http_request_t *r, ngx_chain_t *in) 220 | { 221 | u_char *p; 222 | ngx_buf_t *b; 223 | ngx_chain_t *cl; 224 | ngx_http_pngquant_ctx_t *ctx; 225 | size_t size, rest; 226 | 227 | ctx = ngx_http_get_module_ctx(r, ngx_http_pngquant_module); 228 | 229 | if (ctx->image == NULL) { 230 | 231 | ctx->image = ngx_palloc(r->pool, ctx->length); 232 | 233 | if (ctx->image == NULL) { 234 | 235 | return NGX_ERROR; 236 | } 237 | 238 | ctx->last = ctx->image; 239 | } 240 | 241 | p = ctx->last; 242 | 243 | for (cl = in; cl; cl = cl->next) { 244 | 245 | b = cl->buf; 246 | size = b->last - b->pos; 247 | 248 | rest = ctx->image + ctx->length - p; 249 | 250 | //too big response 251 | if (size > rest) { 252 | 253 | return NGX_ERROR; 254 | } 255 | 256 | p = ngx_cpymem(p, b->pos, size); 257 | b->pos += size; 258 | 259 | if (b->last_buf) { 260 | 261 | ctx->last = p; 262 | 263 | return NGX_OK; 264 | } 265 | } 266 | 267 | ctx->last = p; 268 | 269 | r->connection->buffered |= NGX_HTTP_PNGQUANT_BUFFERED; 270 | 271 | return NGX_AGAIN; 272 | } 273 | 274 | 275 | static void 276 | ngx_http_pngquant_length(ngx_http_request_t *r, ngx_buf_t *b) 277 | { 278 | r->headers_out.content_length_n = b->last - b->pos; 279 | 280 | if (r->headers_out.content_length) { 281 | r->headers_out.content_length->hash = 0; 282 | } 283 | 284 | r->headers_out.content_length = NULL; 285 | } 286 | 287 | 288 | static ngx_buf_t * 289 | ngx_http_pngquant_asis(ngx_http_request_t *r, ngx_http_pngquant_ctx_t *ctx) 290 | { 291 | ngx_buf_t *b; 292 | 293 | b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); 294 | if (b == NULL) { 295 | return NULL; 296 | } 297 | 298 | b->pos = ctx->image; 299 | b->last = ctx->last; 300 | b->memory = 1; 301 | b->last_buf = 1; 302 | 303 | ngx_http_pngquant_length(r, b); 304 | 305 | return b; 306 | } 307 | 308 | 309 | static void 310 | ngx_http_pngquant_cleanup(void *data) 311 | { 312 | gdFree(data); 313 | } 314 | 315 | 316 | static void 317 | ngx_pngquant_free_true_color_image_data(gdImagePtr oim) 318 | { 319 | int i; 320 | oim->trueColor = 0; 321 | /* Junk the truecolor pixels */ 322 | for (i = 0; i < oim->sy; i++) { 323 | gdFree (oim->tpixels[i]); 324 | } 325 | free (oim->tpixels); 326 | oim->tpixels = 0; 327 | } 328 | 329 | 330 | /** 331 | * Based on libgd/gd_topal.c 332 | */ 333 | static void 334 | ngx_pngquant_convert_gd_pixel_to_rgba(liq_color output_row[], int y, int width, 335 | void *userinfo) 336 | { 337 | gdImagePtr oim = userinfo; 338 | int x; 339 | 340 | for(x = 0; x < width; x++) { 341 | 342 | output_row[x].r = gdTrueColorGetRed(oim->tpixels[y][x]) * 255/gdRedMax; 343 | output_row[x].g = gdTrueColorGetGreen(oim->tpixels[y][x]) * 255/gdGreenMax; 344 | output_row[x].b = gdTrueColorGetBlue(oim->tpixels[y][x]) * 255/gdBlueMax; 345 | 346 | int alpha = gdTrueColorGetAlpha(oim->tpixels[y][x]); 347 | 348 | if (gdAlphaOpaque < gdAlphaTransparent) { 349 | 350 | alpha = gdAlphaTransparent - alpha; 351 | } 352 | 353 | output_row[x].a = alpha * 255/gdAlphaMax; 354 | } 355 | } 356 | 357 | 358 | /** 359 | * Based on libgd/gd_topal.c 360 | */ 361 | static int 362 | ngx_pngquant_gd_image(gdImagePtr oim, int dither, int colorsWanted, int speed) 363 | { 364 | int i; 365 | 366 | int maxColors = gdMaxColors; 367 | 368 | if (!oim->trueColor) { 369 | 370 | return 1; 371 | } 372 | 373 | /* If we have a transparent color (the alphaless mode of transparency), we 374 | * must reserve a palette entry for it at the end of the palette. */ 375 | if (oim->transparent >= 0) { 376 | 377 | maxColors--; 378 | } 379 | 380 | if (colorsWanted > maxColors) { 381 | 382 | colorsWanted = maxColors; 383 | } 384 | 385 | oim->pixels = calloc(sizeof (unsigned char *), oim->sy); 386 | 387 | if (!oim->pixels) { 388 | /* No can do */ 389 | goto outOfMemory; 390 | } 391 | 392 | for (i = 0; (i < oim->sy); i++) { 393 | 394 | oim->pixels[i] = (unsigned char *) calloc(sizeof (unsigned char *), 395 | oim->sx); 396 | 397 | if (!oim->pixels[i]) { 398 | goto outOfMemory; 399 | } 400 | } 401 | 402 | liq_attr *attr = liq_attr_create_with_allocator(malloc, gdFree); 403 | 404 | liq_image *image; 405 | liq_result *remap; 406 | int remapped_ok = 0; 407 | 408 | liq_set_max_colors(attr, colorsWanted); 409 | 410 | /* by default make it fast to match speed of previous implementation */ 411 | liq_set_speed(attr, speed ? speed : 9); 412 | 413 | if (oim->paletteQuantizationMaxQuality) { 414 | 415 | liq_set_quality(attr, 416 | oim->paletteQuantizationMinQuality, 417 | oim->paletteQuantizationMaxQuality); 418 | } 419 | 420 | image = liq_image_create_custom(attr, ngx_pngquant_convert_gd_pixel_to_rgba, 421 | oim, oim->sx, oim->sy, 0); 422 | remap = liq_quantize_image(attr, image); 423 | 424 | if (!remap) { /* minimum quality not met, leave image unmodified */ 425 | 426 | liq_image_destroy(image); 427 | liq_attr_destroy(attr); 428 | 429 | goto outOfMemory; 430 | } 431 | 432 | liq_set_dithering_level(remap, dither ? 1 : 0); 433 | 434 | if (LIQ_OK == liq_write_remapped_image_rows(remap, image, oim->pixels)) { 435 | 436 | remapped_ok = 1; 437 | 438 | const liq_palette *pal = liq_get_palette(remap); 439 | 440 | oim->transparent = -1; 441 | 442 | unsigned int icolor; 443 | 444 | for(icolor=0; icolor < pal->count; icolor++) { 445 | 446 | oim->open[icolor] = 0; 447 | oim->red[icolor] = pal->entries[icolor].r * gdRedMax/255; 448 | oim->green[icolor] = pal->entries[icolor].g * gdGreenMax/255; 449 | oim->blue[icolor] = pal->entries[icolor].b * gdBlueMax/255; 450 | 451 | int alpha = pal->entries[icolor].a * gdAlphaMax/255; 452 | 453 | if (gdAlphaOpaque < gdAlphaTransparent) { 454 | 455 | alpha = gdAlphaTransparent - alpha; 456 | } 457 | 458 | oim->alpha[icolor] = alpha; 459 | 460 | if (oim->transparent == -1 && alpha == gdAlphaTransparent) { 461 | 462 | oim->transparent = icolor; 463 | } 464 | } 465 | 466 | oim->colorsTotal = pal->count; 467 | } 468 | 469 | liq_result_destroy(remap); 470 | liq_image_destroy(image); 471 | liq_attr_destroy(attr); 472 | 473 | if (remapped_ok) { 474 | 475 | ngx_pngquant_free_true_color_image_data(oim); 476 | 477 | return 1; 478 | } 479 | 480 | outOfMemory: 481 | 482 | if (oim->trueColor) { 483 | 484 | /* On failure only */ 485 | if (oim->pixels) { 486 | 487 | for (i = 0; i < oim->sy; i++) { 488 | 489 | if (oim->pixels[i]) { 490 | gdFree (oim->pixels[i]); 491 | } 492 | } 493 | 494 | gdFree (oim->pixels); 495 | } 496 | 497 | oim->pixels = NULL; 498 | } 499 | 500 | return 0; 501 | } 502 | 503 | 504 | static ngx_buf_t * 505 | ngx_http_pngquant_quantize(ngx_http_request_t *r, ngx_http_pngquant_ctx_t *ctx) 506 | { 507 | u_char *out; 508 | ngx_buf_t *b; 509 | ngx_pool_cleanup_t *cln; 510 | ngx_http_pngquant_conf_t *conf; 511 | gdImagePtr img; 512 | int size; 513 | 514 | ngx_int_t rc; 515 | ngx_temp_file_t *tf; 516 | ssize_t n; 517 | ngx_ext_rename_file_t ext; 518 | ngx_str_t dest; 519 | ngx_str_t value; 520 | 521 | 522 | img = gdImageCreateFromPngPtr(ctx->length, ctx->image); 523 | 524 | if (img == NULL) { 525 | 526 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 527 | "gdImageCreateFromPngPtr() failed"); 528 | 529 | return NULL; 530 | } 531 | 532 | conf = ngx_http_get_module_loc_conf(r, ngx_http_pngquant_module); 533 | 534 | /* 535 | * gdImageTrueColorToPaletteSetMethod(img, GD_QUANT_LIQ, conf->speed); 536 | * gdImageTrueColorToPalette(img, conf->dither, conf->colors); 537 | */ 538 | 539 | ngx_pngquant_gd_image(img, conf->dither, conf->colors, conf->speed); 540 | 541 | out = gdImagePngPtr(img, &size); 542 | 543 | gdImageDestroy(img); 544 | 545 | ngx_pfree(r->pool, ctx->image); 546 | 547 | if (out == NULL) { 548 | 549 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 550 | "gdImagePngPtr() failed"); 551 | 552 | return NULL; 553 | } 554 | 555 | if (conf->store) { 556 | 557 | if(ngx_http_complex_value(r, conf->store, &value) != NGX_OK) { 558 | 559 | goto failed; 560 | } 561 | 562 | dest.len = value.len + 1; 563 | dest.data = ngx_pnalloc(r->pool, dest.len); 564 | 565 | if (dest.data == NULL) { 566 | 567 | goto failed; 568 | } 569 | 570 | ngx_memzero(dest.data, dest.len); 571 | ngx_memcpy(dest.data, value.data, value.len); 572 | 573 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 574 | "pngquant_store (%s)", dest.data); 575 | 576 | tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); 577 | 578 | if (tf == NULL) { 579 | 580 | goto failed; 581 | } 582 | 583 | tf->file.fd = NGX_INVALID_FILE; 584 | tf->file.log = r->connection->log; 585 | tf->path = conf->temp_path; 586 | tf->pool = r->pool; 587 | tf->persistent = 1; 588 | rc = ngx_create_temp_file(&tf->file, tf->path, tf->pool, tf->persistent, 589 | tf->clean, tf->access); 590 | 591 | if (rc != NGX_OK) { 592 | 593 | goto failed; 594 | } 595 | 596 | n = ngx_write_fd(tf->file.fd, out, size); 597 | 598 | if (n == NGX_FILE_ERROR) { 599 | 600 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, 601 | ngx_write_fd_n " \"%s\" failed", tf->file.name.data); 602 | 603 | goto failed; 604 | } 605 | 606 | if ((int) n != size) { 607 | 608 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, 609 | ngx_write_fd_n " has written only %z of %uz bytes", 610 | n, size); 611 | 612 | goto failed; 613 | } 614 | 615 | ext.access = conf->store_access; 616 | ext.path_access = conf->store_access; 617 | ext.time = -1; 618 | ext.create_path = 1; 619 | ext.delete_file = 1; 620 | ext.log = r->connection->log; 621 | 622 | rc = ngx_ext_rename_file(&tf->file.name, &dest, &ext); 623 | 624 | if (rc != NGX_OK) { 625 | 626 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 627 | "ngx_ext_rename_file() failed"); 628 | 629 | goto failed; 630 | } 631 | } 632 | 633 | cln = ngx_pool_cleanup_add(r->pool, 0); 634 | 635 | if (cln == NULL) { 636 | 637 | goto failed; 638 | } 639 | 640 | b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); 641 | 642 | if (b == NULL) { 643 | 644 | goto failed; 645 | } 646 | 647 | cln->handler = ngx_http_pngquant_cleanup; 648 | cln->data = out; 649 | 650 | b->pos = out; 651 | b->last = out + size; 652 | b->memory = 1; 653 | b->last_buf = 1; 654 | 655 | ngx_http_pngquant_length(r, b); 656 | 657 | #if defined(nginx_version) && (nginx_version >= 1007003) 658 | ngx_http_weak_etag(r); 659 | #endif 660 | 661 | return b; 662 | 663 | failed: 664 | 665 | gdFree(out); 666 | 667 | return NULL; 668 | } 669 | 670 | 671 | static ngx_buf_t * 672 | ngx_http_pngquant_process(ngx_http_request_t *r) 673 | { 674 | ngx_http_pngquant_ctx_t *ctx; 675 | ngx_http_pngquant_conf_t *conf; 676 | 677 | r->connection->buffered &= ~NGX_HTTP_PNGQUANT_BUFFERED; 678 | 679 | ctx = ngx_http_get_module_ctx(r, ngx_http_pngquant_module); 680 | 681 | conf = ngx_http_get_module_loc_conf(r, ngx_http_pngquant_module); 682 | 683 | if (conf->enabled) { 684 | 685 | return ngx_http_pngquant_quantize(r, ctx); 686 | } 687 | 688 | return ngx_http_pngquant_asis(r, ctx); 689 | } 690 | 691 | 692 | static ngx_int_t 693 | ngx_http_pngquant_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 694 | { 695 | ngx_int_t rc; 696 | ngx_str_t *ct; 697 | ngx_chain_t out; 698 | ngx_http_pngquant_ctx_t *ctx; 699 | 700 | if (in == NULL) { 701 | 702 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 703 | "pngquant_body_filter (!in)"); 704 | 705 | return ngx_http_next_body_filter(r, in); 706 | } 707 | 708 | ctx = ngx_http_get_module_ctx(r, ngx_http_pngquant_module); 709 | 710 | if (ctx == NULL) { 711 | 712 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 713 | "pngquant_body_filter (!ctx)"); 714 | 715 | return ngx_http_next_body_filter(r, in); 716 | } 717 | 718 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 719 | "pngquant_body_filter"); 720 | 721 | switch (ctx->phase) { 722 | 723 | case NGX_HTTP_PNGQUANT_START: 724 | 725 | if (NGX_OK != ngx_http_pngquant_is_png(r, in)) { 726 | 727 | return ngx_http_filter_finalize_request(r, 728 | &ngx_http_pngquant_module, 729 | NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); 730 | } 731 | 732 | /* override content type */ 733 | 734 | ct = &ngx_http_pngquant_content_type[0]; 735 | r->headers_out.content_type_len = ct->len; 736 | r->headers_out.content_type = *ct; 737 | r->headers_out.content_type_lowcase = NULL; 738 | 739 | ctx->phase = NGX_HTTP_PNGQUANT_READ; 740 | 741 | /* fall through */ 742 | 743 | case NGX_HTTP_PNGQUANT_READ: 744 | 745 | rc = ngx_http_pngquant_read(r, in); 746 | 747 | if (rc == NGX_AGAIN) { 748 | 749 | return NGX_OK; 750 | } 751 | 752 | if (rc == NGX_ERROR) { 753 | 754 | return ngx_http_filter_finalize_request(r, 755 | &ngx_http_pngquant_module, 756 | NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); 757 | } 758 | 759 | /* fall through */ 760 | 761 | case NGX_HTTP_PNGQUANT_PROCESS: 762 | 763 | out.buf = ngx_http_pngquant_process(r); 764 | 765 | if (out.buf == NULL) { 766 | 767 | return ngx_http_filter_finalize_request(r, 768 | &ngx_http_pngquant_module, 769 | NGX_HTTP_UNSUPPORTED_MEDIA_TYPE); 770 | } 771 | 772 | out.next = NULL; 773 | ctx->phase = NGX_HTTP_PNGQUANT_PASS; 774 | 775 | return ngx_http_pngquant_send(r, ctx, &out); 776 | 777 | case NGX_HTTP_PNGQUANT_PASS: 778 | 779 | return ngx_http_next_body_filter(r, in); 780 | 781 | case NGX_HTTP_PNGQUANT_DONE: 782 | default: 783 | 784 | rc = ngx_http_next_body_filter(r, NULL); 785 | 786 | /* NGX_ERROR resets any pending data */ 787 | return (rc == NGX_OK) ? NGX_ERROR : rc; 788 | } 789 | } 790 | 791 | 792 | static ngx_int_t 793 | ngx_http_pngquant_header_filter(ngx_http_request_t *r) 794 | { 795 | off_t len; 796 | ngx_http_pngquant_ctx_t *ctx; 797 | ngx_http_pngquant_conf_t *conf; 798 | 799 | if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { 800 | 801 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 802 | "pngquant_header_filter (not_modified)"); 803 | 804 | return ngx_http_next_header_filter(r); 805 | } 806 | 807 | ctx = ngx_http_get_module_ctx(r, ngx_http_pngquant_module); 808 | 809 | if (ctx) { 810 | 811 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 812 | "pngquant_header_filter (set_ctx)"); 813 | 814 | ngx_http_set_ctx(r, NULL, ngx_http_pngquant_module); 815 | 816 | return ngx_http_next_header_filter(r); 817 | } 818 | 819 | conf = ngx_http_get_module_loc_conf(r, ngx_http_pngquant_module); 820 | 821 | if (!conf->enabled) { 822 | 823 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 824 | "pngquant_header_filter (!enabled)"); 825 | 826 | return ngx_http_next_header_filter(r); 827 | } 828 | 829 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 830 | "pngquant_header_filter"); 831 | 832 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_pngquant_ctx_t)); 833 | 834 | if (ctx == NULL) { 835 | 836 | return NGX_ERROR; 837 | } 838 | 839 | ngx_http_set_ctx(r, ctx, ngx_http_pngquant_module); 840 | 841 | len = r->headers_out.content_length_n; 842 | 843 | if (len != -1 && len > (off_t) conf->buffer_size) { 844 | 845 | return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; 846 | } 847 | 848 | if (len == -1) { 849 | 850 | ctx->length = conf->buffer_size; 851 | 852 | } else { 853 | 854 | ctx->length = (size_t) len; 855 | } 856 | 857 | if (r->headers_out.refresh) { 858 | 859 | r->headers_out.refresh->hash = 0; 860 | } 861 | 862 | r->main_filter_need_in_memory = 1; 863 | 864 | r->allow_ranges = 0; 865 | 866 | return NGX_OK; 867 | } 868 | 869 | 870 | static void * 871 | ngx_http_pngquant_create_conf(ngx_conf_t *cf) 872 | { 873 | ngx_http_pngquant_conf_t *conf; 874 | 875 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_pngquant_conf_t)); 876 | 877 | if (conf == NULL) { 878 | 879 | return NGX_CONF_ERROR; 880 | } 881 | 882 | /* 883 | * via ngx_pcalloc(): 884 | * conf->store = NULL; 885 | * conf->temp_path = NULL; 886 | */ 887 | 888 | conf->enabled = NGX_CONF_UNSET; 889 | conf->buffer_size = NGX_CONF_UNSET_SIZE; 890 | conf->dither = NGX_CONF_UNSET; 891 | conf->colors = NGX_CONF_UNSET_UINT; 892 | conf->speed = NGX_CONF_UNSET_UINT; 893 | conf->store = NGX_CONF_UNSET_PTR; 894 | conf->store_access = NGX_CONF_UNSET_UINT; 895 | 896 | return conf; 897 | } 898 | 899 | 900 | static char * 901 | ngx_http_pngquant_merge_conf(ngx_conf_t *cf, void *parent, void *child) 902 | { 903 | ngx_http_pngquant_conf_t *prev = parent; 904 | ngx_http_pngquant_conf_t *conf = child; 905 | 906 | ngx_conf_merge_value(conf->enabled, prev->enabled, 0); 907 | 908 | ngx_conf_merge_value(conf->dither, prev->dither, 1); 909 | 910 | ngx_conf_merge_uint_value(conf->colors, prev->colors, 256); 911 | 912 | ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 913 | 1 * 1024 * 1024); 914 | 915 | ngx_conf_merge_uint_value(conf->speed, prev->speed, 0); 916 | 917 | if (ngx_conf_merge_path_value(cf, &conf->temp_path, prev->temp_path, 918 | &ngx_http_pngquant_temp_path) != NGX_OK) 919 | { 920 | return NGX_CONF_ERROR; 921 | } 922 | 923 | ngx_conf_merge_ptr_value(conf->store, prev->store, NULL); 924 | 925 | ngx_conf_merge_uint_value(conf->store_access, 926 | prev->store_access, NGX_FILE_OWNER_ACCESS); 927 | 928 | if (conf->colors < 1) { 929 | 930 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 931 | "pngquant_colors must be equal or more than 1"); 932 | 933 | return NGX_CONF_ERROR; 934 | } 935 | 936 | if (conf->colors > 256) { 937 | 938 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 939 | "pngquant_colors must be equal or less than 256"); 940 | 941 | return NGX_CONF_ERROR; 942 | } 943 | 944 | return NGX_CONF_OK; 945 | } 946 | 947 | static ngx_int_t 948 | ngx_http_pngquant_init(ngx_conf_t *cf) 949 | { 950 | ngx_http_next_header_filter = ngx_http_top_header_filter; 951 | ngx_http_top_header_filter = ngx_http_pngquant_header_filter; 952 | 953 | ngx_http_next_body_filter = ngx_http_top_body_filter; 954 | ngx_http_top_body_filter = ngx_http_pngquant_body_filter; 955 | 956 | return NGX_OK; 957 | } 958 | 959 | static char * 960 | ngx_http_pngquant_store_command(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 961 | { 962 | ngx_http_pngquant_conf_t *pqlc = conf; 963 | ngx_str_t *value; 964 | ngx_http_compile_complex_value_t ccv; 965 | 966 | value = cf->args->elts; 967 | 968 | pqlc->store = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); 969 | 970 | if(pqlc->store == NULL) { 971 | 972 | return NGX_CONF_ERROR; 973 | } 974 | 975 | ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); 976 | 977 | ccv.cf = cf; 978 | ccv.value = &value[1]; 979 | ccv.complex_value = pqlc->store; 980 | 981 | if(ngx_http_compile_complex_value(&ccv) != NGX_OK) { 982 | 983 | return NGX_CONF_ERROR; 984 | } 985 | 986 | return NGX_CONF_OK; 987 | } 988 | --------------------------------------------------------------------------------