How to write a 16 bit PNG_COLOR_TYPE_GRAY with libpng?

2.4k views Asked by At

I wrote the following piece of code to generate a black and white PNG image where each pixel has a value of 16 bits (0 = black, 0xFFFF = white). It is a simple 640x480 test image where all the lines are the same and each line on the left has the maximum black that fades to the white going to the right. Since every line is 640 pixels wide I would have expected to see an almost totally black image that reaches a maximum of 640/65535 of white on the right. Instead I get an image that reaches pure white 2 times in correspondence with the values ​​0x00ff and 0x01ff. This shows that the most significant byte of each pixel is not used by libpng. Can someone tell me where I'm wrong? Thanks to everyone anyway. Bye

System and Compilation details:

System MacBook Pro (Retina, 15 ", end 2013) with OS X 10.11.6

PNG > gcc --version

Configured with: --prefix = / Applications / Xcode.app / Contents / Developer / usr --with-gxx-include-dir = / Applications / Xcode.app / Contents / Developer / Platforms / MacOSX.platform / Developer / SDKs / MacOSX10.12.sdk / usr / include / c ++ / 4.2.1 Apple LLVM version 8.0.0 (clang-800.0.42.1) Target: x86_64-apple-darwin15.6.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

PNG> gcc -I /opt/X11/include/ -L /opt/X11/lib/ -l png -l z writeTest16bit.c

The generated image (file.png) is: enter image description here

/* This is the test code upper described */
#include <png.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define ERROR   -1

#define GOTO_ERROR {line = __LINE__; goto error;}

#define width     640
#define height    460
#define bit_depth 16
unsigned short int image[height][width];

void setBitMapImageGray (void);


int main (int argc, char **argv)
{ 
  char        *pngFileName = "file.png";
  FILE        *pngFile     = NULL;
  png_structp  pngStruct   = NULL;
  png_infop    pngInfo     = NULL;
  int          line        = __LINE__;
  int          i;
  setBitMapImageGray ();

  if (NULL == (pngFile   = fopen (pngFileName, "wb")))                                            GOTO_ERROR;
  if (NULL == (pngStruct = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)))    GOTO_ERROR;
  if (NULL == (pngInfo   = png_create_info_struct (pngStruct)))                                   GOTO_ERROR; 

  // setting long jump: posponed

  png_init_io(pngStruct, pngFile);

  png_set_IHDR (pngStruct, pngInfo, width, height, bit_depth,
                PNG_COLOR_TYPE_GRAY,          PNG_INTERLACE_NONE,  
                PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

  png_write_info (pngStruct, pngInfo);

  for (i=0; i<height; i++)
    png_write_row (pngStruct, (png_const_bytep)&image[i][0]);
  png_write_end (pngStruct, NULL);

  png_destroy_write_struct (&pngStruct, (png_infopp)NULL);
  fclose (pngFile);

  return 0;

error:
  printf ("Error in line %d\n", line);
  if (pngStruct)  png_destroy_write_struct (&pngStruct, (png_infopp)NULL);
  if (pngFile)    fclose (pngFile);
  return (ERROR);
}

void setBitMapImageGray (void)
{
  int x,y;
  unsigned short int const black=0, step=0x10000/width;


  for (y=0;   y<height; y++) 
    for (x=0; x<width;  x++) 
//    image[y][x] = 0xFF00;
      image[y][x] = x;
}
1

There are 1 answers

1
Mark Setchell On BEST ANSWER

You are nearly right - it is not ignoring the high byte but you have an endian-ness issue.

Your code works fine if you add png_set_swap() after png_write_info() like this:

...
png_set_IHDR (pngStruct, pngInfo, width, height, bit_depth,
            PNG_COLOR_TYPE_GRAY,          PNG_INTERLACE_NONE,
            PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(pngStruct, pngInfo);

png_set_swap(pngStruct);    // <--- THIS LINE ADDED

...

enter image description here


Keywords: PNG, libpng, 16-bit, 16 bit, greyscale, endian, endianness, byte order, little endian, big-endian, write, image, image processing, C.