1 #include "mupdf/fitz.h"
2 
3 #include "z-imp.h"
4 
5 #include <string.h>
6 
big32(unsigned char * buf,unsigned int v)7 static inline void big32(unsigned char *buf, unsigned int v)
8 {
9 	buf[0] = (v >> 24) & 0xff;
10 	buf[1] = (v >> 16) & 0xff;
11 	buf[2] = (v >> 8) & 0xff;
12 	buf[3] = (v) & 0xff;
13 }
14 
putchunk(fz_context * ctx,fz_output * out,char * tag,unsigned char * data,size_t size)15 static void putchunk(fz_context *ctx, fz_output *out, char *tag, unsigned char *data, size_t size)
16 {
17 	unsigned int sum;
18 
19 	if ((uint32_t)size != size)
20 		fz_throw(ctx, FZ_ERROR_GENERIC, "PNG chunk too large");
21 
22 	fz_write_int32_be(ctx, out, (int)size);
23 	fz_write_data(ctx, out, tag, 4);
24 	fz_write_data(ctx, out, data, size);
25 	sum = crc32(0, NULL, 0);
26 	sum = crc32(sum, (unsigned char*)tag, 4);
27 	sum = crc32(sum, data, (unsigned int)size);
28 	fz_write_int32_be(ctx, out, sum);
29 }
30 
31 void
fz_save_pixmap_as_png(fz_context * ctx,fz_pixmap * pixmap,const char * filename)32 fz_save_pixmap_as_png(fz_context *ctx, fz_pixmap *pixmap, const char *filename)
33 {
34 	fz_output *out = fz_new_output_with_path(ctx, filename, 0);
35 	fz_band_writer *writer = NULL;
36 
37 	fz_var(writer);
38 
39 	fz_try(ctx)
40 	{
41 		writer = fz_new_png_band_writer(ctx, out);
42 		fz_write_header(ctx, writer, pixmap->w, pixmap->h, pixmap->n, pixmap->alpha, pixmap->xres, pixmap->yres, 0, pixmap->colorspace, pixmap->seps);
43 		fz_write_band(ctx, writer, pixmap->stride, pixmap->h, pixmap->samples);
44 		fz_close_output(ctx, out);
45 	}
46 	fz_always(ctx)
47 	{
48 		fz_drop_band_writer(ctx, writer);
49 		fz_drop_output(ctx, out);
50 	}
51 	fz_catch(ctx)
52 	{
53 		fz_rethrow(ctx);
54 	}
55 }
56 
57 void
fz_write_pixmap_as_png(fz_context * ctx,fz_output * out,const fz_pixmap * pixmap)58 fz_write_pixmap_as_png(fz_context *ctx, fz_output *out, const fz_pixmap *pixmap)
59 {
60 	fz_band_writer *writer;
61 
62 	if (!out)
63 		return;
64 
65 	writer = fz_new_png_band_writer(ctx, out);
66 
67 	fz_try(ctx)
68 	{
69 		fz_write_header(ctx, writer, pixmap->w, pixmap->h, pixmap->n, pixmap->alpha, pixmap->xres, pixmap->yres, 0, pixmap->colorspace, pixmap->seps);
70 		fz_write_band(ctx, writer, pixmap->stride, pixmap->h, pixmap->samples);
71 	}
72 	fz_always(ctx)
73 	{
74 		fz_drop_band_writer(ctx, writer);
75 	}
76 	fz_catch(ctx)
77 	{
78 		fz_rethrow(ctx);
79 	}
80 }
81 
82 typedef struct png_band_writer_s
83 {
84 	fz_band_writer super;
85 	unsigned char *udata;
86 	unsigned char *cdata;
87 	uLong usize, csize;
88 	z_stream stream;
89 	int stream_ended;
90 } png_band_writer;
91 
92 static void
png_write_icc(fz_context * ctx,png_band_writer * writer,fz_colorspace * cs)93 png_write_icc(fz_context *ctx, png_band_writer *writer, fz_colorspace *cs)
94 {
95 #if FZ_ENABLE_ICC
96 	if (cs && !(cs->flags & FZ_COLORSPACE_IS_DEVICE) && (cs->flags & FZ_COLORSPACE_IS_ICC) && cs->u.icc.buffer)
97 	{
98 		fz_output *out = writer->super.out;
99 		size_t size, csize;
100 		fz_buffer *buffer = cs->u.icc.buffer;
101 		unsigned char *pos, *cdata, *chunk = NULL;
102 		const char *name;
103 
104 		/* Deflate the profile */
105 		cdata = fz_new_deflated_data_from_buffer(ctx, &csize, buffer, FZ_DEFLATE_DEFAULT);
106 
107 		if (!cdata)
108 			return;
109 
110 		name = cs->name;
111 		size = csize + strlen(name) + 2;
112 
113 		fz_try(ctx)
114 		{
115 			chunk = fz_calloc(ctx, size, 1);
116 			pos = chunk;
117 			memcpy(chunk, name, strlen(name));
118 			pos += strlen(name) + 2;
119 			memcpy(pos, cdata, csize);
120 			putchunk(ctx, out, "iCCP", chunk, size);
121 		}
122 		fz_always(ctx)
123 		{
124 			fz_free(ctx, cdata);
125 			fz_free(ctx, chunk);
126 		}
127 		fz_catch(ctx)
128 		{
129 			fz_rethrow(ctx);
130 		}
131 	}
132 #endif
133 }
134 
135 static void
png_write_header(fz_context * ctx,fz_band_writer * writer_,fz_colorspace * cs)136 png_write_header(fz_context *ctx, fz_band_writer *writer_, fz_colorspace *cs)
137 {
138 	png_band_writer *writer = (png_band_writer *)(void *)writer_;
139 	fz_output *out = writer->super.out;
140 	int w = writer->super.w;
141 	int h = writer->super.h;
142 	int n = writer->super.n;
143 	int alpha = writer->super.alpha;
144 	static const unsigned char pngsig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
145 	unsigned char head[13];
146 	int color;
147 
148 	if (writer->super.s != 0)
149 		fz_throw(ctx, FZ_ERROR_GENERIC, "PNGs cannot contain spot colors");
150 
151 	/* Treat alpha only as greyscale */
152 	if (n == 1 && alpha)
153 		alpha = 0;
154 
155 	switch (n - alpha)
156 	{
157 	case 1: color = (alpha ? 4 : 0); break; /* 0 = Greyscale, 4 = Greyscale + Alpha */
158 	case 3: color = (alpha ? 6 : 2); break; /* 2 = RGB, 6 = RGBA */
159 	default:
160 		fz_throw(ctx, FZ_ERROR_GENERIC, "pixmap must be grayscale or rgb to write as png");
161 	}
162 
163 	big32(head+0, w);
164 	big32(head+4, h);
165 	head[8] = 8; /* depth */
166 	head[9] = color;
167 	head[10] = 0; /* compression */
168 	head[11] = 0; /* filter */
169 	head[12] = 0; /* interlace */
170 
171 	fz_write_data(ctx, out, pngsig, 8);
172 	putchunk(ctx, out, "IHDR", head, 13);
173 
174 	big32(head+0, writer->super.xres * 100/2.54f + 0.5f);
175 	big32(head+4, writer->super.yres * 100/2.54f + 0.5f);
176 	head[8] = 1; /* metre */
177 	putchunk(ctx, out, "pHYs", head, 9);
178 
179 	png_write_icc(ctx, writer, cs);
180 }
181 
182 static void
png_write_band(fz_context * ctx,fz_band_writer * writer_,int stride,int band_start,int band_height,const unsigned char * sp)183 png_write_band(fz_context *ctx, fz_band_writer *writer_, int stride, int band_start, int band_height, const unsigned char *sp)
184 {
185 	png_band_writer *writer = (png_band_writer *)(void *)writer_;
186 	fz_output *out = writer->super.out;
187 	unsigned char *dp;
188 	int y, x, k, err, finalband;
189 	int w, h, n;
190 
191 	if (!out)
192 		return;
193 
194 	w = writer->super.w;
195 	h = writer->super.h;
196 	n = writer->super.n;
197 
198 	finalband = (band_start+band_height >= h);
199 	if (finalband)
200 		band_height = h - band_start;
201 
202 	if (writer->udata == NULL)
203 	{
204 		writer->usize = ((uLong)w * n + 1) * band_height;
205 		/* Sadly the bound returned by compressBound is just for a
206 		 * single usize chunk; if you compress a sequence of them
207 		 * the buffering can result in you suddenly getting a block
208 		 * larger than compressBound outputted in one go, even if you
209 		 * take all the data out each time. */
210 		writer->csize = compressBound(writer->usize);
211 		writer->udata = Memento_label(fz_malloc(ctx, writer->usize), "png_write_udata");
212 		writer->cdata = Memento_label(fz_malloc(ctx, writer->csize), "png_write_cdata");
213 		writer->stream.opaque = ctx;
214 		writer->stream.zalloc = fz_zlib_alloc;
215 		writer->stream.zfree = fz_zlib_free;
216 		err = deflateInit(&writer->stream, Z_DEFAULT_COMPRESSION);
217 		if (err != Z_OK)
218 			fz_throw(ctx, FZ_ERROR_GENERIC, "compression error %d", err);
219 	}
220 
221 	dp = writer->udata;
222 	stride -= w*n;
223 	if (writer->super.alpha)
224 	{
225 		/* Unpremultiply data */
226 		for (y = 0; y < band_height; y++)
227 		{
228 			int prev[FZ_MAX_COLORS];
229 			*dp++ = 1; /* sub prediction filter */
230 			for (x = 0; x < w; x++)
231 			{
232 				int a = sp[n-1];
233 				int inva = a ? 256*255/a : 0;
234 				int p;
235 				for (k = 0; k < n-1; k++)
236 				{
237 					int v = (sp[k] * inva + 128)>>8;
238 					p = x ? prev[k] : 0;
239 					prev[k] = v;
240 					v -= p;
241 					dp[k] = v;
242 				}
243 				p = x ? prev[k] : 0;
244 				prev[k] = a;
245 				a -= p;
246 				dp[k] = a;
247 				sp += n;
248 				dp += n;
249 			}
250 			sp += stride;
251 		}
252 	}
253 	else
254 	{
255 		for (y = 0; y < band_height; y++)
256 		{
257 			*dp++ = 1; /* sub prediction filter */
258 			for (x = 0; x < w; x++)
259 			{
260 				for (k = 0; k < n; k++)
261 				{
262 					if (x == 0)
263 						dp[k] = sp[k];
264 					else
265 						dp[k] = sp[k] - sp[k-n];
266 				}
267 				sp += n;
268 				dp += n;
269 			}
270 			sp += stride;
271 		}
272 	}
273 
274 	writer->stream.next_in = (Bytef*)writer->udata;
275 	writer->stream.avail_in = (uInt)(dp - writer->udata);
276 	do
277 	{
278 		writer->stream.next_out = writer->cdata;
279 		writer->stream.avail_out = (uInt)writer->csize;
280 
281 		if (!finalband)
282 		{
283 			err = deflate(&writer->stream, Z_NO_FLUSH);
284 			if (err != Z_OK)
285 				fz_throw(ctx, FZ_ERROR_GENERIC, "compression error %d", err);
286 		}
287 		else
288 		{
289 			err = deflate(&writer->stream, Z_FINISH);
290 			if (err == Z_OK)
291 			{
292 				/* more output space needed, try again */
293 				writer->cdata = Memento_label(fz_realloc(ctx, writer->cdata, writer->csize << 2), "realloc png_write_cdata");
294 				writer->csize <<= 2;
295 				continue;
296 			}
297 
298 			if (err != Z_STREAM_END)
299 				fz_throw(ctx, FZ_ERROR_GENERIC, "compression error %d", err);
300 		}
301 
302 		if (writer->stream.next_out != writer->cdata)
303 			putchunk(ctx, out, "IDAT", writer->cdata, writer->stream.next_out - writer->cdata);
304 	}
305 	while (writer->stream.avail_out == 0);
306 }
307 
308 static void
png_write_trailer(fz_context * ctx,fz_band_writer * writer_)309 png_write_trailer(fz_context *ctx, fz_band_writer *writer_)
310 {
311 	png_band_writer *writer = (png_band_writer *)(void *)writer_;
312 	fz_output *out = writer->super.out;
313 	unsigned char block[1];
314 	int err;
315 
316 	writer->stream_ended = 1;
317 	err = deflateEnd(&writer->stream);
318 	if (err != Z_OK)
319 		fz_throw(ctx, FZ_ERROR_GENERIC, "compression error %d", err);
320 
321 	putchunk(ctx, out, "IEND", block, 0);
322 }
323 
324 static void
png_drop_band_writer(fz_context * ctx,fz_band_writer * writer_)325 png_drop_band_writer(fz_context *ctx, fz_band_writer *writer_)
326 {
327 	png_band_writer *writer = (png_band_writer *)(void *)writer_;
328 
329 	if (!writer->stream_ended)
330 	{
331 		int err = deflateEnd(&writer->stream);
332 		if (err != Z_OK)
333 			fz_warn(ctx, "ignoring compression error %d", err);
334 	}
335 
336 	fz_free(ctx, writer->cdata);
337 	fz_free(ctx, writer->udata);
338 }
339 
fz_new_png_band_writer(fz_context * ctx,fz_output * out)340 fz_band_writer *fz_new_png_band_writer(fz_context *ctx, fz_output *out)
341 {
342 	png_band_writer *writer = fz_new_band_writer(ctx, png_band_writer, out);
343 
344 	writer->super.header = png_write_header;
345 	writer->super.band = png_write_band;
346 	writer->super.trailer = png_write_trailer;
347 	writer->super.drop = png_drop_band_writer;
348 
349 	return &writer->super;
350 }
351 
352 /* We use an auxiliary function to do pixmap_as_png, as it can enable us to
353  * drop pix early in the case where we have to convert, potentially saving
354  * us having to have 2 copies of the pixmap and a buffer open at once. */
355 static fz_buffer *
png_from_pixmap(fz_context * ctx,fz_pixmap * pix,fz_color_params color_params,int drop)356 png_from_pixmap(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params, int drop)
357 {
358 	fz_buffer *buf = NULL;
359 	fz_output *out = NULL;
360 	fz_pixmap *pix2 = NULL;
361 
362 	fz_var(buf);
363 	fz_var(out);
364 	fz_var(pix2);
365 
366 	if (pix->w == 0 || pix->h == 0)
367 	{
368 		if (drop)
369 			fz_drop_pixmap(ctx, pix);
370 		return NULL;
371 	}
372 
373 	fz_try(ctx)
374 	{
375 		if (pix->colorspace && pix->colorspace != fz_device_gray(ctx) && pix->colorspace != fz_device_rgb(ctx))
376 		{
377 			pix2 = fz_convert_pixmap(ctx, pix, fz_device_rgb(ctx), NULL, NULL, color_params, 1);
378 			if (drop)
379 				fz_drop_pixmap(ctx, pix);
380 			pix = pix2;
381 		}
382 		buf = fz_new_buffer(ctx, 1024);
383 		out = fz_new_output_with_buffer(ctx, buf);
384 		fz_write_pixmap_as_png(ctx, out, pix);
385 		fz_close_output(ctx, out);
386 	}
387 	fz_always(ctx)
388 	{
389 		fz_drop_pixmap(ctx, drop ? pix : pix2);
390 		fz_drop_output(ctx, out);
391 	}
392 	fz_catch(ctx)
393 	{
394 		fz_drop_buffer(ctx, buf);
395 		fz_rethrow(ctx);
396 	}
397 	return buf;
398 }
399 
400 fz_buffer *
fz_new_buffer_from_image_as_png(fz_context * ctx,fz_image * image,fz_color_params color_params)401 fz_new_buffer_from_image_as_png(fz_context *ctx, fz_image *image, fz_color_params color_params)
402 {
403 	fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, NULL, NULL);
404 	return png_from_pixmap(ctx, pix, color_params, 1);
405 }
406 
407 fz_buffer *
fz_new_buffer_from_pixmap_as_png(fz_context * ctx,fz_pixmap * pix,fz_color_params color_params)408 fz_new_buffer_from_pixmap_as_png(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params)
409 {
410 	return png_from_pixmap(ctx, pix, color_params, 0);
411 }
412