/* This file is part of dvi2bitmap; see README for copyrights and licence */ #include #include // debug code writes to cerr #include #include #if HAVE_CSTD_INCLUDE #include #include // for floor() #else #include #include // 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(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_fn), static_cast(&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="<(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(palettes_[bpp_][i].red) << '\t' << static_cast(palettes_[bpp_][i].green) << '\t' << static_cast(palettes_[bpp_][i].blue) << '\t' << static_cast(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<= 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("Software"); text_fields[nfields].text = const_cast(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("Comment"); t1.assign ("Converted from DVI file "); t1 += *inputfilename; text_fields[nfields].text = const_cast(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("Comment"); t2.assign ("See "); t2 += *furtherinfo; text_fields[nfields].text = const_cast(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(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<bit_depth)-1; /* cout << "invert_greyscale: " << " bitdepth=" << static_cast(row_info->bit_depth) << " => maxval=" << maxval << " channels=" << static_cast(row_info->channels) << " color_type=" << static_cast(row_info->color_type) << " width=" << row_info->width << " rowbytes=" << row_info->rowbytes << "\n"; */ assert (row_info->width >= 0); for (unsigned int n=0; nwidth; n++, dp++) *dp = maxval-*dp; } #endif