PNG 文件被截断并且不能被每个应用程序显示

PNG file are truncated and cannot be shown by every application

我有一个生成 PNG 文件的代码(使用 libpng)。 我可以使用 EOG (Eye Of Gnome) 打开这些文件,但使用 GIMP、imagemagic 和其他工具时我遇到了错误。 Exiftool 告诉我 png 文件被截断了,但我看不到在哪里。在 EOG 上一切正常。

代码:

int savepng(const char *name, fits *fit, uint32_t bytes_per_sample,
        gboolean is_colour) {
    int32_t ret = -1;
    png_structp png_ptr;
    png_infop info_ptr;
    const uint32_t width = fit->rx;
    const uint32_t height = fit->ry;

    char *filename = strdup(name);
    if (!ends_with(filename, ".png")) {
        filename = str_append(&filename, ".png");
    }

    FILE *p_png_file = g_fopen(name, "wb");
    if (p_png_file == NULL) {
        return ret;
    }

    /* 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 NULL 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, NULL, NULL, NULL);
    if (png_ptr == NULL) {
        fclose(p_png_file);
        return ret;
    }

    /* Allocate/initialize the image information data.  REQUIRED */
    info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL) {
        fclose(p_png_file);
        png_destroy_write_struct(&png_ptr, NULL);
        return ret;
    }

    /* 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 writing the file */
        fclose(p_png_file);
        png_destroy_write_struct(&png_ptr, &info_ptr);
        return ret;
    }

    /* Set up the output control if you are using standard C streams */
    png_init_io(png_ptr, p_png_file);

    /* 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
     */
    if (is_colour) {
        png_set_IHDR(png_ptr, info_ptr, width, height, bytes_per_sample * 8,
                PNG_COLOR_TYPE_RGB,
                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
                PNG_FILTER_TYPE_DEFAULT);
        uint32_t profile_len;
        const unsigned char *profile = get_sRGB_profile_data(&profile_len);

        if (profile_len > 0) {
            png_set_iCCP(png_ptr, info_ptr, *name ? name : "icc", 0, (png_const_bytep) profile, profile_len);
        }
    } else {
        png_set_IHDR(png_ptr, info_ptr, width, height, bytes_per_sample * 8,
                PNG_COLOR_TYPE_GRAY,
                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
                PNG_FILTER_TYPE_DEFAULT);
    }

    /* Write the file header information.  REQUIRED */
    png_write_info(png_ptr, info_ptr);

    png_bytep *row_pointers = malloc((size_t) height * sizeof(png_bytep));

    int samples_per_pixel;
    if (is_colour) {
        samples_per_pixel = 3;
    } else {
        samples_per_pixel = 1;
    }

    if (bytes_per_sample == 2) {
        /* swap bytes of 16 bit files to most significant bit first */
        png_set_swap(png_ptr);
        WORD *data = convert_data(fit);
        for (unsigned i = 0, j = height - 1; i < height; i++)
            row_pointers[j--] = (png_bytep) ((uint16_t*) data + (size_t) samples_per_pixel * i * width);
    } else {
        uint8_t *data = convert_data8(fit);
        for (unsigned i = 0, j = height - 1; i < height; i++)
            row_pointers[j--] = (uint8_t*) data + (size_t) samples_per_pixel * i * width;
    }

    png_write_image(png_ptr, row_pointers);

    /* Clean up after the write, and free any memory allocated */
    png_destroy_write_struct(&png_ptr, &info_ptr);


    /* Close the file */
    fclose(p_png_file);
    free(row_pointers);
    free(filename);
    return 0;
}

有人可以指出我的错误吗?无法读取图像的软件只会显示一个错误对话框。就这样。所以我很难知道错误在哪里。

我认为png_write_image()只写入了图像行数据,因此缺少各种headers或meta-data的元素。我通常使用 png_write_png() 来写整个文件。

我附上了一些对我来说绝对有用的代码,因为 Gimp 等可以读取输出。我不声称它是 production-quality ;)

int bitmap_write_png (const bitmap_t *bitmap, const char *path)
  {
  int ret = -1;

  size_t x, y;
  int pixel_size = 3;
  int depth = 8;

  FILE *fp = fopen (path, "wb");
  if (fp)
    {
    ret = 0;
    png_structp png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
      NULL, NULL, NULL); 
    png_infop info_ptr = info_ptr = png_create_info_struct (png_ptr);

    png_set_IHDR (png_ptr,
                  info_ptr,
                  bitmap->width,
                  bitmap->height,
                  depth,
                  PNG_COLOR_TYPE_RGB,
                  PNG_INTERLACE_NONE,
                  PNG_COMPRESSION_TYPE_DEFAULT,
                  PNG_FILTER_TYPE_DEFAULT);

    png_byte ** row_pointers = png_malloc (png_ptr,
        bitmap->height * sizeof (png_byte *));
    for (y = 0; y < bitmap->height; y++)
      {
      png_byte *row =  
            png_malloc (png_ptr, sizeof (uint8_t) * bitmap->width * pixel_size);
      row_pointers[y] = row;
      for (x = 0; x < bitmap->width; x++)
        {
        pixel_t *pixel = pixel_at_const (bitmap, x, y);
        *row++ = pixel->red * 255;
        *row++ = pixel->green * 255;
        *row++ = pixel->blue * 255;
        }
      }

    png_init_io (png_ptr, fp);
    png_set_rows (png_ptr, info_ptr, row_pointers);
    png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    for (y = 0; y < bitmap->height; y++)
      {
      png_free (png_ptr, row_pointers[y]);
      }
    png_free (png_ptr, row_pointers);
    close (fp);
    }
  else
    {
    ret = errno;
    }
  return ret;
  }

根据@MarkSetchell 的建议,缺少一行:

/* Clean up after the write, and free any memory allocated */
    png_write_end(png_ptr, info_ptr); // this line was missing
    png_destroy_write_struct(&png_ptr, &info_ptr);

就这些了。

谢谢。