/* A* ------------------------------------------------------------------- B* This file contains source code for the PyMOL computer program C* Copyright (c) Schrodinger, LLC. D* ------------------------------------------------------------------- E* It is unlawful to modify or remove this copyright notice. F* ------------------------------------------------------------------- G* Please see the accompanying LICENSE file for further information. H* ------------------------------------------------------------------- I* Additional authors of this source file include: -* TJO: TJ O'Donnell, under contract for Schrodinger, LLC -* -* Z* ------------------------------------------------------------------- */ #include"os_predef.h" #include"MemoryDebug.h" #include"pymol/memory.h" /* backwards compatibility */ #ifdef _HAVE_LIBPNG #ifndef _PYMOL_LIBPNG #define _PYMOL_LIBPNG #endif #endif /* do we have libpng? */ #ifdef _PYMOL_LIBPNG #include /* The png_jmpbuf() macro, used in error handling, became available in * libpng version 1.0.6. If you want to be able to run your code with older * versions of libpng, you must define the macro yourself (but only if it * is not already defined by libpng!). */ #ifndef png_jmpbuf # define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) #endif #endif #include"os_std.h" #include"Base.h" #include "MyPNG.h" #include"MemoryDebug.h" #include "Setting.h" #include "Err.h" #include "File.h" /* * base64 decoding * http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c */ static unsigned char base64_decoding_table[] = { 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 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}; static unsigned char *base64_decode(const char *data, size_t input_length=0) { unsigned char *decoded_data = nullptr; unsigned int triple; unsigned int i = 0, j = 0, k; char c; if (input_length < 1) input_length = strlen(data); ok_assert(1, decoded_data = pymol::malloc(input_length / 4 * 3)); while (i < input_length) { triple = 0; for (k = 4; k && i < input_length;) { c = data[i++]; if (c < 43 || c > 122) continue; triple += base64_decoding_table[c - 43] << (--k) * 6; } ok_assert(1, k == 0); for (k = 3; k;) decoded_data[j++] = (triple >> (--k) * 8) & 0xFF; } return decoded_data; ok_except1: mfree(decoded_data); return nullptr; } typedef struct { unsigned char *c; // moving pointer unsigned char *h; // for freeing } uchar2p; #ifdef _PYMOL_LIBPNG /** * Use with png_set_read_fn, for reading a PNG file from memory. * This simply copies from the memory pointer to outBytes while * incrementing the memory pointer. */ void read_data_from_buffer(png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) { uchar2p *io_ptr = (uchar2p*) png_get_io_ptr(png_ptr); if(!io_ptr) return; while(byteCountToRead--) { *(outBytes++) = *(io_ptr->c++); } } /** * Use with png_set_write_fn, for writing a PNG file to memory. */ static void write_data_to_buffer(png_structp png_ptr, png_bytep data, png_size_t length) { auto io_ptr = reinterpret_cast(png_get_io_ptr(png_ptr)); io_ptr->insert(io_ptr->end(), data, data + length); } /** * Use with png_set_read_fn instead of png_init_io, allows mixing of * Visual Studio versions */ static void read_data_from_file( png_structp png_ptr, png_bytep buffer, png_size_t count) { auto fp = static_cast(png_get_io_ptr(png_ptr)); fread(buffer, 1, count, fp); } /** * Use with png_set_write_fn instead of png_init_io, allows mixing of * Visual Studio versions */ static void write_data_to_file( png_structp png_ptr, png_bytep buffer, png_size_t count) { auto fp = static_cast(png_get_io_ptr(png_ptr)); fwrite(buffer, 1, count, fp); } #endif int MyPNGWrite(pymol::zstring_view file_name_view, const pymol::Image& img, const float dpi, const int format, const int quiet, const float screen_gamma, const float file_gamma, png_outbuf_t* io_ptr) { const char* file_name = file_name_view.c_str(); const unsigned char* data_ptr = img.bits(); int width = img.getWidth(); int height = img.getHeight(); switch (format) { case cMyPNG_FormatPNG: { #ifdef _PYMOL_LIBPNG int ok = true; FILE *fp = nullptr; png_structp png_ptr; png_infop info_ptr; int bit_depth = 8; int bytes_per_pixel = 4; png_uint_32 k; png_byte *image = (png_byte *) data_ptr; png_bytep *row_pointers; int fd = 0; row_pointers = pymol::malloc(height); /* open the file, allowing use of an encoded file descriptor, with approach adapted from TJO: chr(1) followed by ascii-format integer */ if (!io_ptr) { if(file_name[0] == 1) { if(sscanf(file_name + 1, "%d", &fd) == 1) { fp = fdopen(fd, "wb"); } } else { fp = pymol_fopen(file_name, "wb"); } if(fp == nullptr) { ok = false; goto cleanup; } else if(feof(fp)) { ok = false; goto cleanup; } } /* Create and initialize the png_struct with the desired error handler * functions. If you want to use the default stderr and longjump method, * you can supply nullptr for the last three parameters. We also check that * the library version is compatible with the one used at compile time, * in case we are using dynamically linked libraries. REQUIRED. */ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if(png_ptr == nullptr) { ok = false; goto cleanup; } /* Allocate/initialize the image information data. REQUIRED */ info_ptr = png_create_info_struct(png_ptr); if(info_ptr == nullptr) { png_destroy_write_struct(&png_ptr, (png_infopp) nullptr); ok = false; goto cleanup; } /* Set error handling. REQUIRED if you aren't supplying your own * error handling functions in the png_create_write_struct() call. */ if(setjmp(png_jmpbuf(png_ptr))) { /* If we get here, we had a problem reading the file */ png_destroy_write_struct(&png_ptr, (png_infopp) nullptr); ok = false; goto cleanup; } if (io_ptr) { png_set_write_fn(png_ptr, (void*) io_ptr, write_data_to_buffer, nullptr); } else { /* set up the output control if you are using standard C streams */ png_set_write_fn(png_ptr, (void*) fp, write_data_to_file, nullptr); } /* Set the image information here. Width and height are up to 2^31, * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED */ png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if(dpi > 0.0F) { /* only set resolution if dpi is positive */ int dots_per_meter = (int) (dpi * 39.3700787); png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER); } png_set_gamma(png_ptr, screen_gamma, file_gamma); /* stamp the image as being created by PyMOL we could consider * supporting optional annotations as well: PDB codes, canonical * smiles, INCHIs, and other common identifiers */ { png_text text; text.compression = PNG_TEXT_COMPRESSION_NONE; text.key = (png_charp) "Software"; text.text = (png_charp) "PyMOL"; text.text_length = 5; png_set_text(png_ptr, info_ptr, &text, 1); } { png_text text; text.compression = PNG_TEXT_COMPRESSION_NONE; text.key = (png_charp) "URL"; text.text = (png_charp) "http://www.pymol.org"; text.text_length = 5; png_set_text(png_ptr, info_ptr, &text, 1); } /* Write the file header information. REQUIRED */ png_write_info(png_ptr, info_ptr); /* The easiest way to write the image (you may have a different memory * layout, however, so choose what fits your needs best). You need to * use the first method if you aren't handling interlacing yourself. */ for(k = 0; k < height; k++) row_pointers[(height - k) - 1] = image + k * width * bytes_per_pixel; png_write_image(png_ptr, row_pointers); /* It is REQUIRED to call this to finish writing the rest of the file */ png_write_end(png_ptr, info_ptr); /* clean up after the write, and free any memory allocated */ png_destroy_write_struct(&png_ptr, &info_ptr); cleanup: if(fp) { /* close the file */ fclose(fp); } mfree(row_pointers); /* that's it */ return ok; #else return 0; #endif } break; case cMyPNG_FormatPPM: { FILE *fil = pymol_fopen(file_name, "wb"); unsigned char *buffer = pymol::malloc(3 * width * height); if(fil && buffer) { fprintf(fil, "P6\n"); fprintf(fil, "%d %d\n", width, height); fprintf(fil, "255\n"); { unsigned int a, b; unsigned char *q = buffer; const unsigned char *p; p = data_ptr + width * 4 * (height - 1); for(b = 0; b < height; b++) { for(a = 0; a < width; a++) { *(q++) = *(p++); /* platform-specific ordering? */ *(q++) = *(p++); *(q++) = *(p++); p++; } p -= width * 8; } fwrite(buffer, width, height * 3, fil); } } if(fil) { fclose(fil); } FreeP(buffer); } return 1; break; } return 0; } std::unique_ptr MyPNGRead(const char *file_name) { std::unique_ptr img; #ifdef _PYMOL_LIBPNG FILE *png_file = nullptr; png_struct *png_ptr = nullptr; png_info *info_ptr = nullptr; png_byte buf[8]; png_byte *png_pixels = nullptr; png_byte **row_pointers = nullptr; png_byte *pix_ptr = nullptr; png_uint_32 row_bytes = 0; png_uint_32 width; png_uint_32 height; int bit_depth; int color_type; int row, col; int ret; int i; int ok = true; double file_gamma; uchar2p data = {NULL, nullptr}; if(!file_name) return nullptr; if(!strncmp(file_name, "data:image/png;base64,", 22)) { const char *base64str = file_name + 22; data.h = data.c = base64_decode(base64str); memcpy(buf, data.c, 8); data.c += 8; } else { png_file = pymol_fopen(file_name, "rb"); if(png_file == nullptr) return nullptr; /* read and check signature in PNG file */ ret = fread(buf, 1, 8, png_file); if(ret != 8) ok = false; } if(ok) { ret = !png_sig_cmp(buf, 0, 8); if(!ret) ok = false; } /* create png and info structures */ if(ok) { png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if(!png_ptr) ok = false; } if(ok) { info_ptr = png_create_info_struct(png_ptr); if(!info_ptr) ok = false; } if(ok && setjmp(png_jmpbuf(png_ptr))) ok = false; if(ok) { /* set up the input control for C streams */ if(data.h) { png_set_read_fn(png_ptr, (void*) &data, read_data_from_buffer); } else { png_set_read_fn(png_ptr, (void*) png_file, read_data_from_file); } png_set_sig_bytes(png_ptr, 8); /* we already read the 8 signature bytes */ /* read the file information */ png_read_info(png_ptr, info_ptr); /* get size and bit-depth of the PNG-image */ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr); /* set-up the transformations */ if(color_type != PNG_COLOR_TYPE_RGB_ALPHA) { png_set_expand(png_ptr); png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); } if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); /* only if file has a file gamma, we do a correction */ if(png_get_gAMA(png_ptr, info_ptr, &file_gamma)) png_set_gamma(png_ptr, (double) 2.2, file_gamma); /* all transformations have been registered; now update info_ptr data, * get rowbytes and channels, and allocate image memory */ png_read_update_info(png_ptr, info_ptr); /* get the new color-type and bit-depth (after expansion/stripping) */ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr); /* row_bytes is the width x number of channels x (bit-depth / 8) */ row_bytes = png_get_rowbytes(png_ptr, info_ptr); if((png_pixels = (png_byte *) malloc(row_bytes * height * sizeof(png_byte))) == nullptr) { ok = false; } } if(ok) { if((row_pointers = (png_byte **) malloc(height * sizeof(png_bytep))) == nullptr) { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); free(png_pixels); png_pixels = nullptr; ok = false; } } if(ok) { /* set the individual row_pointers to point at the correct offsets */ for(i = 0; i < ((signed) height); i++) row_pointers[i] = png_pixels + i * row_bytes; /* now we can go ahead and just read the whole image */ png_read_image(png_ptr, row_pointers); /* read rest of file, and get additional chunks in info_ptr - REQUIRED */ png_read_end(png_ptr, info_ptr); } img = std::make_unique(width, height); if(ok) { auto p = img->bits(); for(row = 0; row < (signed) height; row++) { pix_ptr = row_pointers[(height - 1) - row]; for(col = 0; col < (signed) width; col++) { *p++ = *pix_ptr++; *p++ = *pix_ptr++; *p++ = *pix_ptr++; *p++ = *pix_ptr++; } } } if(row_pointers != (unsigned char **) nullptr) free(row_pointers); if(png_pixels != (unsigned char *) nullptr) free(png_pixels); if(png_ptr) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); } if(png_file) fclose(png_file); if(data.h) mfree(data.h); return img; #else return nullptr; #endif } /* end of source */