summaryrefslogtreecommitdiff
path: root/dviware/dvi2bitmap/PNGBitmap.cc
blob: 6fd98c0868514875737ded849fdefd8108cdc31d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/* This file is part of dvi2bitmap; see README for copyrights and licence */

#include <config.h>

#include <iostream>             // debug code writes to cerr

#include <time.h>
#include <assert.h>

#if HAVE_CSTD_INCLUDE
#include <cstdio>
#include <cmath>		// for floor()
#else
#include <stdio.h>
#include <math.h>		// for floor()
#endif

#include "Bitmap.h"		// for BitmapError exception class
#include "PNGBitmap.h"

using STD::fopen;
using STD::fclose;
using STD::cerr;

// The PNG calls below have been written to be general, rather than
// depending on a particular colour model.  That is, it should be
// possible to define colour_type in write() to be PNG_COLOR_TYPE_GRAY
// instead of the current PNG_COLOR_TYPE_PALETTE, and for the rest of
// the routine to simply work.  This requires the function
// png_set_write_user_transform_fn to be in libpng, which is true only
// of versions of the library after 0.96.  Having said that, it hasn't
// been tested recently.  The following define is really intended only to
// document the points which would need attention if the colour model
// were to be generalised in future: if you were to define it to be 1
// the write() routine would probably work, but I'm not guaranteeing anything.
#define GREYSCALE_BITMAP 0

png_structp PNGBitmap::png_ptr_ = 0;
png_infop PNGBitmap::info_ptr_ = 0;
// Following two must have indexes 1..8 (for up to 8 bpp bit depth),
// be initialised to zero, and have the same length.
// Static, so not deleted in destructor.
png_color *PNGBitmap::palettes_[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
png_byte *PNGBitmap::trans_[]     = { 0, 0, 0, 0, 0, 0, 0, 0, 0  };

#if GREYSCALE_BITMAP
static void png_invert_greyscale (png_structp png_ptr,
				  png_row_infop row_info,
				  png_bytep data);
#endif

PNGBitmap::PNGBitmap (const int w, const int h, const int bpp)
    : BitmapImage (w, h, bpp)
{
}

PNGBitmap::~PNGBitmap()
{
    if (png_ptr_ != 0)
	png_destroy_write_struct (&png_ptr_, (!info_ptr_ ? 0 : &info_ptr_));
    /*
    int npalettes = sizeof(palettes_)/sizeof(palettes_[0]);
    for (int i=0; i<npalettes; i++)
    {
	if (palettes_[i] != 0)
	    delete[] palettes_[i];
	if (trans_[i] != 0)
	    // Won't necessarily be non-zero when palettes_[i] is, if
	    // isTransparent_ is true.
	    delete[] trans_[i];
    }
    */
}

inline int iround (float f)
{
    return static_cast<int>(f>0 ? floor(f+0.5) : ceil(f-0.5));
}

void PNGBitmap::write (const string filename)
{
    // Write out bitmaps using a palette.  A greyscale is more
    // obvious, since the smoothed bit values are essentially
    // monochrome, but using a palette (a) makes it very easy to use
    // colours different from black in the future, and (b) allows us
    // to use a tRNS chunk to include transparency information,
    // without the overhead of a full alpha channel.  Palettes are
    // limited to 8 bits of colour table.

    // This routine can cope with only these two types
#if GREYSCALE_BITMAP
    int colour_type = PNG_COLOR_TYPE_GRAY;
#else
    int colour_type = PNG_COLOR_TYPE_PALETTE;
#endif

    if (bitmapRows_ != h_)
	throw BitmapError ("attempt to PNGBitmap::write with incomplete bitmap");

    // Can't cope with bit depths greater than 8.  The library can
    // cope with depths of 16, too, but then I'd probably have to
    // rewrite png_invert_greyscale()
    if (bpp_ > 8)
	throw BitmapError ("can't presently cope with bit depths greater than 8");

    FILE *pngfile = fopen (filename.c_str(), "wb");
    if (!pngfile)
	throw BitmapError ("Can't open file "+filename+" to write");

    if (png_ptr_ == 0)		// allocate and initialise
    {
	// Use default error handlers for now
	png_ptr_ = png_create_write_struct
	    (PNG_LIBPNG_VER_STRING,
	     0,
	     static_cast<png_error_ptr>(&png_error_fn),
	     static_cast<png_error_ptr>(&png_warning_fn));

	if (! png_ptr_)
	{
	    fclose (pngfile);
	    throw BitmapError ("Can't create png_ptr");
	}

	info_ptr_ = png_create_info_struct (png_ptr_);
	if (! info_ptr_)
	{
	    png_destroy_write_struct (&png_ptr_, 0);
	    fclose (pngfile);
	    throw BitmapError ("Can't create PNG info_ptr");
	}

#if GREYSCALE_BITMAP
	// Call png_set_write_user_transform_fn to install a function
	// to invert the greyscale on output.  This function is not
	// present in libpng 0.96 -- should I work around this or just
	// require newer versions?
	if (colour_type == PNG_COLOR_TYPE_GRAY)
	    png_set_write_user_transform_fn (png_ptr_, &png_invert_greyscale);
#endif

	/* not needed since I use custom error functions above
	if (setjmp (png_ptr_->jmpbuf))
	{
	    png_destroy_write_struct (&png_ptr_, &info_ptr_);
	    fclose (pngfile);
	    throw BitmapError ("libpng internal error");
	}
	*/
    }

    // Set up output code
    png_init_io (png_ptr_, pngfile);

    // find the first power-of-two bitdepth not less than bpp_
    assert (bpp_ <= 16);	// following loop will terminate
    int png_bitdepth;
    for (png_bitdepth = 1; png_bitdepth < bpp_; png_bitdepth *= 2)
	;

    if (verbosity_ > normal)
	cerr << "Initialising png_set_IHDR: w="<<w_
	     << " h=" << h_
	     << " bpp=" << bpp_
	     << " png_bitdepth=" << png_bitdepth
	     << "\n";

    png_set_IHDR (png_ptr_, info_ptr_, w_, h_, png_bitdepth,
		  colour_type,
		  PNG_INTERLACE_ADAM7,
		  PNG_COMPRESSION_TYPE_BASE,
		  PNG_FILTER_TYPE_BASE);

    if (colour_type & PNG_COLOR_MASK_PALETTE)
    {
	if (palettes_[bpp_] == 0
	    || (isTransparent_ && trans_[bpp_] == 0))
	{
	    // Create and set palette, making a smooth palette between
	    // 0..(2^bpp_-1).  Note that the input bitmap is coded in
	    // terms of high=black, so invert this in this palette.
	    int maxcolour = (1<<bpp_)-1;
	    int diffred   = fg_.red   - bg_.red;
	    int diffgreen = fg_.green - bg_.green;
	    int diffblue  = fg_.blue  - bg_.blue;

	    palettes_[bpp_] = new png_color[maxcolour+1];

	    if (isTransparent_)
		trans_[bpp_] = new png_byte[maxcolour+1];

	    for (int i=0; i<=maxcolour; i++)
	    {
		float rat = static_cast<float>(i)/maxcolour;
		if (isTransparent_)
		{
		    // A linear alpha function (iround(rat*255)) is
		    // the obvious thing here, but when an image is
		    // displayed against a white background, this
		    // effectively makes the palette quadratic, and so
		    // makes the image look very jaggy.
		    //
		    // Given foreground colour f, background colour b,
		    // and rat in [0,1], we want the resulting
		    // intensity to be I=p_0=b+rat(f-b), as in the
		    // non-alpha case.  With an alpha function a, and
		    // palette p_a, displayed against a background of
		    // colour b, I suppose we'll have I=(1-a)b+a p_a
		    // (yes?), which equals p_0 if p_a=b+rat/a(f-b).
		    // So choosing an alpha function a=sqrt(rat) makes
		    // p_a=b+sqrt(rat)(f-b).
		    //
		    // Note that this shows that transparency
		    // information is optimal for only one background
		    // colour.
		    //trans_[bpp_][i] = iround (rat*255);
		    float sqrat = sqrt(rat);
		    trans_[bpp_][i] = iround (sqrat*255);
		    palettes_[bpp_][i].red
			= iround (bg_.red   + sqrat*diffred);
		    palettes_[bpp_][i].green
			= iround (bg_.green + sqrat*diffgreen);
		    palettes_[bpp_][i].blue
			= iround (bg_.blue  + sqrat*diffblue);
		}
		else 
		{
		    palettes_[bpp_][i].red
			= bg_.red   + iround(rat*diffred);
		    palettes_[bpp_][i].green
			= bg_.green + iround(rat*diffgreen);
		    palettes_[bpp_][i].blue
			= bg_.blue  + iround(rat*diffblue);
		}
	    }
	    if (verbosity_ > debug)
	    {
		cerr << "Allocated palette for bitdepth " << bpp_
		     << "...\n";
		for (int i=0; i<=maxcolour; i++)
		    cerr << '\t' << i
			 << '\t'
			 << static_cast<int>(palettes_[bpp_][i].red)
			 << '\t'
			 << static_cast<int>(palettes_[bpp_][i].green)
			 << '\t'
			 << static_cast<int>(palettes_[bpp_][i].blue)
			 << '\t'
			 << static_cast<int>(isTransparent_
					     ? trans_[bpp_][i]
					     : -1)
			 << '\n';
	    }
	}
	assert (palettes_[bpp_] != 0);
	assert (!isTransparent_ || (trans_[bpp_] != 0));

	png_set_PLTE (png_ptr_, info_ptr_,
		      palettes_[bpp_], (1<<bpp_));

	if (isTransparent_)
	    png_set_tRNS (png_ptr_, info_ptr_,
			  trans_[bpp_], (1<<bpp_),
			  0);

	// Fill in the bKGD chunk, just for completeness.
	// This definition of the background colour is suitable for
	// different colour models: colour index 0 is the background
	// colour by construction, above.
	png_color_16 bKGD_colour;
	bKGD_colour.index = 0;
	bKGD_colour.red   = bg_.red;
	bKGD_colour.green = bg_.green;
	bKGD_colour.blue  = bg_.blue;
	png_set_bKGD (png_ptr_, info_ptr_, &bKGD_colour);
    }

    time_t sectime = time(0);
    if (sectime >= 0)
    {
	png_time pngtime;
	png_convert_from_time_t (&pngtime, sectime);
	png_set_tIME (png_ptr_, info_ptr_, &pngtime);
    }

    png_text text_fields[3];
    unsigned int nfields = 0;
    if (softwareversion != 0)
    {
	text_fields[nfields].key = const_cast<char *>("Software");
	text_fields[nfields].text
	    = const_cast<char*>(softwareversion->c_str());
	text_fields[nfields].text_length = strlen (text_fields[nfields].text);
	text_fields[nfields].compression = PNG_TEXT_COMPRESSION_NONE;
	nfields++;
    }

    string t1, t2;
    if (inputfilename != 0)
    {
	text_fields[nfields].key = const_cast<char *>("Comment");
	t1.assign ("Converted from DVI file ");
	t1 += *inputfilename;
	text_fields[nfields].text = const_cast<char*>(t1.c_str());
	text_fields[nfields].text_length = t1.length();
	text_fields[nfields].compression = PNG_TEXT_COMPRESSION_NONE;
	nfields++;
    }

    if (furtherinfo != 0)
    {
	text_fields[nfields].key = const_cast<char *>("Comment");
	t2.assign ("See ");
	t2 += *furtherinfo;
	text_fields[nfields].text = const_cast<char*>(t2.c_str());
	text_fields[nfields].text_length = t2.length();
	text_fields[nfields].compression = PNG_TEXT_COMPRESSION_NONE;
	nfields++;
    }

    assert (nfields <= sizeof(text_fields)/sizeof(text_fields[0]));

    if (nfields > 0)
	png_set_text (png_ptr_, info_ptr_, text_fields, nfields);

    png_write_info (png_ptr_, info_ptr_);

    // data is supplied one pixel per byte: tell libpng this
    png_set_packing (png_ptr_);

    // PNG can handle bit depths of 1, 2, 4, 8, and 16, but this
    // routine can be given bit depths other than these.  If this is
    // the case (so that png_bitdepth will have been set different
    // from bpp_ above), then log this
    // by writing an sBIT chunk, and (importantly) also call
    // png_set_shift to tell libpng to scale the bits.
    if (png_bitdepth != bpp_)
    {
	png_color_8 sigbit;
	if (colour_type & PNG_COLOR_MASK_COLOR)
	    sigbit.red
		= sigbit.green
		= sigbit. blue
		= bpp_;
	else
	    sigbit.gray = bpp_;
	if (colour_type & PNG_COLOR_MASK_ALPHA)
	    sigbit.alpha = bpp_;
	png_set_sBIT (png_ptr_, info_ptr_, &sigbit);
	png_set_shift (png_ptr_, &sigbit);
    }

    // Now write the image.  png_write_image requires an array of
    // pointers to rows in the bitmap.  Create this here, possibly
    // after (re)allocating the array.  The RowPointer typedef is here
    // partly to document the intention, but also because some
    // compilers (legitimately?) object to `new (const Byte*)[rows_alloc]'.
    typedef const Byte* RowPointer;
    static RowPointer *rows;
    static int rows_alloc = -1;	// rely on this being initialised
				// negative, and specifically less
				// than any h_
    if (rows_alloc < h_)
    {
	if (rows_alloc >= 0)		// previously allocated
	    delete[] rows;
	// find the first power-of-two not less than h_ (there's no
	// real reason why it has to be a power of two, but this means
	// that the allocated space grows gracefully).
	for (rows_alloc = 1; rows_alloc < h_; rows_alloc *= 2)
	    ;
	rows = new RowPointer[rows_alloc];
	if (verbosity_ > normal)
	    cerr << "PNGBitmap: allocated " << rows_alloc
		 << "(" << h_ << ") row elements\n";
    }
    for (int r=0; r<h_; r++)
	rows[r] = &bitmap_[r*w_];

    png_write_image (png_ptr_, const_cast<Byte**>(rows));

    png_write_end (png_ptr_,info_ptr_);

    // Don't delete the write and info structs, since they're
    // statically allocated, and usable by later (but not
    // simultaneous) instances of this class.

    fclose (pngfile);
}
					    
void PNGBitmap::png_error_fn (png_structp png_ptr,
                              png_const_charp error_msg)
{
    string err = "PNG error ";
    err.append(error_msg);
    throw BitmapError (err);
}

void PNGBitmap::png_warning_fn (png_structp png_ptr,
				png_const_charp warning_msg)
{
    if (verbosity_ > quiet)
	cerr << "PNG warning: " << warning_msg;
}

#if GREYSCALE_BITMAP
// A row-transform function which inverts the greyscale.  Bitmaps are
// sent to this class with zero being white and all-bits-on being
// black, which is opposite to what PNG wants.  png_set_invert_mono
// doesn't do this, since it works for single-bit monochrome only, and
// not greyscale.  This code patterned after the example in pngtest.c
static void png_invert_greyscale (png_structp png_ptr,
				  png_row_infop row_info,
				  png_bytep dp)
{
    if (png_ptr == 0)
	return;

    if (row_info == 0 || dp == 0)
	throw BitmapError ("Call of png_invert_greyscale with null pointers");

    // sanity-check
    if (row_info->color_type != 0 || row_info->channels != 1)
	throw BitmapError ("Call of png_invert_greyscale with unknown colour model");
	

    // contents of row_info:
    //   png_uint_32 width      width of row
    //   png_uint_32 rowbytes   number of bytes in row
    //   png_byte color_type    color type of pixels
    //   png_byte bit_depth     bit depth of samples
    //   png_byte channels      number of channels (1-4)
    //   png_byte pixel_depth   bits per pixel (depth*channels)

    assert (row_info->bit_depth <= 8); // *dp is a byte
    unsigned int maxval = (1<<row_info->bit_depth)-1;

    /*
    cout << "invert_greyscale: "
	 << " bitdepth=" << static_cast<int>(row_info->bit_depth)
	 << " => maxval=" << maxval
	 << " channels=" << static_cast<int>(row_info->channels)
	 << " color_type=" << static_cast<int>(row_info->color_type)
	 << " width=" << row_info->width
	 << " rowbytes=" << row_info->rowbytes
	 << "\n";
    */

    assert (row_info->width >= 0);
    for (unsigned int n=0; n<row_info->width; n++, dp++)
	*dp = maxval-*dp;
}
#endif