diff --git a/src/osgPlugins/jpeg/ReaderWriterJPEG.cpp b/src/osgPlugins/jpeg/ReaderWriterJPEG.cpp index 7c57d20ea..1f4be0559 100644 --- a/src/osgPlugins/jpeg/ReaderWriterJPEG.cpp +++ b/src/osgPlugins/jpeg/ReaderWriterJPEG.cpp @@ -48,6 +48,7 @@ extern "C" { #include + #include "jerror.h" }; #include @@ -62,6 +63,313 @@ extern "C" static int jpegerror = ERR_NO_ERROR; +/* CODE FOR READING/WRITING JPEG FROM STREAMS + * This code was taken directly from jdatasrc.c and jdatadst.c (libjpeg source) + * and modified to use a std::istream/ostream* instead of a FILE* + */ + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + std::istream * infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} stream_source_mgr; + +typedef stream_source_mgr * stream_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +void init_source (j_decompress_ptr cinfo) +{ + stream_src_ptr src = (stream_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +boolean fill_input_buffer (j_decompress_ptr cinfo) +{ + stream_src_ptr src = (stream_src_ptr) cinfo->src; + size_t nbytes; + + src->infile->read((char*)src->buffer,INPUT_BUF_SIZE); + nbytes = src->infile->gcount(); + + if (nbytes <= 0) { + if (src->start_of_file) /* Treat empty input file as fatal error */ + ERREXIT(cinfo, JERR_INPUT_EMPTY); + WARNMS(cinfo, JWRN_JPEG_EOF); + /* Insert a fake EOI marker */ + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + } + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = nbytes; + src->start_of_file = FALSE; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +void skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + stream_src_ptr src = (stream_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer(cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ +void term_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + +void jpeg_istream_src(j_decompress_ptr cinfo, std::istream *infile) +{ + stream_src_ptr 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, JPOOL_PERMANENT,sizeof(stream_source_mgr)); + src = (stream_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,INPUT_BUF_SIZE * sizeof(JOCTET)); + } + + src = (stream_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + std::ostream * outfile; /* target stream */ + JOCTET * buffer; /* start of buffer */ +} stream_destination_mgr; + +typedef stream_destination_mgr * stream_dest_ptr; + +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +void init_destination (j_compress_ptr cinfo) +{ + stream_dest_ptr dest = (stream_dest_ptr) cinfo->dest; + + /* Allocate the output buffer --- it will be released when done with image */ + dest->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, OUTPUT_BUF_SIZE * sizeof(JOCTET)); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +boolean empty_output_buffer (j_compress_ptr cinfo) +{ + stream_dest_ptr dest = (stream_dest_ptr) cinfo->dest; + + dest->outfile->write((const char*)dest->buffer,OUTPUT_BUF_SIZE); + if (dest->outfile->bad()) + ERREXIT(cinfo, JERR_FILE_WRITE); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; + + return TRUE; +} + + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +void term_destination (j_compress_ptr cinfo) +{ + stream_dest_ptr dest = (stream_dest_ptr) cinfo->dest; + size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + /* Write any data remaining in the buffer */ + if (datacount > 0) { + dest->outfile->write((const char*)dest->buffer,datacount); + if (dest->outfile->bad()) + ERREXIT(cinfo, JERR_FILE_WRITE); + } + dest->outfile->flush(); + /* Make sure we wrote the output file OK */ + if (dest->outfile->bad()) + ERREXIT(cinfo, JERR_FILE_WRITE); +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +void jpeg_stream_dest (j_compress_ptr cinfo, std::ostream * outfile) +{ + stream_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(stream_destination_mgr)); + } + + dest = (stream_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; +} + +/* END OF READ/WRITE STREAM CODE */ + int simage_jpeg_error(char * buffer, int buflen) { @@ -128,9 +436,8 @@ copyScanline(unsigned char *currPtr, unsigned char *from, int cnt) return currPtr; } - unsigned char * -simage_jpeg_load(const char *filename, +simage_jpeg_load(std::istream& fin, int *width_ret, int *height_ret, int *numComponents_ret) @@ -149,7 +456,7 @@ int *numComponents_ret) */ struct my_error_mgr jerr; /* More stuff */ - FILE * infile; /* source file */ + //FILE * infile; /* source file */ JSAMPARRAY rowbuffer; /* Output row buffer */ int row_stride; /* physical row width in output buffer */ @@ -161,11 +468,11 @@ int *numComponents_ret) * requires it in order to read binary files. */ - if ((infile = fopen(filename, "rb")) == NULL) + /*if ((infile = fopen(filename, "rb")) == NULL) { jpegerror = ERR_OPEN; return NULL; - } + }*/ /* Step 1: allocate and initialize JPEG decompression object */ @@ -180,7 +487,7 @@ int *numComponents_ret) */ jpegerror = ERR_JPEGLIB; jpeg_destroy_decompress(&cinfo); - fclose(infile); + //fclose(infile); //if (buffer) delete [] buffer; return NULL; } @@ -193,7 +500,8 @@ int *numComponents_ret) /* Step 2: specify data source (eg, a file) */ - jpeg_stdio_src(&cinfo, infile); + //jpeg_stdio_src(&cinfo, infile); + jpeg_istream_src(&cinfo,&fin); /* Step 3: read file parameters with jpeg_read_header() */ @@ -281,7 +589,7 @@ int *numComponents_ret) * so as to simplify the setjmp error logic above. (Actually, I don't * think that jpeg_destroy can do an error exit, but why assume anything...) */ - fclose(infile); + //fclose(infile); /* At this point you may want to check to see whether any corrupt-data * warnings occurred (test whether jerr.pub.num_warnings is nonzero). @@ -304,7 +612,7 @@ int *numComponents_ret) class ReaderWriterJPEG : public osgDB::ReaderWriter { - WriteResult::WriteStatus write_JPEG_file (const char* filename,int image_width,int image_height,JSAMPLE* image_buffer,int quality = 100) const + WriteResult::WriteStatus write_JPEG_file (std::ostream &fout,int image_width,int image_height,JSAMPLE* image_buffer,int quality = 100) const { /* This struct contains the JPEG compression parameters and pointers to * working space (which is allocated as needed by the JPEG library). @@ -323,7 +631,7 @@ class ReaderWriterJPEG : public osgDB::ReaderWriter */ struct jpeg_error_mgr jerr; /* More stuff */ - FILE * outfile; /* target file */ + //FILE * outfile; /* target file */ JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ int row_stride; /* physical row width in image buffer */ @@ -346,11 +654,12 @@ class ReaderWriterJPEG : public osgDB::ReaderWriter * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that * requires it in order to write binary files. */ - if (!(outfile = fopen(filename, "wb"))) + /*if (!(outfile = fopen(filename, "wb"))) { return WriteResult::ERROR_IN_WRITING_FILE; - } - jpeg_stdio_dest(&cinfo, outfile); + }*/ + //jpeg_stdio_dest(&cinfo, outfile); + jpeg_stream_dest(&cinfo, &fout); /* Step 3: set parameters for compression */ @@ -402,7 +711,7 @@ class ReaderWriterJPEG : public osgDB::ReaderWriter jpeg_finish_compress(&cinfo); /* After finish_compress, we can close the output file. */ - fclose(outfile); + //fclose(outfile); /* Step 7: release JPEG compression object */ @@ -434,20 +743,14 @@ class ReaderWriterJPEG : public osgDB::ReaderWriter return osgDB::equalCaseInsensitive(extension,"jpeg") || osgDB::equalCaseInsensitive(extension,"jpg"); } - virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const + ReadResult readJPGStream(std::istream& fin) const { - std::string ext = osgDB::getLowerCaseFileExtension(file); - if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED; - - std::string fileName = osgDB::findDataFile( file, options ); - if (fileName.empty()) return ReadResult::FILE_NOT_FOUND; - unsigned char *imageData = NULL; int width_ret; int height_ret; int numComponents_ret; - imageData = simage_jpeg_load(fileName.c_str(),&width_ret,&height_ret,&numComponents_ret); + imageData = simage_jpeg_load(fin,&width_ret,&height_ret,&numComponents_ret); if (imageData==NULL) return ReadResult::FILE_NOT_HANDLED; @@ -471,7 +774,6 @@ class ReaderWriterJPEG : public osgDB::ReaderWriter unsigned int dataType = GL_UNSIGNED_BYTE; osg::Image* pOsgImage = new osg::Image; - pOsgImage->setFileName(fileName.c_str()); pOsgImage->setImage(s,t,r, internalFormat, pixelFormat, @@ -481,14 +783,44 @@ class ReaderWriterJPEG : public osgDB::ReaderWriter return pOsgImage; } + + virtual ReadResult readImage(std::istream& fin,const osgDB::ReaderWriter::Options* =NULL) const + { + return readJPGStream(fin); + } + + virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const + { + std::string ext = osgDB::getLowerCaseFileExtension(file); + if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED; + + std::string fileName = osgDB::findDataFile( file, options ); + if (fileName.empty()) return ReadResult::FILE_NOT_FOUND; + + std::ifstream istream(fileName.c_str(), std::ios::in | std::ios::binary); + if(!istream) return ReadResult::FILE_NOT_HANDLED; + ReadResult rr = readJPGStream(istream); + if(rr.validImage()) rr.getImage()->setFileName(file); + return rr; + } + + virtual WriteResult writeImage(const osg::Image& img,std::ostream& fout,const osgDB::ReaderWriter::Options *options) const + { + osg::ref_ptr tmp_img = new osg::Image(img); + tmp_img->flipVertical(); + WriteResult::WriteStatus ws = write_JPEG_file(fout,img.s(),img.t(),(JSAMPLE*)(tmp_img->data()),getQuality(options)); + return ws; + } + virtual WriteResult writeImage(const osg::Image &img,const std::string& fileName, const osgDB::ReaderWriter::Options *options) const { std::string ext = osgDB::getFileExtension(fileName); if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED; - osg::ref_ptr tmp_img = new osg::Image(img); - tmp_img->flipVertical(); - WriteResult::WriteStatus ws = write_JPEG_file(fileName.c_str(),img.s(),img.t(),(JSAMPLE*)(tmp_img->data()),getQuality(options)); - return ws; + + std::ofstream fout(fileName.c_str(), std::ios::out | std::ios::binary); + if(!fout) return WriteResult::ERROR_IN_WRITING_FILE; + + return writeImage(img,fout,options); } };