From 861f59061f8f823009eeca4d999216237f179307 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Sat, 14 Oct 2017 10:50:46 +0200 Subject: [PATCH] [DEV] support of JPG compression ==> 1 thread only, code not finished --- egami/egami.cpp | 20 +++++- egami/wrapperJPG.cpp | 162 +++++++++++++++++++++++++++++++++++++++++++ egami/wrapperJPG.hpp | 14 ++++ sample/write.cpp | 2 +- 4 files changed, 195 insertions(+), 3 deletions(-) diff --git a/egami/egami.cpp b/egami/egami.cpp index e56040b..8ce01d9 100644 --- a/egami/egami.cpp +++ b/egami/egami.cpp @@ -161,7 +161,15 @@ bool egami::store(const egami::Image& _input, const etk::String& _fileName) { return false; #endif } else if (etk::end_with(tmpName, ".jpg") == true) { - EGAMI_ERROR("Can not store in JPEG file '" << _fileName << "'"); + #ifdef EGAMI_BUILD_JPEG + if (egami::storeJPG(_fileName, _input) == false) { + EGAMI_ERROR("Error to store JPEG file '" << _fileName << "'"); + return false; + } + #else + EGAMI_WARNING("egami not compile with the JPEG dependency for file '" << _fileName << "'"); + return false; + #endif return false; } else if (etk::end_with(tmpName, ".j2k") == true) { EGAMI_ERROR("Can not store in JPEG 2000 file '" << _fileName << "'"); @@ -195,7 +203,15 @@ bool egami::store(const egami::Image& _input, etk::Vector& _buffer, con return false; #endif } else if (_mineType == "image/jpeg") { - EGAMI_ERROR("Can not store in JPEG for Raw output"); + #ifdef EGAMI_BUILD_JPEG + if (egami::storeJPG(_buffer, _input) == false) { + EGAMI_ERROR("Error to store JPG for Raw output"); + return false; + } + #else + EGAMI_WARNING("egami not compile with the JPG dependency for Raw output"); + return false; + #endif return false; } else { EGAMI_ERROR("Extention not managed for Raw output Sopported extention: .bmp / .png / .jpg"); diff --git a/egami/wrapperJPG.cpp b/egami/wrapperJPG.cpp index d2dfc76..ef63d9b 100644 --- a/egami/wrapperJPG.cpp +++ b/egami/wrapperJPG.cpp @@ -132,4 +132,166 @@ egami::Image egami::loadJPG(const etk::Vector& _buffer) { return out; } +static etk::Vector myBuffer; +#define BLOCK_SIZE 16384 +void myInitDestination(j_compress_ptr _cinfo) { + myBuffer.resize(BLOCK_SIZE); + _cinfo->dest->next_output_byte = &myBuffer[0]; + _cinfo->dest->free_in_buffer = myBuffer.size(); +} + +boolean myEmptyOutputBuffer(j_compress_ptr _cinfo) { + size_t oldsize = myBuffer.size(); + myBuffer.resize(oldsize + BLOCK_SIZE); + _cinfo->dest->next_output_byte = &myBuffer[oldsize]; + _cinfo->dest->free_in_buffer = myBuffer.size() - oldsize; + return TRUE; +} + +void myTermDestination(j_compress_ptr _cinfo) { + myBuffer.resize(myBuffer.size() - _cinfo->dest->free_in_buffer); +} + +bool egami::storeJPG(const etk::String& _fileName, const egami::Image& _inputImage) { + etk::FSNode fileName(_fileName); + EGAMI_VERBOSE("File='" << _fileName << "' ==> " << fileName << " ==> " << fileName.getFileSystemName()); + if(fileName.fileOpenWrite() == false) { + EGAMI_ERROR("Can not crete the output file name='" << fileName << "'"); + return false; + } + etk::Vector allData; + bool ret = storeJPG(allData, _inputImage); + fileName.fileWriteAll(allData); + fileName.fileClose(); + return ret; +} + +/* + * IMAGE DATA FORMATS: + * + * The standard input image format is a rectangular array of pixels, with + * each pixel having the same number of "component" values (color channels). + * Each pixel row is an array of JSAMPLEs (which typically are unsigned chars). + * If you are working with color data, then the color values for each pixel + * must be adjacent in the row; for example, R,G,B,R,G,B,R,G,B,... for 24-bit + * RGB color. + * + * For this example, we'll assume that this data structure matches the way + * our application has stored the image in memory, so we can just pass a + * pointer to our image buffer. In particular, let's say that the image is + * RGB color and is described by: + */ + +int quality = 250; + +bool egami::storeJPG(etk::Vector& _buffer, const egami::Image& _inputImage) { + _buffer.clear(); + /* This struct contains the JPEG compression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + * It is possible to have several such structures, representing multiple + * compression/decompression processes, in existence at once. We refer + * to any one struct (and its associated working data) as a "JPEG object". + */ + struct jpeg_compress_struct cinfo; + // We use our private extension JPEG error handler. Note that this struct must live as long as the main JPEG parameter struct, to avoid dangling-pointer problems. + struct my_error_mgr jerr; + /* More stuff */ + int row_stride; /* physical row width in image buffer */ + + /* Step 1: allocate and initialize JPEG compression object */ + + // We set up the normal JPEG error routines, then override error_exit. + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + + /* Here we use the library-supplied code to send compressed data to a + * stdio stream. You can also write your own code to do something else. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to write binary files. + */ + #if 0 + FILE * outfile; /* target file */ + if ((outfile = fopen(filename, "wb")) == NULL) { + fprintf(stderr, "can't open %s\n", filename); + exit(1); + } + jpeg_stdio_dest(&cinfo, outfile); + #else + /* + uint8_t* rgba = nullptr; + unsigned long size = 0; + etk::Vector buffer. + jpeg_mem_dest(jpegdata, &rgba, &size); + if(size > 0) { + buffer.resize(size); + for(ii=0; iiiinit_destination = &myInitDestination; + cinfo.dest->empty_output_buffer = &myEmptyOutputBuffer; + cinfo.dest->term_destination = &myTermDestination; + #endif + + // Step 3: set parameters for compression + // First we supply a description of the input image. Four fields of the cinfo struct must be filled in: + cinfo.image_width = _inputImage.getSize().x(); + cinfo.image_height = _inputImage.getSize().y(); + // # of color components per pixel + cinfo.input_components = getFormatColorSize(_inputImage.getType()); + // colorspace of input image + cinfo.in_color_space = JCS_RGB; + /* Now use the library's routine to set default compression parameters. + * (You must set at least cinfo.in_color_space before calling this, + * since the defaults depend on the source color space.) + */ + jpeg_set_defaults(&cinfo); + /* Now you can set any non-default parameters you wish to. + * Here we just illustrate the use of quality (quantization table) scaling: + */ + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + + /* Step 4: Start compressor */ + + /* TRUE ensures that we will write a complete interchange-JPEG file. + * Pass TRUE unless you are very sure of what you're doing. + */ + jpeg_start_compress(&cinfo, TRUE); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + uint8_t * dataPointer = (uint8_t*)_inputImage.getTextureDataPointer(); + while (cinfo.next_scanline < cinfo.image_height) { + /* jpeg_write_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could pass + * more than one scanline at a time if that's more convenient. + */ + JSAMPROW tmp[1]; + tmp[0] = &dataPointer[cinfo.next_scanline * cinfo.image_width * getFormatColorSize(_inputImage.getType())]; + (void) jpeg_write_scanlines(&cinfo, tmp, 1); + } + + /* Step 6: Finish compression */ + + jpeg_finish_compress(&cinfo); + /* Step 7: release JPEG compression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_compress(&cinfo); + etk::swap(_buffer, myBuffer); + return true; +} diff --git a/egami/wrapperJPG.hpp b/egami/wrapperJPG.hpp index 276e5cd..74873df 100644 --- a/egami/wrapperJPG.hpp +++ b/egami/wrapperJPG.hpp @@ -20,5 +20,19 @@ namespace egami { * @return Read Image. */ egami::Image loadJPG(const etk::Vector& _buffer); + /** + * @breif Store a jpg file in the image. + * @param[in] _fileName Name of the file. + * @param[in] _inputImage write data. + * @return true if all is done correctly, false otherwise. + */ + bool storeJPG(const etk::String& _fileName, const egami::Image& _inputImage); + /** + * @breif Store a jpg file in the image. + * @param[out] _buffer output file buffer. + * @param[in] _inputImage write data. + * @return true if all is done correctly, false otherwise. + */ + bool storeJPG(etk::Vector& _buffer, const egami::Image& _inputImage); } diff --git a/sample/write.cpp b/sample/write.cpp index 058f04b..9778019 100644 --- a/sample/write.cpp +++ b/sample/write.cpp @@ -43,7 +43,7 @@ static void writePNG() { } static void writeJPG() { // create an empty Image (no type and no inside data) - egami::Image image(ivec2(25,25), egami::colorType::RGBA8); + egami::Image image(ivec2(25,25), egami::colorType::RGB8); image.set(ivec2(5,5), etk::Color<>(0x88, 0xFF, 0x00, 0xFF)); image.set(ivec2(12,15), etk::Color<>(0x88, 0xFF, 0x00, 0xFF)); image.set(ivec2(4,9), etk::Color<>(0x88, 0xFF, 0x00, 0xFF));