CESNET/GPUJPEG

No api to load image from memory buffer?

shi-yan opened this issue · 9 comments

In my use case, I need to encode an image directly from a memory buffer. There doesn't seem to be an api for that, other than loading images from files.

Hi,
acutally there is the API (as well as for a CUDA buffer and a GL texture) – please look into the file gpujpeg_encoder.h. You'll probably want the function gpujpeg_encoder_input_set_image() (you can refer for example main.c).

Kindest regards,
Martin

the project isn't documented well.

I tried the following:

// for YUV420 format
cudaMallocHost((void**)&image, width * height * sizeof(uint8_t) *1.5 );
struct gpujpeg_encoder_input encoder_input;
gpujpeg_encoder_input_set_image(&encoder_input, image);

But this doesn't work, and caused a crash. After debugging, it turned out that the buffer's height is rounded to a multiply of the macro block size. This makes sense. But there is no mentioning of this in the document.

there is a function gpujpeg_coder_init_image, which appears to be an initialization function for image. but there is no mentioning of it in the doc.

this is the full code btw:

#include "libgpujpeg/gpujpeg.h"

int main( int argc, char** argv )
{

     if ( gpujpeg_init_device(0, 0) )
          return -1;
  const int width = 1920;
  const int height = 1080;
    struct gpujpeg_parameters param;
    gpujpeg_set_default_parameters(&param);   
    param.quality = 100; 
    gpujpeg_parameters_chroma_subsampling_420(&param);
    
    struct gpujpeg_image_parameters param_image;
    gpujpeg_image_set_default_parameters(&param_image);
    param_image.width = width;
    param_image.height = height;
    param_image.comp_count = 3;
    // (for now, it must be 3)
    param_image.color_space = GPUJPEG_NONE; 
    // or GPUJPEG_YCBCR_ITU_R or GPUJPEG_YCBCR_JPEG
    // (default value is GPUJPEG_RGB)
    param_image.pixel_format = GPUJPEG_420_U8_P0P1P2;

    gpujpeg_parameters_chroma_subsampling_420(&param);

    // or GPUJPEG_4_2_2
    // (default value is GPUJPEG_4_4_4)
    uint8_t* image = NULL;
    int image_size = gpujpeg_image_calculate_size(&param_image);

    struct gpujpeg_encoder* encoder = gpujpeg_encoder_create(nullptr);
      if ( encoder == NULL )
          return -1;

    printf("%d\n", image_size);
    cudaMallocHost((void**)&image, image_size );
      struct gpujpeg_encoder_input encoder_input;

      gpujpeg_encoder_input_set_image(&encoder_input, image);
     uint8_t* image_compressed = NULL;
      int image_compressed_size = 0;

      if ( gpujpeg_encoder_encode(encoder, &param, &param_image, &encoder_input, &image_compressed, 
               &image_compressed_size) != 0 )
      {
        std::cout << "returned" << std::endl;
          return -1;
      }
        
      if ( gpujpeg_image_save_to_file("test.jpg", image_compressed, 
               image_compressed_size) != 0 )
      {
          std::cout << "returned" << std::endl;
          return -1;
      }

     gpujpeg_image_destroy(image_compressed);

        
    

      gpujpeg_image_destroy(image);
      gpujpeg_encoder_destroy(encoder);
  

  //delete [] data;

  return 0;
}

the project isn't documented well.

I tried the following:

// for YUV420 format
cudaMallocHost((void**)&image, width * height * sizeof(uint8_t) *1.5 );
struct gpujpeg_encoder_input encoder_input;
gpujpeg_encoder_input_set_image(&encoder_input, image);

But this doesn't work, and caused a crash. After debugging, it turned out that the buffer's height is rounded to a multiply of the macro block size. This makes sense. But there is no mentioning of this in the document.

Well, actually this should work if the source is YUV420 (and both dimensions divisible by 2). If not, it is a bug. The rounding is rather the property of the JPEG (that it always encodes whole MCUs) but it is not a requirement for input buffer.

there is a function gpujpeg_coder_init_image, which appears to be an initialization function for image. but there is no mentioning of it in the doc.

This function is AFAIK part of the internal API and shouldn't be used directly and thus it is not mentioned in README. However, you actually correctly pointed out to a problem that GPUJPEG doesn't have separated external and internal API. Generally speaking, everything outside gpujpeg_encoder.h and gpujpeg_decoder.h can be considered as an internal API.

I have attached my full code, it will cause CUDA exception.

I can't tell anything wrong. the dimensions, 1920 1080 are divisible by 2.

I have attached my full code, it will cause CUDA exception.

I can't tell anything wrong. the dimensions, 1920 1080 are divisible by 2.

Yes, that's why I am telling that it is bug in GPUJPEG.

I've perhaps fixed the problem – the problem was indeed that there was incorrectly computed portion of CUDA-copied source data. It was rounded to a multiple of MCU which shouldn't have been done for source data. The fix should be in the master branch now.

The image color space needs to be set now to GPUJPEG_YCBCR_JPEG or GPUJPEG_YCBCR_BT601_256LVLS (which are now aliases) to avoid color transformations (which are currently not supported for planar pixel formats). I guess that setting color to GPUJPEG_NONE was rather incorrect so the behavior has changed here.

Regards,
Martin

Just FYI, I've now realized that it may still not work for width not divisible by 8 (444) or 16 (422 and 420 subsampling). Sorry for that – the support for planar raw formats is relatively new and it is not entirely complete (similarly the absence of color space conversion) as packed formats are.

I'll check (and fix if needed) the width next week.

Thank you.

As a suggestion, I use https://github.com/nayuki/QR-Code-generator and https://sourceforge.net/projects/zbar/

to implement automatic test. I use the first lib to generate a qr code on a randomly sized image, and encode.

Then I decode it and use the second lib to read the qr code and make sure the content is correct.

As I supposed, widths non divisible with MCU width were also broken. It should be fixed in 8ea95f0.