Search code examples
c++iostreamistreamlibjpeg

How to use libjpeg to read a JPEG from a std::istream?


libjpeg can read JPEG data from a FILE* or a buffer. My data is coming from a std::istream. I could read the entire std::istream into a buffer to use with libjpeg, but I'd rather have libjpeg read directly from the std::istream if possible. How can this be done?


Solution

  • You just need to provide wrappers around your istream. Define a struct, for instance

    struct JpegStream {
      jpeg_source_mgr pub;
      std::istream* stream;
      byte buffer [4096];
    }
    

    Then you need four methods to operate on the stream:

    void init_source (j_decompress_ptr cinfo)
    {
        auto src = (JpegStream*)(cinfo->src);
        src->stream-> // seek to 0 here
    }
    
    boolean fill_buffer (j_decompress_ptr cinfo)
    {
        // Read to buffer
        JpegStream* src = // as above
        src->pub.next_input_byte = src->buffer;
        src->pub.bytes_in_buffer = // How many yo could read
    
        return eof() ? FALSE : TRUE;
    }
    
    void skip (j_decompress_ptr cinfo, long count)
    {
       // Seek by count bytes forward
       // Make sure you know how much you have cached and subtract that
       // set bytes_in_buffer and next_input_byte
    }
    
    void term (j_decompress_ptr cinfo)
    {
        // Close the stream, can be nop
    }
    

    and one method to bind them to the JPEG decompression info structure:

    void make_stream (j_decompress_ptr cinfo, std::istream* in)
    {
        JpegStream * src;
    
        /* The source object and input buffer are made permanent so that a series
        * of JPEG images can be read from the same file by calling jpeg_stdio_src
        * only before the first one.  (If we discarded the buffer at the end of
        * one image, we'd likely lose the start of the next one.)
        * This makes it unsafe to use this manager and a different source
        * manager serially with the same JPEG object.  Caveat programmer.
        */
        if (cinfo->src == NULL)
        {   
                /* first time for this JPEG object? */
                cinfo->src = (struct jpeg_source_mgr *)
            (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, POOL_PERMANENT,   sizeof(JpegStream));
                src = reinterpret_cast<JpegStream*> (cinfo->src);
        }
    
        src = reinterpret_cast<JpegStream*> (cinfo->src);
        src->pub.init_source = init_source;
        src->pub.fill_input_buffer = fill_buffer;
        src->pub.skip_input_data = skip;
        src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
        src->pub.term_source = term;
        src->stream = in;
        src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
        src->pub.next_input_byte = NULL; /* until buffer loaded */
    }
    

    After calling jpeg_create_decompress, call your make_stream function.