├── .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 | | Syntax: | pngquant on | off; |
88 | | Default: | pngquant off; |
89 | | Context: | location |
90 |
91 |
92 | Turns on/off module processing in a surrounding location.
93 |
94 | ---
95 |
96 |
97 | | Syntax: | pngquant_buffer_size size; |
98 | | Default: | pngquant_buffer_size 1M; |
99 | | Context: | http, server, location |
100 |
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 | | Syntax: | pngquant_colors colors; |
108 | | Default: | pngquant_colors 256; |
109 | | Context: | http, server, location |
110 |
111 |
112 | Sets the maximum number of palette entries in images.
113 |
114 | ---
115 |
116 |
117 | | Syntax: | pngquant_dither on | off; |
118 | | Default: | pngquant_dither on; |
119 | | Context: | http, server, location |
120 |
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 | | Syntax: | pngquant_speed speed; |
128 | | Default: | pngquant_speed 0; |
129 | | Context: | http, server, location |
130 |
131 |
132 | Speed is from 1 (highest quality) to 10 (fastest). Speed 0 selects library-specific default (recommended).
133 |
134 | ---
135 |
136 |
137 | | Syntax: | pngquant_store string; |
138 | | Default: | none |
139 | | Context: | http, server, location |
140 |
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 | | Syntax: | pngquant_temp_path path [level1] [level2] [level3]; |
170 | | Default: | pngquant_temp_path /tmp 1 2; |
171 | | Context: | http |
172 |
173 |
174 | Sets temporary area where files are stored before they are moved to ``pngquant_store`` area.
175 |
176 | ---
177 |
178 |
179 | | Syntax: | pngquant_store_access users:permissions ...; |
180 | | Default: | pngquant_store_access user:rw; |
181 | | Context: | http, server, location |
182 |
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 |
--------------------------------------------------------------------------------