$OpenBSD: patch-lavtools_png2yuv_c,v 1.1 2011/07/08 20:36:09 naddy Exp $

Fix build with png-1.5.

--- lavtools/png2yuv.c.orig	Thu Nov  8 10:31:50 2007
+++ lavtools/png2yuv.c	Mon Jul  4 17:49:05 2011
@@ -49,12 +49,9 @@ png2yuv
 
 #include "subsample.h"
 #include "colorspace.h"
-//#include "mplexconsts.hh"
 
 #define DEFAULT_CHROMA_MODE Y4M_CHROMA_420JPEG
 
-#define MAXPIXELS (2800*1152)  /**< Maximum size of final image */
-
 typedef struct _parameters 
 {
   char *pngformatstr;
@@ -70,14 +67,10 @@ typedef struct _parameters 
   int ss_mode; /**< subsampling mode (based on ssm_id from subsample.h) */
 
   int new_width; /// new MPEG2 width, in case the original one is uneven
+  int new_height; /// new MPEG2 width, in case the original one is uneven
 } parameters_t;
 
 
-struct _parameters *sh_param; 
-png_structp png_ptr;
-png_infop info_ptr, end_info;
-uint8_t *raw0, *raw1, *raw2;  /* buffer for RGB first, and then Y/Cb/Cr planes of decoded PNG */
-
 /*
  * The User Interface parts 
  */
@@ -152,8 +145,6 @@ static void parse_commandline(int argc, char ** argv, 
   param->interleave = -1;
   param->verbose = 1;
   param->ss_mode = DEFAULT_CHROMA_MODE;
-  //param->mza_filename = NULL;
-  //param->make_z_alpha = 0;
 
   /* parse options */
   for (;;) {
@@ -240,94 +231,44 @@ static void parse_commandline(int argc, char ** argv, 
     }
 }
 
-void png_separation(png_structp png_ptr, png_row_infop row_info, png_bytep data)
-{
-  int row_nr = png_ptr->row_number; // internal variable ? 
-  int i, width = row_info->width; 
-  int new_width = sh_param->new_width;
 
-  /* 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)
-   */
-
-  //mjpeg_debug("PNG YUV transformation callback; color_type is %d row_number %d\n", 
-  //	 row_info->color_type, row_nr);
-
-  if(row_info->color_type == PNG_COLOR_TYPE_GRAY) // only Z available
-    {
-      //mjpeg_debug("Grayscale to YUV, row %d", row_nr);
-      for (i = 0; i < width; i++)
-	{
-	  raw0[i + row_nr * new_width] = data[i];
-	  raw1[i + row_nr * new_width] = data[i];
-	  raw2[i + row_nr * new_width] = data[i];
-	}
-      return;
-    }
-
-  if(row_info->color_type == PNG_COLOR_TYPE_RGB) // Z and Alpha available
-    {
-      //mjpeg_info("RGB to YUV, row %d", row_nr);
-      for (i = 0; i < width; i++)
-	{
-	  raw0[i + row_nr * new_width] = data[i*3];
-	  raw1[i + row_nr * new_width] = data[i*3 + 1];
-	  raw2[i + row_nr * new_width] = data[i*3 + 2];
-	}
-      return;
-    }
-
-  mjpeg_error_exit1("mpegz: UNKNOWN COLOR FORMAT %d in PNG transformation !\n", row_info->color_type);
-}
-
-
 /*
  * The file handling parts 
  */
 /** 
 Reads one PNG file. 
-@param process Process the image data (0 for initial parameter determination)
+@param process Process the image data (NULL for initial parameter determination)
 @returns -1 on failure, 1 on sucess
+@on success returns RGB data in the second, yuv, parameter
 */
-int decode_png(const char *pngname, int process, parameters_t *param)
+int decode_png(const char *pngname, uint8_t *yuv[], parameters_t *param)
 {
-  int num_pass = 1;
-  int bit_depth, color_type;
+  png_structp png_ptr;
+  png_infop info_ptr;
   FILE *pngfile;
-  //png_byte hdptr[8];
 
-  /* Now open this PNG file, and examine its header to retrieve the 
-     YUV4MPEG info that shall be written */
-  pngfile = fopen(pngname, "rb");
-  if (!pngfile)
-    {
-      perror("PNG file open failed:");
-      return -1;
-    }
+  /* libpng needs two structs - a png_struct and a png_info, there is no
+   * need to make the third, another png_info, because that is only used
+   * to store data (such as textual information) that can come after the
+   * PNG image.  This code only cares about the image.
+   */
+  info_ptr = NULL;
+  pngfile = NULL;
+  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+  if (!png_ptr)
+    mjpeg_error_exit1("%s: Could not allocate PNG read struct !", pngname);
 
-  //fread(hdptr, 1, 8, pngfile);
-
-#if 0 
-  bool is_png = !png_sig_cmp(hdptr, 0, 8);
-  if (!is_png)
+  /* This needs to be done immediately after creation of the png_struct
+   * because storage allocation failures will longjmp back to here:
+   */
+  if (setjmp(png_jmpbuf(png_ptr)))
     {
-      mjpeg_error("%s is _no_ PNG file !\n");
+      png_destroy_read_struct(&png_ptr, &info_ptr, 0);
+      if (pngfile) (void)fclose(pngfile);
+      mjpeg_error("%s: Corrupted PNG file !", pngname);
       return -1;
     }
-#endif
   
-  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-  if (!png_ptr)
-    mjpeg_error_exit1("%s: Could not allocate PNG read struct !", pngname);
-  
-  png_init_io(png_ptr, pngfile);
-  //png_set_sig_bytes(png_ptr, 8);
-  
   info_ptr = png_create_info_struct(png_ptr);
   if (!info_ptr)
     {
@@ -336,79 +277,101 @@ int decode_png(const char *pngname, int process, param
       mjpeg_error_exit1("%s: Could not allocate PNG info struct !", pngname);
     }
   
-  end_info = png_create_info_struct(png_ptr);
-  if (!end_info)
+  /* Now open this PNG file, and examine its header to retrieve the 
+     YUV4MPEG info that shall be written */
+  pngfile = fopen(pngname, "rb");
+  if (!pngfile)
     {
-      png_destroy_read_struct(&png_ptr, &info_ptr,
-			      (png_infopp)NULL);
-      mjpeg_error_exit1("%s: Could not allocate PNG end info struct !", pngname);
+      perror(pngname);
+      png_error(png_ptr, "PNG file open failed");
     }
-  
-  if (setjmp(png_jmpbuf(png_ptr)))
+
+  png_init_io(png_ptr, pngfile);
+
+  if (yuv)
     {
-      png_destroy_read_struct(&png_ptr, &info_ptr,
-			      &end_info);
-      mjpeg_error("%s: Corrupted PNG file !", pngname);
-      return -1;
+      png_uint_32 nr, input_height, input_width, output_height, output_width;
+      uint8_t *r, *g, *b;
+      png_bytepp rows;
+
+      /* The code uses png_read_png to obtain a complete buffered copy of the
+       * PNG file reduced (or expanded) to 8 bit RGB.  This is a little wasteful
+       * in the case of a non-interlaced image - the code could work row by
+       * row without buffering the whole image - but the interlaced case is
+       * almost impossible to handle this way so it is better to be simple and
+       * correct.
+       */
+#     if PNG_LIBPNG_VER >= 10500 && PNG_LIBPNG_VER < 10502
+	  /* There is a bug in 1.5 before 1.5.2 which causes png_read_png to
+	   * whine most terribly on interlaced images, this stops it:
+	   */
+          (void)png_set_interlace_handling(png_ptr);
+#     endif
+      png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 |
+	PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_EXPAND |
+	PNG_TRANSFORM_GRAY_TO_RGB /* requires libpng 1.4 or later */, 0);
+
+      /* And return the separated data to the parameters. */
+      rows = png_get_rows(png_ptr, info_ptr);
+
+      /* Since the PNG files for the frames are separate the actual PNG file 
+       * that was read could be unrelated - a random width and height.  Because
+       * the output may be interleaved the output height may be twice the input
+       * PNG height.  Because the MPEG code requires an even width the output
+       * width may be one more than the original frame width.
+       *
+       * For the interleaving the PNG data is smashed into the lower half of
+       * the yuv rows.  For the other cases the input data is cropped or
+       * top-lefted as appropriate.
+       */
+      output_height = param->new_height;
+
+      input_height = png_get_image_height(png_ptr, info_ptr);
+      if (input_height > output_height)
+          input_height = output_height;
+
+      output_width = param->new_width;
+
+      input_width = png_get_image_width(png_ptr, info_ptr);
+      if (input_width > output_width)
+        input_width = output_width;
+
+      /* Breaking up the RGB data is not hard to do, the separated channels are
+       * simply packed into the three raw yuv arrays with new_width values per
+       * row.
+       */
+      r = yuv[0];
+      g = yuv[1];
+      b = yuv[2];
+      for (nr=0; nr<input_height; ++nr)
+        {
+	  png_uint_32 nc;
+	  png_bytep row = *rows++;
+
+	  for (nc=0; nc<input_width; ++nc)
+	    {
+	    	*r++ = *row++;
+		*g++ = *row++;
+		*b++ = *row++;
+	    }
+	
+	  /* Pad the output: */
+	  for (; nc<output_width; ++nc)
+	    *r++ = *g++ = *b++ = 0;
+	}
     }
-  
-  if (process)
-    png_set_read_user_transform_fn(png_ptr, png_separation);
-  png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_STRIP_ALPHA, NULL);
-  
-  if (png_get_IHDR(png_ptr, info_ptr, &param->width, &param->height, &bit_depth,
-		       //   &color_type, &interlace_type, &compression_type, &filter_type))
-		   &color_type, NULL, NULL, NULL))	
-    num_pass = png_set_interlace_handling(png_ptr);
   else
-    mjpeg_error_exit1("PNG header reading failed !!\n");
-#if 0 
-  mjpeg_info("Reading info struct...\n");
-  png_read_info(png_ptr, info_ptr);
-  mjpeg_info("Done...\n");
-  
-  if (png_get_IHDR(png_ptr, info_ptr, &param->width, &param->height, &bit_depth,
-		       //   &color_type, &interlace_type, &compression_type, &filter_type))
-		   &color_type, NULL, NULL, NULL))	
-    num_pass = png_set_interlace_handling(png_ptr);
-  else
-    mjpeg_error_exit1("PNG header reading failed !!\n");
-  
-  if (process)
     {
-      printf("%d passes needed\n\n", num_pass);
-      
-      if (bit_depth != 8 && bit_depth != 16)
-	{
-	  mjpeg_error_exit1("Invalid bit_depth %d, only 8 and 16 bit allowed !!\n", bit_depth);
-	}
-      
-      png_set_strip_16(png_ptr); // always has to strip the 16bit input, MPEG can't handle it	  
-      png_set_strip_alpha(png_ptr); // Alpha can't be processed until Z/Alpha is integrated
-      
-      printf("\nAllocating row buffer...");
-      png_set_read_user_transform_fn(png_ptr, png_separation);
-      png_bytep row_buf = (png_bytep)png_malloc(png_ptr,
-						png_get_rowbytes(png_ptr, info_ptr));
-      
-      for (int n=0; n < num_pass; n++)
-	for (int y=0; y < sh_param->height; y++)
-	  {
-	    printf("Writing row data for pass %d\n", n);
-	    png_read_rows(png_ptr, (png_bytepp)&row_buf, NULL, 1);
-	  }
-      
-      png_free(png_ptr, row_buf);	      
+      /* Just return the image width and height in *param */
+      png_read_info(png_ptr, info_ptr);
+
+      param->width = png_get_image_width(png_ptr, info_ptr);
+      param->height = png_get_image_height(png_ptr, info_ptr);
     }
-  png_read_end(png_ptr, info_ptr);
-#endif  
-  if (setjmp(png_ptr->jmpbuf)) {
-    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
-    return 2;
-    }
 
+  /* Successful exit: */
+  png_destroy_read_struct(&png_ptr, &info_ptr, 0);
   fclose(pngfile);
-
   return 1;
 }
 
@@ -419,13 +382,17 @@ int decode_png(const char *pngname, int process, param
  */
 static int init_parse_files(parameters_t *param)
 { 
-  char pngname[255];
+  char pngname[PATH_MAX+1]; /* See POSIX 1003.1 section 2.9.5 */
 
   snprintf(pngname, sizeof(pngname), 
 	   param->pngformatstr, param->begin);
   mjpeg_debug("Analyzing %s to get the right pic params", pngname);
   
-  if (decode_png(pngname, 0, param) == -1)
+  /* The first frame (the param->begin frame) determines the height and
+   * width of the output.  Passing NULL instead of yuv (see below) causes
+   * decode_png to fill in param with the image dimensions.
+   */
+  if (decode_png(pngname, NULL, param) == -1)
     mjpeg_error_exit1("Reading of %s failed.\n", pngname);
 
   mjpeg_info("Image dimensions are %ux%u",
@@ -455,6 +422,7 @@ static int init_parse_files(parameters_t *param)
 
   if (!(param->interleave) && (param->interlace != Y4M_ILACE_NONE)) 
     {
+      /* So the height in 'param' might be twice the PNG input height:*/
       param->height *= 2;
       mjpeg_info("Non-interleaved fields (image height doubled)");
     }
@@ -466,33 +434,29 @@ static int init_parse_files(parameters_t *param)
 static int generate_YUV4MPEG(parameters_t *param)
 {
   uint32_t frame;
-  //size_t pngsize;
-  char pngname[FILENAME_MAX];
-  uint8_t *yuv[3];  /* buffer for Y/U/V planes of decoded PNG */
+  uint8_t *yuv[3];  /* Buffers, initially for R,G,B then Y,Cb,Cr */
   y4m_stream_info_t streaminfo;
   y4m_frame_info_t frameinfo;
 
-  if ((param->width % 2) == 0)
-    param->new_width = param->width;
-  else
-    {
-      param->new_width = ((param->width >> 1) + 1) << 1;
-      printf("Setting new, even image width %d", param->new_width);
-    }
+  /* Make the output even, so the output may be one larger than the
+   * original PNG image width.
+   */
+  param->new_width = param->width + (param->width & 1);
+  param->new_height = param->height + (param->height & 1);
 
   mjpeg_info("Now generating YUV4MPEG stream.");
   y4m_init_stream_info(&streaminfo);
   y4m_init_frame_info(&frameinfo);
 
   y4m_si_set_width(&streaminfo, param->new_width);
-  y4m_si_set_height(&streaminfo, param->height);
+  y4m_si_set_height(&streaminfo, param->new_height);
   y4m_si_set_interlace(&streaminfo, param->interlace);
   y4m_si_set_framerate(&streaminfo, param->framerate);
   y4m_si_set_chroma(&streaminfo, param->ss_mode);
 
-  yuv[0] = (uint8_t *)malloc(param->new_width * param->height * sizeof(yuv[0][0]));
-  yuv[1] = (uint8_t *)malloc(param->new_width * param->height * sizeof(yuv[1][0]));
-  yuv[2] = (uint8_t *)malloc(param->new_width * param->height * sizeof(yuv[2][0]));
+  yuv[0] = (uint8_t *)malloc(param->new_width * param->new_height * sizeof(yuv[0][0]));
+  yuv[1] = (uint8_t *)malloc(param->new_width * param->new_height * sizeof(yuv[1][0]));
+  yuv[2] = (uint8_t *)malloc(param->new_width * param->new_height * sizeof(yuv[2][0]));
 
   y4m_write_stream_header(STDOUT_FILENO, &streaminfo);
 
@@ -500,15 +464,13 @@ static int generate_YUV4MPEG(parameters_t *param)
        (frame < param->numframes + param->begin) || (param->numframes == -1);
        frame++) 
     {
-      //      if (frame < 25)
-      //      else      
-      //snprintf(pngname, sizeof(pngname), param->pngformatstr, frame - 25);
+      char pngname[PATH_MAX+1];
       snprintf(pngname, sizeof(pngname), param->pngformatstr, frame);
             
-      raw0 = yuv[0];
-      raw1 = yuv[1];
-      raw2 = yuv[2];
-      if (decode_png(pngname, 1, param) == -1)
+      /* decode_png reads the PNG into the yuv buffers as r,g,b [0..255]
+       * values.
+       */
+      if (decode_png(pngname, yuv, param) == -1)
 	{
 	  mjpeg_info("Read from '%s' failed:  %s", pngname, strerror(errno));
 	  if (param->numframes == -1) 
@@ -523,79 +485,18 @@ static int generate_YUV4MPEG(parameters_t *param)
 	} 
       else 
 	{
-#if 0 
-	  mjpeg_debug("Preparing frame");
-	  
-	  /* Now open this PNG file, and examine its header to retrieve the 
-	     YUV4MPEG info that shall be written */
-
-	  if ((param->interlace == Y4M_ILACE_NONE) || (param->interleave == 1)) 
-	    {
-	      mjpeg_info("Processing non-interlaced/interleaved %s.", 
-			 pngname, pngsize);
-
-	      decode_png(imagedata, 0, 420, yuv[0], yuv[1], yuv[2], 
-			 param->width, param->height, param->new_width);
-	      
-#if 0 
-	      if (param->make_z_alpha)
-		{
-		  mjpeg_info("Writing Z/Alpha data.\n");
-		  za_write(real_z_imagemap, param->width, param->height,z_alpha_fp,frame);
-		}
-#endif
-	    } 
-	  else 
-	    {
-	      mjpeg_error_exit1("Can't handle interlaced PNG information (yet) since there is no standard for it.\n"
-				"Use interleaved mode (-L option) to create interlaced material.");
-
-	      switch (param->interlace) 
-		{		  
-		case Y4M_ILACE_TOP_FIRST:
-		  mjpeg_info("Processing interlaced, top-first %s", pngname);
-#if 0 
-		  decode_jpeg_raw(jpegdata, jpegsize,
-				  Y4M_ILACE_TOP_FIRST,
-				  420, param->width, param->height,
-				  yuv[0], yuv[1], yuv[2]);
-#endif
-		  break;
-		case Y4M_ILACE_BOTTOM_FIRST:
-		  mjpeg_info("Processing interlaced, bottom-first %s", pngname);
-#if 0 
-		  decode_jpeg_raw(jpegdata, jpegsize,
-				  Y4M_ILACE_BOTTOM_FIRST,
-				  420, param->width, param->height,
-				  yuv[0], yuv[1], yuv[2]);
-#endif
-		  break;
-		default:
-		  mjpeg_error_exit1("FATAL logic error?!?");
-		  break;
-		}
-	    }
-#endif
 	  mjpeg_debug("Converting frame to YUV format.");
 	  /* Transform colorspace, then subsample (in place) */
-	  convert_RGB_to_YCbCr(yuv, param->height *  param->new_width);
-	  chroma_subsample(param->ss_mode, yuv, param->new_width, param->height);
+	  convert_RGB_to_YCbCr(yuv, param->new_height *  param->new_width);
+	  chroma_subsample(param->ss_mode, yuv, param->new_width, param->new_height);
 
 	  mjpeg_debug("Frame decoded, now writing to output stream.");
 	}
-      
+
       mjpeg_debug("Frame decoded, now writing to output stream.");
       y4m_write_frame(STDOUT_FILENO, &streaminfo, &frameinfo, yuv);
     }
 
-#if 0 
-  if (param->make_z_alpha)
-    {
-      za_write_end(z_alpha_fp);
-      fclose(z_alpha_fp);
-    }
-#endif
-
   y4m_fini_stream_info(&streaminfo);
   y4m_fini_frame_info(&frameinfo);
   free(yuv[0]);
@@ -614,7 +515,6 @@ static int generate_YUV4MPEG(parameters_t *param)
 int main(int argc, char ** argv)
 { 
   parameters_t param;
-  sh_param = &param;
 
   y4m_accept_extensions(1);
 
@@ -632,13 +532,3 @@ int main(int argc, char ** argv)
 
   return 0;
 }
-
-
-
-
-
-
-
-
-
-
