Canola  0.8.D001
lib/image/format/png.cc
Go to the documentation of this file.
00001 //
00002 // canola - canon canola 1614p emulator
00003 // Copyright (C) 2011 Peter Miller
00004 //
00005 // This program is free software; you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License, version 3, as
00007 // published by the Free Software Foundation.
00008 //
00009 // This program is distributed in the hope that it will be useful,
00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012 // General Public License for more details.
00013 //
00014 // You should have received a copy of the GNU General Public License along
00015 // with this program. If not, see <http://www.gnu.org/licenses/>.
00016 //
00017 
00018 #include <lib/config.h>
00019 #include <libexplain/fclose.h>
00020 #include <libexplain/fopen.h>
00021 #include <libexplain/fread.h>
00022 #include <libexplain/fwrite.h>
00023 #include <libexplain/output.h>
00024 #include <png.h>
00025 
00026 #include <lib/image/format/png.h>
00027 
00028 
00029 image_format_png::~image_format_png()
00030 {
00031 }
00032 
00033 
00034 image_format_png::image_format_png(int a_width, int a_height) :
00035     image_format(a_width, a_height)
00036 {
00037 }
00038 
00039 
00040 image_format_png::image_format_png(int a_width, int a_height, data_t *a_data) :
00041     image_format(a_width, a_height, a_data)
00042 {
00043 }
00044 
00045 
00046 const char *
00047 image_format_png::type_name(void)
00048     const
00049 {
00050     return "PNG";
00051 }
00052 
00053 
00054 static void
00055 input_read_glue(png_structp png_ptr, png_bytep data, png_size_t size)
00056 {
00057   FILE *fp = (FILE *)png_get_io_ptr(png_ptr);
00058     size_t check = explain_fread_or_die(data, 1, size, fp);
00059     if (check != size)
00060         png_error(png_ptr, "Read Error");
00061 }
00062 
00063 
00064 image::pointer
00065 image_format_png::create(const std::string &filename)
00066 {
00067     //
00068     // open file and test for it being a png
00069     //
00070     FILE *fp = explain_fopen_or_die(filename.c_str(), "rb");
00071     png_byte header[4];
00072     explain_fread_or_die(header, sizeof(header), 1, fp);
00073     if (png_sig_cmp(header, 0, sizeof(header)))
00074         explain_output_error_and_die("%s: not a PNG file", filename.c_str());
00075 
00076     //
00077     // read the image from the file
00078     //
00079     png_structp png_ptr =
00080         png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
00081     if (!png_ptr)
00082     {
00083         oops:
00084         explain_output_error_and_die("read %s: oops", filename.c_str());
00085     }
00086 
00087     // If you want to understand what is happening here, read the PNG
00088     // book "PNG The Definitive Guide" by Greg Roelofs.
00089     //
00090     // The relevant chapter is available online as
00091     // http://www.libpng.org/pub/png/book/chapter13.html
00092     //
00093     png_info *info_ptr = png_create_info_struct(png_ptr);
00094     if (!info_ptr)
00095         goto oops;
00096 
00097     if (setjmp(png_jmpbuf(png_ptr)))
00098     {
00099         explain_output_error_and_die("%s: error during init_io",
00100             filename.c_str());
00101     }
00102 
00103     png_set_read_fn(png_ptr, fp, input_read_glue);
00104     png_set_sig_bytes(png_ptr, sizeof(header));
00105     png_read_info(png_ptr, info_ptr);
00106 
00107     size_t width = png_get_image_width(png_ptr, info_ptr);
00108     assert(width >= 1);
00109     size_t height = png_get_image_height(png_ptr, info_ptr);
00110     assert(height >= 1);
00111     int color_type = png_get_color_type(png_ptr, info_ptr);
00112     int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
00113     assert(bit_depth >= 1);
00114 
00115     // force palette images to be expanded to 24-bit RGB.
00116     if (color_type == PNG_COLOR_TYPE_PALETTE)
00117     {
00118         png_set_palette_to_rgb(png_ptr);
00119     }
00120 
00121     // low-bit-depth grayscale images are to be expanded to 8 bits
00122     if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
00123     {
00124         png_set_expand_gray_1_2_4_to_8(png_ptr);
00125     }
00126 
00127     // expand any tRNS chunk data into a full alpha channel
00128     if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
00129     {
00130         png_set_tRNS_to_alpha(png_ptr);
00131     }
00132 
00133     // reduce images with 16-bit samples (e.g., 48-bit RGB) to 8 bits
00134     // per sample
00135     if (bit_depth == 16)
00136         png_set_strip_16(png_ptr);
00137 
00138     // expand grayscale images to RGB
00139     if
00140     (
00141         color_type == PNG_COLOR_TYPE_GRAY
00142     ||
00143         color_type == PNG_COLOR_TYPE_GRAY_ALPHA
00144     )
00145         png_set_gray_to_rgb(png_ptr);
00146 
00147     // Once we've registered all of our desired transformations, we
00148     // request that libpng update the information struct appropriately
00149     png_read_update_info(png_ptr, info_ptr);
00150 
00151     color_type = png_get_color_type(png_ptr, info_ptr);
00152     assert(color_type == PNG_COLOR_TYPE_RGB ||
00153         color_type == PNG_COLOR_TYPE_RGB_ALPHA);
00154     bit_depth = png_get_bit_depth(png_ptr, info_ptr);
00155     assert(bit_depth == 8);
00156 
00157     // read file
00158     if (setjmp(png_jmpbuf(png_ptr)))
00159     {
00160         explain_output_error_and_die("%s: error during read_image",
00161             filename.c_str());
00162     }
00163 
00164     size_t rgba_rowspan = width * 4;
00165     size_t png_rowbytes = png_get_rowbytes(png_ptr, info_ptr);
00166     size_t png_data_size = height * png_rowbytes;
00167     png_byte *png_data = new png_byte [png_data_size];
00168     png_byte **row_pointers = new png_byte * [height];
00169     for (size_t y = 0; y < height; ++y)
00170         row_pointers[y] = png_data + y * png_rowbytes;
00171     png_read_image(png_ptr, row_pointers);
00172     explain_fclose_or_die(fp);
00173     fp = 0;
00174 
00175     //
00176     // Turn it into continuous RGBA data
00177     //
00178     if (png_rowbytes != rgba_rowspan)
00179     {
00180         assert(color_type == PNG_COLOR_TYPE_RGB);
00181         size_t rgba_size = height * rgba_rowspan;
00182         data_t *rgba_data = new data_t [rgba_size];
00183         for (size_t y = 0; y < height; ++y)
00184         {
00185             data_t *rgba_row = rgba_data + y * rgba_rowspan;
00186             png_byte *png_row = row_pointers[y];
00187             for (size_t x = 0; x < width; ++x)
00188             {
00189                 *rgba_row++ = *png_row++;
00190                 *rgba_row++ = *png_row++;
00191                 *rgba_row++ = *png_row++;
00192                 *rgba_row++ = 255;
00193             }
00194         }
00195         delete [] png_data;
00196         png_data = rgba_data;
00197     }
00198     delete [] row_pointers;
00199 
00200     //
00201     // Finally, we have an image.
00202     //
00203     return pointer(new image_format_png(width, height, png_data));
00204 }
00205 
00206 
00207 static void
00208 output_write_glue(png_structp png_ptr, png_bytep data, png_size_t size)
00209 {
00210   FILE *fp = (FILE *)png_get_io_ptr(png_ptr);
00211     explain_fwrite_or_die(data, 1, size, fp);
00212 }
00213 
00214 
00215 bool
00216 image_format_png::calc_needs_alpha(void)
00217     const
00218 {
00219     for (unsigned y = 0; y < get_height(); ++y)
00220     {
00221         png_byte *p = (png_byte *)get_data(y);
00222         for (unsigned x = 0; x < get_width(); ++x, p += 4)
00223         {
00224             if (p[3] != 255)
00225                 return true;
00226         }
00227     }
00228     return false;
00229 }
00230 
00231 
00232 bool
00233 image_format_png::calc_needs_rgb(void)
00234     const
00235 {
00236     for (unsigned y = 0; y < get_height(); ++y)
00237     {
00238         png_byte *p = (png_byte *)get_data(y);
00239         for (unsigned x = 0; x < get_width(); ++x, p += 4)
00240         {
00241             if (p[3] != 0 && (p[0] != p[1] || p[0] != p[2]))
00242                 return true;
00243         }
00244     }
00245     return false;
00246 }
00247 
00248 
00249 void
00250 image_format_png::save_to_file(const std::string &filename)
00251     const
00252 {
00253     //
00254     // create file
00255     //
00256     FILE *fp = explain_fopen_or_die(filename.c_str(), "wb");
00257 
00258     // initialize stuff
00259     png_structp png_ptr =
00260         png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
00261     if (!png_ptr)
00262     {
00263         oops:
00264         explain_output_error_and_die
00265         (
00266             "%s: oops, write failed",
00267             filename.c_str()
00268         );
00269     }
00270     png_info *info_ptr = png_create_info_struct(png_ptr);
00271     if (!info_ptr)
00272         goto oops;
00273     if (setjmp(png_jmpbuf(png_ptr)))
00274     {
00275         explain_output_error_and_die
00276         (
00277             "%s: errror during init_io",
00278             filename.c_str()
00279         );
00280     }
00281     png_set_write_fn(png_ptr, fp, output_write_glue, 0);
00282 
00283     // write header
00284     if (setjmp(png_jmpbuf(png_ptr)))
00285     {
00286         explain_output_error_and_die
00287         (
00288             "%s: error writing header",
00289             filename.c_str()
00290         );
00291     }
00292 
00293     bool needs_alpha = calc_needs_alpha();
00294     bool needs_rgb = calc_needs_rgb();
00295     int color_type =
00296         (
00297             needs_rgb
00298         ?
00299             (needs_alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB)
00300         :
00301             (needs_alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY)
00302         );
00303     png_set_IHDR(png_ptr, info_ptr, get_width(), get_height(),
00304                  8, color_type, PNG_INTERLACE_NONE,
00305                  PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
00306     png_write_info(png_ptr, info_ptr);
00307 
00308     // write bytes
00309     if (setjmp(png_jmpbuf(png_ptr)))
00310     {
00311         explain_output_error_and_die
00312         (
00313             "%s: error writing bytes",
00314             filename.c_str()
00315         );
00316     }
00317     if (needs_rgb)
00318     {
00319         if (needs_alpha)
00320         {
00321             png_byte **row_pointers = new png_byte * [get_height()];
00322             for (size_t y = 0; y < get_height(); ++y)
00323                 row_pointers[y] = (png_byte *)get_data(y);
00324             png_write_image(png_ptr, row_pointers);
00325             delete [] row_pointers;
00326         }
00327         else
00328         {
00329             png_byte *ob_base = new png_byte [get_width() * 3];
00330             for (unsigned y = 0; y < get_height(); ++y)
00331             {
00332                 png_byte *ob = ob_base;
00333                 png_byte *ib = (png_byte *)get_data(y);
00334                 for (unsigned x = 0; x < get_width(); ++x, ib += 4)
00335                 {
00336                     *ob++ = ib[0];
00337                     *ob++ = ib[1];
00338                     *ob++ = ib[2];
00339                 }
00340                 png_write_row(png_ptr, ob_base);
00341             }
00342             delete ob_base;
00343         }
00344     }
00345     else
00346     {
00347         if (needs_alpha)
00348         {
00349             png_byte *ob_base = new png_byte [get_width() * 2];
00350             for (unsigned y = 0; y < get_height(); ++y)
00351             {
00352                 png_byte *ob = ob_base;
00353                 png_byte *ib = (png_byte *)get_data(y);
00354                 for (unsigned x = 0; x < get_width(); ++x, ib += 4)
00355                 {
00356                     *ob++ = ib[0];
00357                     *ob++ = ib[3];
00358                 }
00359                 png_write_row(png_ptr, ob_base);
00360             }
00361             delete ob_base;
00362         }
00363         else
00364         {
00365             png_byte *ob_base = new png_byte [get_width()];
00366             for (unsigned y = 0; y < get_height(); ++y)
00367             {
00368                 png_byte *ob = ob_base;
00369                 png_byte *ib = (png_byte *)get_data(y);
00370                 for (unsigned x = 0; x < get_width(); ++x, ib += 4)
00371                 {
00372                     *ob++ = ib[0];
00373                 }
00374                 png_write_row(png_ptr, ob_base);
00375             }
00376             delete ob_base;
00377         }
00378     }
00379 
00380     // end write
00381     if (setjmp(png_jmpbuf(png_ptr)))
00382         explain_output_error_and_die("%s: error finishing", filename.c_str());
00383     png_write_end(png_ptr, NULL);
00384 
00385     explain_fclose_or_die(fp);
00386 }
00387 
00388 
00389 image::pointer
00390 image_format_png::create_blank(int width, int height)
00391 {
00392     return pointer(new image_format_png(width, height));
00393 }