1

My project need to receive a PNG file content over HTTP request, do something to the image and sending back the generated PNG back in the HTTP response. All code need to be done in C/C++.

I'm new to libpng. So I try to write a prototype, reading a PNG file into unsigned char buffer, get the RGB values out (ignore alpha), do no-op, create a new unsigned char buffer with PNG file content, write the new file to the disk and validate I "generate" the same image. I have referenced this question in StackOverflow

My code:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include <png.h>

using namespace std;

typedef struct  
{
    png_bytep data;
    int size;
} ImageTarget;

int read_png(string file_path, unsigned char** buffer)
{
    // ... ...
}

void write_png(string file_path, unsigned char* buffer, int length)
{
    // ... ...
}

static void pngReadCallback(png_structp png_ptr, png_bytep data, png_size_t length)
{
    // ... ...
}

void pngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length)
{
    cout << "- length ----------- " << length << endl;
    ImageTarget * itarget = (ImageTarget*)png_get_io_ptr(png_ptr);
    size_t nsize = itarget->size + length;
    cout << "- nsize ----------- " << nsize << endl;
    cout << "- data ------ " << (size_t) itarget->data << endl;

    if(itarget->data != nullptr)
        itarget->data = (unsigned char*)realloc(itarget->data, nsize);
    else
        itarget->data = (unsigned char*)malloc(nsize);

    memcpy(itarget->data + itarget->size, data, length);
    itarget->size += length;
}

int main()
{
    const string Input_PNG = "pic/c.png";
    const string Output_PNG = "output/output.png";
    
    unsigned char* buffer = nullptr;
    int length = read_png(Input_PNG, &buffer);

    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
    png_infop info_ptr = png_create_info_struct(png_ptr);

    ImageSource imgsource;
    imgsource.data = buffer;
    imgsource.size = length;
    imgsource.offset = 0;
    png_set_read_fn(png_ptr, &imgsource, pngReadCallback);

    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_ALPHA, 0);
    int w = png_get_image_width( png_ptr, info_ptr );
    int h = png_get_image_height( png_ptr, info_ptr );
    
    cout << "Image width (from PNG file): " << w << endl;
    cout << "Image height (from PNG file): " << h << endl;

    png_bytep* row_pointers = png_get_rows( png_ptr, info_ptr );
    png_bytep raw_rgb = (png_bytep)malloc(w * h * 3);
    int i = 0;
    for(int y=0; y<h; ++y ) {
        for(int x=0; x<w*3; ) {
            raw_rgb[i++] = row_pointers[y][x++]; // red
            raw_rgb[i++] = row_pointers[y][x++]; // green
            raw_rgb[i++] = row_pointers[y][x++]; // blue
        }
    }    

    // Do Something
    
    png_destroy_read_struct( &png_ptr, &info_ptr, 0);

    // ---------------------------------

    png_structp wpng_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_infop winfo_ptr = png_create_info_struct(wpng_ptr);
    setjmp(png_jmpbuf(wpng_ptr));

    png_set_IHDR(wpng_ptr, winfo_ptr, 720, 720, 8,
            PNG_COLOR_TYPE_RGB,
            PNG_INTERLACE_NONE,
            PNG_COMPRESSION_TYPE_DEFAULT,
            PNG_FILTER_TYPE_DEFAULT);
    png_set_rows(wpng_ptr, winfo_ptr, &raw_rgb);

    ImageTarget itarget;
    itarget.data = nullptr;
    itarget.size = 0;
    png_set_write_fn(wpng_ptr, &itarget, pngWriteCallback, NULL);

    png_write_png(wpng_ptr, winfo_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    cout << "Output file name: " << Output_PNG << endl;
    write_png(Output_PNG, itarget.data, length);
    return 0;
}

The makefile I use: here.

After compile and run my code, I see the output (I have verified the c.png I used is a PNG file by Irfanview):

Image width (from PNG file): 720
Image height (from PNG file): 720
- length ----------- 8
- nsize ----------- 8
- data ------ 0
- length ----------- 8
- nsize ----------- 16
- data ------ 22161152
- length ----------- 13
- nsize ----------- 29
- data ------ 22161152
- length ----------- 4
- nsize ----------- 33
- data ------ 22161152
[1]    6675 segmentation fault (core dumped)  ./png_from_buffer

Check the core file with gdb, here is the output:

#0  __memcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S:35
#1  0x00007f3c41b3caf0 in png_write_row () from /lib/x86_64-linux-gnu/libpng12.so.0
#2  0x00007f3c41b3cd78 in png_write_image () from /lib/x86_64-linux-gnu/libpng12.so.0
#3  0x00007f3c41b3d61b in png_write_png () from /lib/x86_64-linux-gnu/libpng12.so.0
#4  0x000000000040269c in main () at main.cpp:181

I tried using different buffer, convert to vector<unsigned char*> as the rows. No luck so far. Any idea will be appreciated.

My environment, if matters:

  • Ubuntu 16.04
  • libpng 1.2
    • libpng12-0/xenial-updates,xenial-security,now 1.2.54-1ubuntu1.1 amd64 [installed]
    • libpng12-dev/xenial-updates,xenial-security,now 1.2.54-1ubuntu1.1 amd64 [installed,automatic]
  • g++ (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609
Thomas Fritz
  • 140
  • 2
  • 10
NonStatic
  • 951
  • 1
  • 8
  • 27
  • 4
    [mcve] or it didn't happen. – Swordfish Nov 10 '18 at 08:02
  • Thank you @Swordfish! I do have a compilable cpp file and makefile in the link. They are too long and I do not want to make this post contain the full content. Could you please try click on the link and help me taking a look? Thanks! – NonStatic Nov 10 '18 at 08:04
  • @NonStatic Example needs to be **Minimal** to fit into the post. Actually the posted snippet is full of dangerous raw pointer juggling and wild casts. You should probably fix all that stuff because it is the third most common reason of program bugs. – user7860670 Nov 10 '18 at 08:08
  • This is just a prototype to learn/understand libpng APIs. I will definitely not using those raw pointers in my product code. – NonStatic Nov 10 '18 at 08:10
  • please, go and write up a [mcve] that shows the behaviour you have a question about. – Swordfish Nov 10 '18 at 08:11
  • Simplified the code and output. Hope this could make things easier. – NonStatic Nov 10 '18 at 08:18

2 Answers2

2

I figured out a solution: using libpng v1.6 and the problem could be fixed easily as below:

// READING....

png_image image; 
memset(&image, 0, (sizeof image));
image.version = PNG_IMAGE_VERSION;
if (png_image_begin_read_from_memory(&image, file_buffer, length) == 0) 
{
    return -1;
}

png_bytep buffer;
image.format = PNG_FORMAT_BGR;
size_t input_data_length = PNG_IMAGE_SIZE(image);
buffer = (png_bytep)malloc(input_data_length);
memset(buffer, 0, input_data_length);

if (png_image_finish_read(&image, NULL, buffer, 0, NULL) == 0) 
{
    return -1;
}

Writing to a memory buffer is also quite easy:

// WRITING......

png_image wimage;
memset(&wimage, 0, (sizeof wimage));
wimage.version = PNG_IMAGE_VERSION;
wimage.format = PNG_FORMAT_BGR;
wimage.height = 720;
wimage.width = 720;

// Get memory size
bool wresult = png_image_write_to_memory(&wimage, nullptr, &wlength, 0, buffer, 0, nullptr);
if (!wresult)
{
    cout << "Error: " << image.message << endl;
}

// Real write to memory
unsigned char* wbuffer = (unsigned char*)malloc(wlength);
wresult = png_image_write_to_memory(&wimage, wbuffer, &wlength, 0, buffer, 0, nullptr);

write_png(Output_PNG, wbuffer, wlength);
NonStatic
  • 951
  • 1
  • 8
  • 27
0

libpng v1.6 exist simple api.Both png_image_write_to_memory you use will consume double time. I found the better sample in github: https://gist.github.com/dobrokot/10486786 I hope that it helps.

  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/32148181) – Abhishek Dutt Jul 07 '22 at 07:56