/* Copyright 1984-2001 The MathWorks, Inc. */ /* * IPNG.c * * This module implements the png('info',...) syntax of png.mex. * * Syntax: * info = png('info',filename) * * info is a structure. The fields of info are listed in the variable * fieldnames in the function InfoPNG() below. * */ #include #include "png.h" #include "pngmex.h" #include "mex.h" static char rcsid[] = "$Revision: 1.1.6.1 $"; #define NUM_INPUTS (2) #define FILENAME_IN (prhs[1]) #define INFO_OUT (plhs[0]) #define ERRCHECK(fcn) {err_flag = fcn; if (err_flag != SUCCESS) goto CLEANUP;} #define SCALAR_DOUBLE_ARRAY(value) (mxCreateDoubleScalar(value)) /* * The following function is copied from png_convert_to_rfc1123() in * pngwrite.c in libpng. We're using our own renamed copy because libpng's * version has a bug, in that day "31" is printed as day "0". * -sle, April 1998 */ /* Convert the supplied time into an RFC 1123 string suitable for use in * a "Creation Time" or other text-based time string. */ png_charp mw_png_convert_to_rfc1123(png_structp png_ptr, png_timep ptime) { static PNG_CONST char short_months[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; if (png_ptr->time_buffer == NULL) { png_ptr->time_buffer = (png_charp)png_malloc(png_ptr, (png_uint_32)(29* sizeof(char))); } #ifdef USE_FAR_KEYWORD { char near_time_buf[29]; sprintf(near_time_buf, "%d %s %d %02d:%02d:%02d +0000", (ptime->day - 1) % 31 + 1, short_months[(ptime->month - 1) %12], ptime->year, ptime->hour % 24, ptime->minute % 60, ptime->second % 61); png_memcpy(png_ptr->time_buffer, near_time_buf, 29*sizeof(char)); } #else sprintf(png_ptr->time_buffer, "%d %s %d %02d:%02d:%02d +0000", (ptime->day - 1) % 31 + 1, short_months[(ptime->month - 1) %12], ptime->year, ptime->hour % 24, ptime->minute % 60, ptime->second % 61); #endif return ((png_charp)png_ptr->time_buffer); } static void InfoErrorHandler(png_structp png_ptr, png_const_charp message) { MexInfoType *mexInfo = (MexInfoType *) (png_ptr->error_ptr); png_destroy_read_struct(&(mexInfo->png_ptr), &(mexInfo->info_ptr), NULL); DestroyMexInfo(mexInfo); mexErrMsgTxt(message); } void InfoPNG(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { png_uint_32 width; png_uint_32 height; int bit_depth; int color_type; int interlace_type; ErrorFlag err_flag = SUCCESS; MexInfoType *mexInfo; png_bytep row; int num_passes; int pass; png_uint_32 y; mxArray *tmpArray; double gamma; int renderingIntent; png_color_8p sig_bit; double *pr; png_colorp palette; int num_palette = 0; png_uint_16p hist; double white_x, white_y; double red_x, red_y; double green_x, green_y; double blue_x, blue_y; png_textp text_ptr; int *otherTextIndices = NULL; int numOtherTextChunks = 0; int k; int num_trans; png_bytep trans; png_color_16p trans_values; png_timep mod_time; png_charp time_buffer; png_color_16p background; png_uint_32 offset_x; png_uint_32 offset_y; int offset_unit; png_uint_32 res_x; png_uint_32 res_y; int res_unit; int num_text; const char *fieldnames[] = { "Filename", "FileModDate", "FileSize", "Format", "FormatVersion", "Width", "Height", "BitDepth", "ColorType", "FormatSignature", "Colormap", "Histogram", "InterlaceType", "Transparency", "SimpleTransparencyData", "BackgroundColor", "RenderingIntent", "Chromaticities", "Gamma", "XResolution", "YResolution", "ResolutionUnit", "XOffset", "YOffset", "OffsetUnit", "SignificantBits", "ImageModTime", "Title", "Author", "Description", "Copyright", "CreationTime", "Software", "Disclaimer", "Warning", "Source", "Comment", "OtherText" }; int numFields = sizeof(fieldnames) / sizeof(*fieldnames); double sig[] = {137.0, 80.0, 78.0, 71.0, 13.0, 10.0, 26.0, 10.0}; InitErrorMessages(); mexInfo = InitMexInfo(); if (nrhs < NUM_INPUTS) { mexErrMsgTxt("Too few inputs."); } else if (nrhs > NUM_INPUTS) { mexErrMsgTxt("Too many inputs."); } ERRCHECK(GetFilename(FILENAME_IN,&(mexInfo->filename))); mexInfo->fp = fopen(mexInfo->filename, "rb"); if (mexInfo->fp == NULL) { err_flag = FILE_OPEN; goto CLEANUP; } mexInfo->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, mexInfo, InfoErrorHandler, WarningHandler); if (mexInfo->png_ptr == NULL) { err_flag = PNG_LIB_INIT; goto CLEANUP; } mexInfo->info_ptr = png_create_info_struct(mexInfo->png_ptr); if (mexInfo->info_ptr == NULL) { err_flag = PNG_LIB_INIT; goto CLEANUP; } png_init_io(mexInfo->png_ptr, mexInfo->fp); png_read_info(mexInfo->png_ptr, mexInfo->info_ptr); png_get_IHDR(mexInfo->png_ptr, mexInfo->info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); row = mxCalloc(png_get_rowbytes(mexInfo->png_ptr, mexInfo->info_ptr), sizeof(*row)); num_passes = png_set_interlace_handling(mexInfo->png_ptr); for (pass = 0; pass < num_passes; pass++) { for (y = 0; y < height; y++) { png_read_rows(mexInfo->png_ptr, &row, NULL, 1); } } png_read_end(mexInfo->png_ptr, mexInfo->info_ptr); INFO_OUT = mxCreateStructMatrix(1, 1, numFields, fieldnames); mxSetField(INFO_OUT, 0, "Filename", mxCreateString(mexInfo->filename)); mxSetField(INFO_OUT, 0, "Format", mxCreateString("png")); mxSetField(INFO_OUT, 0, "Width", SCALAR_DOUBLE_ARRAY(width)); mxSetField(INFO_OUT, 0, "Height", SCALAR_DOUBLE_ARRAY(height)); if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA)) { mxSetField(INFO_OUT, 0, "BitDepth", SCALAR_DOUBLE_ARRAY(bit_depth * 3)); } else { mxSetField(INFO_OUT, 0, "BitDepth", SCALAR_DOUBLE_ARRAY(bit_depth)); } if ((color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) { mxSetField(INFO_OUT, 0, "ColorType", mxCreateString("grayscale")); } else if (color_type == PNG_COLOR_TYPE_PALETTE) { mxSetField(INFO_OUT, 0, "ColorType", mxCreateString("indexed")); } else if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA)) { mxSetField(INFO_OUT, 0, "ColorType", mxCreateString("truecolor")); } else { mxSetField(INFO_OUT, 0, "ColorType", mxCreateString("unknown")); } tmpArray = mxCreateDoubleMatrix(1, 8, mxREAL); pr = mxGetPr(tmpArray); for (k = 0; k < 8; k++) { pr[k] = sig[k]; } mxSetField(INFO_OUT, 0, "FormatSignature", tmpArray); if (interlace_type == PNG_INTERLACE_NONE) { mxSetField(INFO_OUT, 0, "InterlaceType", mxCreateString("none")); } else if (interlace_type == PNG_INTERLACE_ADAM7) { mxSetField(INFO_OUT, 0, "InterlaceType", mxCreateString("adam7")); } else { mxSetField(INFO_OUT, 0, "InterlaceType", mxCreateString("unknown")); } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_gAMA)) { png_get_gAMA(mexInfo->png_ptr, mexInfo->info_ptr, &gamma); mxSetField(INFO_OUT, 0, "Gamma", SCALAR_DOUBLE_ARRAY(gamma)); } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_sRGB)) { png_get_sRGB(mexInfo->png_ptr, mexInfo->info_ptr, &renderingIntent); switch (renderingIntent) { case PNG_sRGB_INTENT_SATURATION: mxSetField(INFO_OUT, 0, "RenderingIntent", mxCreateString("saturation")); break; case PNG_sRGB_INTENT_PERCEPTUAL: mxSetField(INFO_OUT, 0, "RenderingIntent", mxCreateString("perceptual")); break; case PNG_sRGB_INTENT_ABSOLUTE: mxSetField(INFO_OUT, 0, "RenderingIntent", mxCreateString("absolute")); break; case PNG_sRGB_INTENT_RELATIVE: mxSetField(INFO_OUT, 0, "RenderingIntent", mxCreateString("relative")); break; default: mxSetField(INFO_OUT, 0, "RenderingIntent", mxCreateString("unknown")); } } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_sBIT)) { png_get_sBIT(mexInfo->png_ptr, mexInfo->info_ptr, &sig_bit); switch (color_type) { case PNG_COLOR_TYPE_GRAY: mxSetField(INFO_OUT, 0, "SignificantBits", SCALAR_DOUBLE_ARRAY(sig_bit->gray)); break; case PNG_COLOR_TYPE_RGB: tmpArray = mxCreateDoubleMatrix(1, 3, mxREAL); pr = mxGetPr(tmpArray); pr[0] = (double) sig_bit->red; pr[1] = (double) sig_bit->green; pr[2] = (double) sig_bit->blue; mxSetField(INFO_OUT, 0, "SignificantBits", tmpArray); break; case PNG_COLOR_TYPE_PALETTE: tmpArray = mxCreateDoubleMatrix(1, 3, mxREAL); pr = mxGetPr(tmpArray); pr[0] = (double) sig_bit->red; pr[1] = (double) sig_bit->green; pr[2] = (double) sig_bit->blue; mxSetField(INFO_OUT, 0, "SignificantBits", tmpArray); break; case PNG_COLOR_TYPE_GRAY_ALPHA: tmpArray = mxCreateDoubleMatrix(1, 2, mxREAL); pr = mxGetPr(tmpArray); pr[0] = (double) sig_bit->gray; pr[1] = (double) sig_bit->alpha; mxSetField(INFO_OUT, 0, "SignificantBits", tmpArray); break; case PNG_COLOR_TYPE_RGB_ALPHA: tmpArray = mxCreateDoubleMatrix(1, 4, mxREAL); pr = mxGetPr(tmpArray); pr[0] = (double) sig_bit->red; pr[1] = (double) sig_bit->green; pr[2] = (double) sig_bit->blue; pr[3] = (double) sig_bit->alpha; mxSetField(INFO_OUT, 0, "SignificantBits", tmpArray); break; } } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_PLTE)) { png_get_PLTE(mexInfo->png_ptr, mexInfo->info_ptr, &palette, &num_palette); tmpArray = mxCreateDoubleMatrix(num_palette, 3, mxREAL); pr = mxGetPr(tmpArray); for (k = 0; k < num_palette; k++) { pr[k] = (double) palette[k].red / 255.0; pr[k+num_palette] = (double) palette[k].green / 255.0; pr[k+2*num_palette] = (double) palette[k].blue / 255.0; } mxSetField(INFO_OUT, 0, "Colormap", tmpArray); } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_hIST)) { png_get_hIST(mexInfo->png_ptr, mexInfo->info_ptr, &hist); tmpArray = mxCreateDoubleMatrix(num_palette, 1, mxREAL); pr = mxGetPr(tmpArray); for (k = 0; k < num_palette; k++) { pr[k] = (double) hist[k]; } mxSetField(INFO_OUT, 0, "Histogram", tmpArray); } if ((color_type == PNG_COLOR_TYPE_GRAY_ALPHA) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA)) { mxSetField(INFO_OUT, 0, "Transparency", mxCreateString("alpha")); } else if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_tRNS)) { mxSetField(INFO_OUT, 0, "Transparency", mxCreateString("simple")); png_get_tRNS(mexInfo->png_ptr, mexInfo->info_ptr, &trans, &num_trans, &trans_values); if (color_type == PNG_COLOR_TYPE_PALETTE) { tmpArray = mxCreateDoubleMatrix(num_trans, 1, mxREAL); pr = mxGetPr(tmpArray); for (k = 0; k < num_trans; k++) { pr[k] = (double) trans[k] / 255.0; } } else if (color_type == PNG_COLOR_TYPE_GRAY) { tmpArray = mxCreateDoubleScalar( (double) trans_values->gray / MAX_PIXEL_VALUE(bit_depth)); } else { tmpArray = mxCreateDoubleMatrix(1, 3, mxREAL); pr = mxGetPr(tmpArray); pr[0] = (double) trans_values->red / MAX_PIXEL_VALUE(bit_depth); pr[1] = (double) trans_values->green / MAX_PIXEL_VALUE(bit_depth); pr[2] = (double) trans_values->blue / MAX_PIXEL_VALUE(bit_depth); } mxSetField(INFO_OUT, 0, "SimpleTransparencyData", tmpArray); } else { mxSetField(INFO_OUT, 0, "Transparency", mxCreateString("none")); } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_tIME)) { png_get_tIME(mexInfo->png_ptr, mexInfo->info_ptr, &mod_time); time_buffer = mw_png_convert_to_rfc1123(mexInfo->png_ptr, mod_time); mxSetField(INFO_OUT, 0, "ImageModTime", mxCreateString(time_buffer)); /* * png_destroy_read_struct() doesn't free time_buffer, although * png_destroy_write_struct() does. Weird. We have to free * it ourselves here to avoid leaks later. */ png_free(mexInfo->png_ptr, time_buffer); mexInfo->png_ptr->time_buffer = NULL; } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_cHRM)) { png_get_cHRM(mexInfo->png_ptr, mexInfo->info_ptr, &white_x, &white_y, &red_x, &red_y, &green_x, &green_y, &blue_x, &blue_y); tmpArray = mxCreateDoubleMatrix(1, 8, mxREAL); pr = mxGetPr(tmpArray); pr[0] = white_x; pr[1] = white_y; pr[2] = red_x; pr[3] = red_y; pr[4] = green_x; pr[5] = green_y; pr[6] = blue_x; pr[7] = blue_y; mxSetField(INFO_OUT, 0, "Chromaticities", tmpArray); } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_bKGD)) { png_get_bKGD(mexInfo->png_ptr, mexInfo->info_ptr, &background); if (color_type == PNG_COLOR_TYPE_PALETTE) { /* * Pass 1-based index back to MATLAB */ mxSetField(INFO_OUT, 0, "BackgroundColor", SCALAR_DOUBLE_ARRAY(background->index + 1)); } else if ((color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) { mxSetField(INFO_OUT, 0, "BackgroundColor", SCALAR_DOUBLE_ARRAY((double) background->gray / MAX_PIXEL_VALUE(bit_depth))); } else { tmpArray = mxCreateDoubleMatrix(1, 3, mxREAL); pr = mxGetPr(tmpArray); pr[0] = (double) background->red / MAX_PIXEL_VALUE(bit_depth); pr[1] = (double) background->green / MAX_PIXEL_VALUE(bit_depth); pr[2] = (double) background->blue / MAX_PIXEL_VALUE(bit_depth); mxSetField(INFO_OUT, 0, "BackgroundColor", tmpArray); } } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_oFFs)) { png_get_oFFs(mexInfo->png_ptr, mexInfo->info_ptr, &offset_x, &offset_y, &offset_unit); mxSetField(INFO_OUT, 0, "XOffset", SCALAR_DOUBLE_ARRAY(offset_x)); mxSetField(INFO_OUT, 0, "YOffset", SCALAR_DOUBLE_ARRAY(offset_y)); if (offset_unit == PNG_OFFSET_PIXEL) { mxSetField(INFO_OUT, 0, "OffsetUnit", mxCreateString("pixel")); } else if (offset_unit == PNG_OFFSET_MICROMETER) { mxSetField(INFO_OUT, 0, "OffsetUnit",mxCreateString("micrometer")); } else { mxSetField(INFO_OUT, 0, "OffsetUnit", mxCreateString("unknown")); } } if (png_get_valid(mexInfo->png_ptr, mexInfo->info_ptr, PNG_INFO_pHYs)) { png_get_pHYs(mexInfo->png_ptr, mexInfo->info_ptr, &res_x, &res_y, &res_unit); mxSetField(INFO_OUT, 0, "XResolution", SCALAR_DOUBLE_ARRAY(res_x)); mxSetField(INFO_OUT, 0, "YResolution", SCALAR_DOUBLE_ARRAY(res_y)); if (res_unit == PNG_RESOLUTION_METER) { mxSetField(INFO_OUT, 0, "ResolutionUnit", mxCreateString("meter")); } else { mxSetField(INFO_OUT, 0, "ResolutionUnit", mxCreateString("unknown")); } } /* * Getting num_text as a return argument in addition to an output argument * looks like it is necessary because of a bug in libpng. -sle */ num_text = png_get_text(mexInfo->png_ptr, mexInfo->info_ptr, &text_ptr, &num_text); if (num_text > 0) { /* * Create a vector in which to store the indices of text chunks that * do not belong to the standard set, e.g., Author, Creator, etc. */ otherTextIndices = (int *) mxCalloc(num_text, sizeof(*otherTextIndices)); } for (k = 0; k < num_text; k++) { if (strcmp(text_ptr[k].key, "Author") == 0) { mxSetField(INFO_OUT, 0, "Author", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Title") == 0) { mxSetField(INFO_OUT, 0, "Title", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Description") == 0) { mxSetField(INFO_OUT, 0, "Description", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Copyright") == 0) { mxSetField(INFO_OUT, 0, "Copyright", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Creation Time") == 0) { mxSetField(INFO_OUT, 0, "CreationTime", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Software") == 0) { mxSetField(INFO_OUT, 0, "Software", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Disclaimer") == 0) { mxSetField(INFO_OUT, 0, "Disclaimer", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Warning") == 0) { mxSetField(INFO_OUT, 0, "Warning", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Source") == 0) { mxSetField(INFO_OUT, 0, "Source", mxCreateString(text_ptr[k].text)); } else if (strcmp(text_ptr[k].key, "Comment") == 0) { mxSetField(INFO_OUT, 0, "Comment", mxCreateString(text_ptr[k].text)); } else { otherTextIndices[numOtherTextChunks++] = k; } } if (numOtherTextChunks > 0) { tmpArray = mxCreateCellMatrix(numOtherTextChunks, 2); for (k = 0; k < numOtherTextChunks; k++) { mxSetCell(tmpArray, k, mxCreateString(text_ptr[otherTextIndices[k]].key)); mxSetCell(tmpArray, k + numOtherTextChunks, mxCreateString(text_ptr[otherTextIndices[k]].text)); } mxSetField(INFO_OUT, 0, "OtherText", tmpArray); } CLEANUP: png_destroy_read_struct(&(mexInfo->png_ptr), &(mexInfo->info_ptr), NULL); DestroyMexInfo(mexInfo); if (otherTextIndices != NULL) { mxFree((void *) otherTextIndices); } if (err_flag != SUCCESS) { pngErrorReturn(err_flag); } }