// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdmisc.h" #include "nel/misc/bitmap.h" #ifdef USE_JPEG #define XMD_H #undef FAR #include "nel/misc/stream.h" #include "nel/misc/file.h" #include extern "C" { #include } #endif using namespace std; namespace NLMISC { #ifdef USE_JPEG static NLMISC::IStream *JPGStream = NULL; static const uint32 JPGBufferSize = 4096; static uint32 JPGStreamSize = 0; static char JPGBuffer[JPGBufferSize]; struct my_error_mgr { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; }; void my_error_exit(j_common_ptr cinfo) { my_error_mgr *myerr = (my_error_mgr *) cinfo->err; nlwarning("error while processing JPEG image"); longjmp(myerr->setjmp_buffer, 1); } static void jpgDecompressInit(j_decompress_ptr cinfo) { // get stream size if possible if (JPGStream->seek(0, IStream::end)) JPGStreamSize = JPGStream->getPos(); else nlwarning("can't get JPEG stream size"); // reset current position to the beginning JPGStream->seek(0, IStream::begin); cinfo->src->next_input_byte = (unsigned char *)JPGBuffer; cinfo->src->bytes_in_buffer = 0; } static boolean jpgDecompressFill(j_decompress_ptr cinfo) { uint length = std::min(JPGBufferSize, JPGStreamSize - JPGStream->getPos()); try { JPGStream->serialBuffer((uint8*) JPGBuffer, length); } catch(...) { nlwarning("error while reading JPEG image"); cinfo->src->next_input_byte = (unsigned char *)JPGBuffer; cinfo->src->bytes_in_buffer = 0; return FALSE; } cinfo->src->next_input_byte = (unsigned char *)JPGBuffer; cinfo->src->bytes_in_buffer = length; return TRUE; } static void jpgDecompressSkip(j_decompress_ptr cinfo, long num_bytes) { if (num_bytes > 0) { while (num_bytes > (long) cinfo->src->bytes_in_buffer) { num_bytes -= (long) cinfo->src->bytes_in_buffer; jpgDecompressFill(cinfo); } cinfo->src->next_input_byte += (size_t) num_bytes; cinfo->src->bytes_in_buffer -= (size_t) num_bytes; } } static void jpgDecompressTerm(j_decompress_ptr cinfo) { } static jpeg_source_mgr jpgSourceManager = { NULL, 0, jpgDecompressInit, jpgDecompressFill, jpgDecompressSkip, jpeg_resync_to_restart, jpgDecompressTerm }; /*-------------------------------------------------------------------*\ readJPG \*-------------------------------------------------------------------*/ uint8 CBitmap::readJPG( NLMISC::IStream &f ) { if(!f.isReading()) return false; struct jpeg_decompress_struct cinfo; // set up errors manager struct my_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_decompress(&cinfo); nlwarning("failed to setjump"); return 0; } // set the stream to read from JPGStream = &f; // init decompress jpeg_create_decompress(&cinfo); cinfo.src = &jpgSourceManager; // read header of image if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { jpeg_destroy_decompress(&cinfo); nlwarning("failed to read header"); return 0; } uint dstChannels, srcChannels; if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { dstChannels = 1; srcChannels = 1; resize (cinfo.image_width, cinfo.image_height, Luminance); } else { // force conversion of color spaces in RGB dstChannels = 4; srcChannels = 3; cinfo.out_color_space = JCS_RGB; resize (cinfo.image_width, cinfo.image_height, RGBA); } // start decompression of image data if (!jpeg_start_decompress(&cinfo)) { jpeg_destroy_decompress(&cinfo); nlwarning("failed to start decompressing"); return 0; } JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, cinfo.output_width * cinfo.output_components, 1); uint i, j; while (cinfo.output_scanline < cinfo.output_height) { const uint offset = cinfo.output_scanline * _Width * dstChannels; if (jpeg_read_scanlines(&cinfo, buffer, 1) != 1) { nlwarning("failed to read scanline"); break; } for (i = 0; i < _Width; i++) { for (j = 0; j < srcChannels; ++j) _Data[0][offset+i*dstChannels+j] = buffer[0][i*srcChannels+j]; if (PixelFormat == RGBA) _Data[0][offset+i*dstChannels+j] = 255; } } if (!jpeg_finish_decompress(&cinfo)) nlwarning("failed to finish decompressing"); jpeg_destroy_decompress(&cinfo); JPGStream = NULL; return uint8(srcChannels * 8); } static void jpgCompressInit(j_compress_ptr cinfo) { cinfo->dest->next_output_byte = (unsigned char *)JPGBuffer; cinfo->dest->free_in_buffer = JPGBufferSize; } static boolean jpgCompressEmpty(j_compress_ptr cinfo) { JPGStream->serialBuffer((uint8*) JPGBuffer, JPGBufferSize); cinfo->dest->next_output_byte = (unsigned char *)JPGBuffer; cinfo->dest->free_in_buffer = JPGBufferSize; return TRUE; } static void jpgCompressTerm(j_compress_ptr cinfo) { if(JPGBufferSize - cinfo->dest->free_in_buffer > 0) JPGStream->serialBuffer((uint8*) JPGBuffer, (uint)(JPGBufferSize - cinfo->dest->free_in_buffer)); } static jpeg_destination_mgr jpgDestinationManager = { 0, 0, jpgCompressInit, jpgCompressEmpty, jpgCompressTerm }; /*-------------------------------------------------------------------*\ writeJPG \*-------------------------------------------------------------------*/ bool CBitmap::writeJPG( NLMISC::IStream &f, uint8 quality) { if (f.isReading()) return false; if (PixelFormat > AlphaLuminance) return false; if (!_Width || !_Height) return false; struct jpeg_compress_struct cinfo; // set up errors manager struct my_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_compress(&cinfo); nlwarning("failed to setjump"); return false; } // set the stream to write to JPGStream = &f; // init compress jpeg_create_compress(&cinfo); uint srcChannels, dstChannels; if (PixelFormat == RGBA) { srcChannels = 4; dstChannels = cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; } else { srcChannels = PixelFormat == AlphaLuminance ? 2:1; dstChannels = cinfo.input_components = 1; cinfo.in_color_space = JCS_GRAYSCALE; } cinfo.image_width = _Width; cinfo.image_height = _Height; cinfo.dest = &jpgDestinationManager; // set default compression parameters jpeg_set_defaults(&cinfo); // set image quality jpeg_set_quality(&cinfo, quality, TRUE); // start to compress image jpeg_start_compress(&cinfo, TRUE); JSAMPROW row_pointer[1]; row_pointer[0] = new uint8[_Width*dstChannels]; uint i, j; while (cinfo.next_scanline < cinfo.image_height) { const uint offset = cinfo.next_scanline * _Width * srcChannels; for (i = 0; i < _Width; ++i) { for (j = 0; j < dstChannels; ++j) { row_pointer[0][i*dstChannels+j] = (uint8) _Data[0][offset + i*srcChannels+j]; } } // write image scanline if (jpeg_write_scanlines(&cinfo, row_pointer, 1) != 1) { nlwarning("failed to write scanline"); break; } } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); delete row_pointer[0]; row_pointer[0] = NULL; JPGStream = NULL; return true; } #else bool CBitmap::writeJPG( NLMISC::IStream &/* f */, uint8 /* quality */) { nlwarning ("You must compile NLMISC with USE_JPEG if you want jpeg support"); return false; } uint8 CBitmap::readJPG( NLMISC::IStream &/* f */) { nlwarning ("You must compile NLMISC with USE_JPEG if you want jpeg support"); return 0; } #endif }