From ac66cc921904387518f2e0b2a14670e9598defe4 Mon Sep 17 00:00:00 2001 From: Johannes Schickel Date: Sun, 11 Aug 2013 23:36:57 +0200 Subject: GRAPHICS: Implement JPEGDecoder based on libjpeg. --- graphics/decoders/jpeg.cpp | 815 ++++++++------------------------------------- graphics/decoders/jpeg.h | 83 +---- 2 files changed, 145 insertions(+), 753 deletions(-) (limited to 'graphics') diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp index ff018c799a..891542c415 100644 --- a/graphics/decoders/jpeg.cpp +++ b/graphics/decoders/jpeg.cpp @@ -20,6 +20,10 @@ * */ +// libjpeg uses forbidden symbols in its header. Thus, we need to allow them +// here. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + #include "graphics/pixelformat.h" #include "graphics/yuv_to_rgb.h" #include "graphics/decoders/jpeg.h" @@ -29,35 +33,19 @@ #include "common/stream.h" #include "common/textconsole.h" -namespace Graphics { - -// Order used to traverse the quantization tables -static const uint8 _zigZagOrder[64] = { - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63 -}; - -JPEGDecoder::JPEGDecoder() : ImageDecoder(), - _stream(NULL), _w(0), _h(0), _numComp(0), _components(NULL), _numScanComp(0), - _scanComp(NULL), _currentComp(NULL), _rgbSurface(0) { +#ifdef USE_JPEG +// The original release of libjpeg v6b did not contain any extern "C" in case +// its header files are included in a C++ environment. To avoid any linking +// issues we need to add it on our own. +extern "C" { +#include +#include +} +#endif - // Initialize the quantization tables - for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) - _quant[i] = NULL; +namespace Graphics { - // Initialize the Huffman tables - for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) { - _huff[i].count = 0; - _huff[i].values = NULL; - _huff[i].sizes = NULL; - _huff[i].codes = NULL; - } +JPEGDecoder::JPEGDecoder() : ImageDecoder(), _rgbSurface(nullptr) { } JPEGDecoder::~JPEGDecoder() { @@ -66,7 +54,7 @@ JPEGDecoder::~JPEGDecoder() { const Surface *JPEGDecoder::getSurface() const { // Make sure we have loaded data - if (!isLoaded()) + if (!_yComponent.getPixels()) return 0; if (_rgbSurface) @@ -74,714 +62,189 @@ const Surface *JPEGDecoder::getSurface() const { // Create an RGBA8888 surface _rgbSurface = new Graphics::Surface(); - _rgbSurface->create(_w, _h, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0)); + _rgbSurface->create(_yComponent.w, _yComponent.h, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0)); - // Get our component surfaces - const Graphics::Surface *yComponent = getComponent(1); - const Graphics::Surface *uComponent = getComponent(2); - const Graphics::Surface *vComponent = getComponent(3); - - YUVToRGBMan.convert444(_rgbSurface, Graphics::YUVToRGBManager::kScaleFull, (const byte *)yComponent->getPixels(), (const byte *)uComponent->getPixels(), (const byte *)vComponent->getPixels(), yComponent->w, yComponent->h, yComponent->pitch, uComponent->pitch); + YUVToRGBMan.convert444(_rgbSurface, Graphics::YUVToRGBManager::kScaleFull, (const byte *)_yComponent.getPixels(), (const byte *)_uComponent.getPixels(), (const byte *)_vComponent.getPixels(), _yComponent.w, _yComponent.h, _yComponent.pitch, _uComponent.pitch); return _rgbSurface; } void JPEGDecoder::destroy() { - // Reset member variables - _stream = NULL; - _w = _h = 0; - _restartInterval = 0; - - // Free the components - for (int c = 0; c < _numComp; c++) - _components[c].surface.free(); - delete[] _components; _components = NULL; - _numComp = 0; - - // Free the scan components - delete[] _scanComp; _scanComp = NULL; - _numScanComp = 0; - _currentComp = NULL; - - // Free the quantization tables - for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) { - delete[] _quant[i]; - _quant[i] = NULL; - } - - // Free the Huffman tables - for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) { - _huff[i].count = 0; - delete[] _huff[i].values; _huff[i].values = NULL; - delete[] _huff[i].sizes; _huff[i].sizes = NULL; - delete[] _huff[i].codes; _huff[i].codes = NULL; - } - if (_rgbSurface) { _rgbSurface->free(); delete _rgbSurface; } -} - -bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { - // Reset member variables and tables from previous reads - destroy(); - - // Save the input stream - _stream = &stream; - - bool ok = true; - bool done = false; - while (!_stream->eos() && ok && !done) { - // Read the marker - - // WORKAROUND: While each and every JPEG file should end with - // an EOI (end of image) tag, in reality this may not be the - // case. For instance, at least one image in the Masterpiece - // edition of Myst doesn't, yet other programs are able to read - // the image without complaining. - // - // Apparently, the customary workaround is to insert a fake - // EOI tag. - - uint16 marker = _stream->readByte(); - bool fakeEOI = false; - - if (_stream->eos()) { - fakeEOI = true; - marker = 0xFF; - } - - if (marker != 0xFF) { - error("JPEG: Invalid marker[0]: 0x%02X", marker); - ok = false; - break; - } - - while (marker == 0xFF && !_stream->eos()) - marker = _stream->readByte(); - - if (_stream->eos()) { - fakeEOI = true; - marker = 0xD9; - } - - if (fakeEOI) - warning("JPEG: Inserted fake EOI"); - - // Process the marker data - switch (marker) { - case 0xC0: // Start Of Frame - ok = readSOF0(); - break; - case 0xC4: // Define Huffman Tables - ok = readDHT(); - break; - case 0xD8: // Start Of Image - break; - case 0xD9: // End Of Image - done = true; - break; - case 0xDA: // Start Of Scan - ok = readSOS(); - break; - case 0xDB: // Define Quantization Tables - ok = readDQT(); - break; - case 0xE0: // JFIF/JFXX segment - ok = readJFIF(); - break; - case 0xDD: // Define Restart Interval - ok = readDRI(); - break; - case 0xFE: // Comment - _stream->seek(_stream->readUint16BE() - 2, SEEK_CUR); - break; - default: { // Unknown marker - uint16 size = _stream->readUint16BE(); - - if ((marker & 0xE0) != 0xE0) - warning("JPEG: Unknown marker %02X, skipping %d bytes", marker, size - 2); - - _stream->seek(size - 2, SEEK_CUR); - } - } - } - - _stream = 0; - return ok; -} - -bool JPEGDecoder::readJFIF() { - uint16 length = _stream->readUint16BE(); - uint32 tag = _stream->readUint32BE(); - - if (tag != MKTAG('J', 'F', 'I', 'F')) { - warning("JPEGDecoder::readJFIF() tag mismatch"); - return false; - } - - if (_stream->readByte() != 0) { // NULL - warning("JPEGDecoder::readJFIF() NULL mismatch"); - return false; - } - - byte majorVersion = _stream->readByte(); - byte minorVersion = _stream->readByte(); - if (majorVersion != 1 || minorVersion > 2) - warning("JPEGDecoder::readJFIF(): v%d.%02d JPEGs may not be handled correctly", majorVersion, minorVersion); - - /* byte densityUnits = */_stream->readByte(); - /* uint16 xDensity = */_stream->readUint16BE(); - /* uint16 yDensity = */_stream->readUint16BE(); - byte thumbW = _stream->readByte(); - byte thumbH = _stream->readByte(); - - _stream->seek(thumbW * thumbH * 3, SEEK_CUR); // Ignore thumbnail - if (length != (thumbW * thumbH * 3) + 16) { - warning("JPEGDecoder::readJFIF() length mismatch"); - return false; - } - return true; + _yComponent.free(); + _uComponent.free(); + _vComponent.free(); } -// Marker 0xC0 (Start Of Frame, Baseline DCT) -bool JPEGDecoder::readSOF0() { - debug(5, "JPEG: readSOF0"); - uint16 size = _stream->readUint16BE(); - - // Read the sample precision - uint8 precision = _stream->readByte(); - if (precision != 8) { - warning("JPEG: Just 8 bit precision supported at the moment"); - return false; - } - - // Image size - _h = _stream->readUint16BE(); - _w = _stream->readUint16BE(); +#ifdef USE_JPEG +namespace { - // Number of components - _numComp = _stream->readByte(); - if (size != 8 + 3 * _numComp) { - warning("JPEG: Invalid number of components"); - return false; - } - - // Allocate the new components - delete[] _components; - _components = new Component[_numComp]; - - // Read the components details - for (int c = 0; c < _numComp; c++) { - _components[c].id = _stream->readByte(); - _components[c].factorH = _stream->readByte(); - _components[c].factorV = _components[c].factorH & 0xF; - _components[c].factorH >>= 4; - _components[c].quantTableSelector = _stream->readByte(); - } +#define JPEG_BUFFER_SIZE 4096 - return true; -} - -// Marker 0xC4 (Define Huffman Tables) -bool JPEGDecoder::readDHT() { - debug(5, "JPEG: readDHT"); - uint16 size = _stream->readUint16BE() - 2; - uint32 pos = _stream->pos(); - - while ((uint32)_stream->pos() < (size + pos)) { - // Read the table type and id - uint8 tableId = _stream->readByte(); - uint8 tableType = tableId >> 4; // type 0: DC, 1: AC - tableId &= 0xF; - uint8 tableNum = (tableId << 1) + tableType; - - // Free the Huffman table - delete[] _huff[tableNum].values; _huff[tableNum].values = NULL; - delete[] _huff[tableNum].sizes; _huff[tableNum].sizes = NULL; - delete[] _huff[tableNum].codes; _huff[tableNum].codes = NULL; - - // Read the number of values for each length - uint8 numValues[16]; - _huff[tableNum].count = 0; - for (int len = 0; len < 16; len++) { - numValues[len] = _stream->readByte(); - _huff[tableNum].count += numValues[len]; - } - - // Allocate memory for the current table - _huff[tableNum].values = new uint8[_huff[tableNum].count]; - _huff[tableNum].sizes = new uint8[_huff[tableNum].count]; - _huff[tableNum].codes = new uint16[_huff[tableNum].count]; - - // Read the table contents - int cur = 0; - for (int len = 0; len < 16; len++) { - for (int i = 0; i < numValues[len]; i++) { - _huff[tableNum].values[cur] = _stream->readByte(); - _huff[tableNum].sizes[cur] = len + 1; - cur++; - } - } - - // Fill the table of Huffman codes - cur = 0; - uint16 curCode = 0; - uint8 curCodeSize = _huff[tableNum].sizes[0]; - while (cur < _huff[tableNum].count) { - // Increase the code size to fit the request - while (_huff[tableNum].sizes[cur] != curCodeSize) { - curCode <<= 1; - curCodeSize++; - } - - // Assign the current code - _huff[tableNum].codes[cur] = curCode; - curCode++; - cur++; - } - } +struct StreamSource : public jpeg_source_mgr { + Common::SeekableReadStream *stream; + bool startOfFile; + JOCTET buffer[JPEG_BUFFER_SIZE]; +}; - return true; +void initSource(j_decompress_ptr cinfo) { + StreamSource *source = (StreamSource *)cinfo->src; + source->startOfFile = true; } -// Marker 0xDA (Start Of Scan) -bool JPEGDecoder::readSOS() { - debug(5, "JPEG: readSOS"); - uint16 size = _stream->readUint16BE(); - - // Number of scan components - _numScanComp = _stream->readByte(); - if (size != 6 + 2 * _numScanComp) { - warning("JPEG: Invalid number of components"); - return false; - } - - // Allocate the new scan components - delete[] _scanComp; - _scanComp = new Component *[_numScanComp]; - - // Reset the maximum sampling factors - _maxFactorV = 0; - _maxFactorH = 0; - - // Component-specification parameters - for (int c = 0; c < _numScanComp; c++) { - // Read the desired component id - uint8 id = _stream->readByte(); - - // Search the component with the specified id - bool found = false; - for (int i = 0; !found && i < _numComp; i++) { - if (_components[i].id == id) { - // We found the desired component - found = true; - - // Assign the found component to the c'th scan component - _scanComp[c] = &_components[i]; - } - } +boolean fillInputBuffer(j_decompress_ptr cinfo) { + StreamSource *source = (StreamSource *)cinfo->src; - if (!found) { - warning("JPEG: Invalid component"); - return false; + uint32 bufferSize = source->stream->read((byte *)source->buffer, sizeof(source->buffer)); + if (bufferSize == 0) { + if (source->startOfFile) { + // An empty file is a fatal error + ERREXIT(cinfo, JERR_INPUT_EMPTY); + } else { + // Otherwise we insert an EOF marker + WARNMS(cinfo, JWRN_JPEG_EOF); + source->buffer[0] = (JOCTET)0xFF; + source->buffer[1] = (JOCTET)JPEG_EOI; + bufferSize = 2; } - - // Read the entropy table selectors - _scanComp[c]->DCentropyTableSelector = _stream->readByte(); - _scanComp[c]->ACentropyTableSelector = _scanComp[c]->DCentropyTableSelector & 0xF; - _scanComp[c]->DCentropyTableSelector >>= 4; - - // Calculate the maximum sampling factors - if (_scanComp[c]->factorV > _maxFactorV) - _maxFactorV = _scanComp[c]->factorV; - - if (_scanComp[c]->factorH > _maxFactorH) - _maxFactorH = _scanComp[c]->factorH; - - // Initialize the DC predictor - _scanComp[c]->DCpredictor = 0; } - // Start of spectral selection - if (_stream->readByte() != 0) { - warning("JPEG: Progressive scanning not supported"); - return false; - } + source->next_input_byte = source->buffer; + source->bytes_in_buffer = bufferSize; + source->startOfFile = false; - // End of spectral selection - if (_stream->readByte() != 63) { - warning("JPEG: Progressive scanning not supported"); - return false; - } - - // Successive approximation parameters - if (_stream->readByte() != 0) { - warning("JPEG: Progressive scanning not supported"); - return false; - } - - // Entropy coded sequence starts, initialize Huffman decoder - _bitsNumber = 0; - - // Read all the scan MCUs - uint16 xMCU = _w / (_maxFactorH * 8); - uint16 yMCU = _h / (_maxFactorV * 8); - - // Check for non- multiple-of-8 dimensions - if (_w % (_maxFactorH * 8) != 0) - xMCU++; - if (_h % (_maxFactorV * 8) != 0) - yMCU++; - - // Initialize the scan surfaces - for (uint16 c = 0; c < _numScanComp; c++) { - _scanComp[c]->surface.create(xMCU * _maxFactorH * 8, yMCU * _maxFactorV * 8, PixelFormat::createFormatCLUT8()); - } - - bool ok = true; - uint16 interval = _restartInterval; + return TRUE; +} - for (int y = 0; ok && (y < yMCU); y++) { - for (int x = 0; ok && (x < xMCU); x++) { - ok = readMCU(x, y); +void skipInputData(j_decompress_ptr cinfo, long numBytes) { + StreamSource *source = (StreamSource *)cinfo->src; - // If we have a restart interval, we'll need to reset a couple - // variables - if (_restartInterval != 0) { - interval--; + if (numBytes > 0) { + if (numBytes > (long)source->bytes_in_buffer) { + // In case we need to skip more bytes than there are in the buffer + // we will skip the remaining data and fill the buffer again + numBytes -= (long)source->bytes_in_buffer; - if (interval == 0) { - interval = _restartInterval; - _bitsNumber = 0; + // Skip the remaining bytes + source->stream->skip(numBytes); - for (byte i = 0; i < _numScanComp; i++) - _scanComp[i]->DCpredictor = 0; - } - } + // Fill up the buffer again + (*source->fill_input_buffer)(cinfo); + } else { + source->next_input_byte += (size_t)numBytes; + source->bytes_in_buffer -= (size_t)numBytes; } - } - // Trim Component surfaces back to image height and width - // Note: Code using jpeg must use surface.pitch correctly... - for (uint16 c = 0; c < _numScanComp; c++) { - _scanComp[c]->surface.w = _w; - _scanComp[c]->surface.h = _h; } - - return ok; } -// Marker 0xDB (Define Quantization Tables) -bool JPEGDecoder::readDQT() { - debug(5, "JPEG: readDQT"); - uint16 size = _stream->readUint16BE() - 2; - uint32 pos = _stream->pos(); - - while ((uint32)_stream->pos() < (pos + size)) { - // Read the table precision and id - uint8 tableId = _stream->readByte(); - bool highPrecision = (tableId & 0xF0) != 0; - - // Validate the table id - tableId &= 0xF; - if (tableId >= JPEG_MAX_QUANT_TABLES) { - warning("JPEG: Invalid quantization table"); - return false; - } - - // Create the new table if necessary - if (!_quant[tableId]) - _quant[tableId] = new uint16[64]; - - // Read the table (stored in Zig-Zag order) - for (int i = 0; i < 64; i++) - _quant[tableId][i] = highPrecision ? _stream->readUint16BE() : _stream->readByte(); - } - - return true; +void termSource(j_decompress_ptr cinfo) { } -// Marker 0xDD (Define Restart Interval) -bool JPEGDecoder::readDRI() { - debug(5, "JPEG: readDRI"); - uint16 size = _stream->readUint16BE() - 2; +void jpeg_scummvm_src(j_decompress_ptr cinfo, Common::SeekableReadStream *stream) { + StreamSource *source; - if (size != 2) { - warning("JPEG: Invalid DRI size %d", size); - return false; + // Initialize the source in case it has not been done yet. + if (cinfo->src == NULL) { + cinfo->src = (jpeg_source_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(StreamSource)); } - _restartInterval = _stream->readUint16BE(); - debug(5, "Restart interval: %d", _restartInterval); - return true; -} - -bool JPEGDecoder::readMCU(uint16 xMCU, uint16 yMCU) { - bool ok = true; - for (int c = 0; ok && (c < _numComp); c++) { - // Set the current component - _currentComp = _scanComp[c]; + source = (StreamSource *)cinfo->src; + source->init_source = &initSource; + source->fill_input_buffer = &fillInputBuffer; + source->skip_input_data = &skipInputData; + source->resync_to_restart = &jpeg_resync_to_restart; + source->term_source = &termSource; + source->bytes_in_buffer = 0; + source->next_input_byte = NULL; - // Read the data units of the current component - for (int y = 0; ok && (y < _scanComp[c]->factorV); y++) - for (int x = 0; ok && (x < _scanComp[c]->factorH); x++) - ok = readDataUnit(xMCU * _scanComp[c]->factorH + x, yMCU * _scanComp[c]->factorV + y); - } + source->stream = stream; +} - return ok; +void errorExit(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + // This function is not allowed to return to the caller, thus we simply + // error out with our error handling here. + error("%s", buffer); } -// triple-butterfly-add (and possible rounding) -#define xadd3(xa, xb, xc, xd, h) \ - p = xa + xb; \ - n = xa - xb; \ - xa = p + xc + h; \ - xb = n + xd + h; \ - xc = p - xc + h; \ - xd = n - xd + h; - -// butterfly-mul -#define xmul(xa, xb, k1, k2, sh) \ - n = k1 * (xa + xb); \ - p = xa; \ - xa = (n + (k2 - k1) * xb) >> sh; \ - xb = (n - (k2 + k1) * p) >> sh; - -// IDCT based on public domain code from http://halicery.com/jpeg/idct.html -void JPEGDecoder::idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half) { - int p, n; - - src[0] <<= 9; - src[1] <<= 7; - src[3] *= 181; - src[4] <<= 9; - src[5] *= 181; - src[7] <<= 7; - - // Even part - xmul(src[6], src[2], 277, 669, 0) - xadd3(src[0], src[4], src[6], src[2], half) - - // Odd part - xadd3(src[1], src[7], src[3], src[5], 0) - xmul(src[5], src[3], 251, 50, 6) - xmul(src[1], src[7], 213, 142, 6) - - dest[0 * 8] = (src[0] + src[1]) >> ps; - dest[1 * 8] = (src[4] + src[5]) >> ps; - dest[2 * 8] = (src[2] + src[3]) >> ps; - dest[3 * 8] = (src[6] + src[7]) >> ps; - dest[4 * 8] = (src[6] - src[7]) >> ps; - dest[5 * 8] = (src[2] - src[3]) >> ps; - dest[6 * 8] = (src[4] - src[5]) >> ps; - dest[7 * 8] = (src[0] - src[1]) >> ps; +void outputMessage(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + // Is using debug here a good idea? Or do we want to ignore all libjpeg + // messages? + debug(3, "libjpeg: %s", buffer); } -void JPEGDecoder::idct2D8x8(int32 block[64]) { - int32 tmp[64]; - - // Apply 1D IDCT to rows - for (int i = 0; i < 8; i++) - idct1D8x8(&block[i * 8], &tmp[i], 9, 1 << 8); - - // Apply 1D IDCT to columns - for (int i = 0; i < 8; i++) - idct1D8x8(&tmp[i * 8], &block[i], 12, 1 << 11); - } - -bool JPEGDecoder::readDataUnit(uint16 x, uint16 y) { - // Prepare an empty data array - int16 readData[64]; - for (int i = 1; i < 64; i++) - readData[i] = 0; - - // Read the DC component - readData[0] = _currentComp->DCpredictor + readDC(); - _currentComp->DCpredictor = readData[0]; - - // Read the AC components (stored in Zig-Zag) - readAC(readData); - - // Calculate the DCT coefficients from the input sequence - int32 block[64]; - for (uint8 i = 0; i < 64; i++) { - // Dequantize - int32 val = readData[i]; - int16 quant = _quant[_currentComp->quantTableSelector][i]; - val *= quant; - - // Store the normalized coefficients, undoing the Zig-Zag - block[_zigZagOrder[i]] = val; - } +} // End of anonymous namespace +#endif - // Apply the IDCT - idct2D8x8(block); +bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { +#ifdef USE_JPEG + // Reset member variables from previous decodings + destroy(); - // Level shift to make the values unsigned - for (int i = 0; i < 64; i++) { - block[i] = block[i] + 128; + jpeg_decompress_struct cinfo; + jpeg_error_mgr jerr; - if (block[i] < 0) - block[i] = 0; + // Initialize error handling callbacks + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = &errorExit; + cinfo.err->output_message = &outputMessage; - if (block[i] > 255) - block[i] = 255; - } + // Initialize the decompression structure + jpeg_create_decompress(&cinfo); - // Paint the component surface - uint8 scalingV = _maxFactorV / _currentComp->factorV; - uint8 scalingH = _maxFactorH / _currentComp->factorH; - - // Convert coordinates from MCU blocks to pixels - x <<= 3; - y <<= 3; - - for (uint8 j = 0; j < 8; j++) { - for (uint16 sV = 0; sV < scalingV; sV++) { - // Get the beginning of the block line - byte *ptr = (byte *)_currentComp->surface.getBasePtr(x * scalingH, (y + j) * scalingV + sV); - - for (uint8 i = 0; i < 8; i++) { - for (uint16 sH = 0; sH < scalingH; sH++) { - *ptr = (byte)(block[j * 8 + i]); - ptr++; - } - } - } - } + // Initialize our buffer handling + jpeg_scummvm_src(&cinfo, &stream); - return true; -} + // Read the file header + jpeg_read_header(&cinfo, TRUE); -int16 JPEGDecoder::readDC() { - // DC is type 0 - uint8 tableNum = _currentComp->DCentropyTableSelector << 1; + // We request YUV output because Groovie requires it + cinfo.out_color_space = JCS_YCbCr; - // Get the number of bits to read - uint8 numBits = readHuff(tableNum); + // Actually start decompressing the image + jpeg_start_decompress(&cinfo); - // Read the requested bits - return readSignedBits(numBits); -} + // Allocate buffers for the YUV components + _yComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); + _uComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); + _vComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); -void JPEGDecoder::readAC(int16 *out) { - // AC is type 1 - uint8 tableNum = (_currentComp->ACentropyTableSelector << 1) + 1; - - // Start reading AC element 1 - uint8 cur = 1; - while (cur < 64) { - uint8 s = readHuff(tableNum); - uint8 r = s >> 4; - s &= 0xF; - - if (s == 0) { - if (r == 15) { - // Skip 16 values - cur += 16; - } else { - // EOB: end of block - cur = 64; - } - } else { - // Skip r values - cur += r; + // Allocate buffer for one scanline + JDIMENSION pitch = cinfo.output_width * cinfo.output_components; + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, pitch, 1); - // Read the next value - out[cur] = readSignedBits(s); - cur++; - } - } -} + // Go through the image data scanline by scanline + while (cinfo.output_scanline < cinfo.output_height) { + byte *yPtr = (byte *)_yComponent.getBasePtr(0, cinfo.output_scanline); + byte *uPtr = (byte *)_uComponent.getBasePtr(0, cinfo.output_scanline); + byte *vPtr = (byte *)_vComponent.getBasePtr(0, cinfo.output_scanline); -int16 JPEGDecoder::readSignedBits(uint8 numBits) { - uint16 ret = 0; - if (numBits > 16) - error("requested %d bits", numBits); //XXX + jpeg_read_scanlines(&cinfo, buffer, 1); - // MSB=0 for negatives, 1 for positives - for (int i = 0; i < numBits; i++) - ret = (ret << 1) + readBit(); - - // Extend sign bits (PAG109) - if (!(ret >> (numBits - 1))) { - uint16 tmp = ((uint16)-1 << numBits) + 1; - ret = ret + tmp; - } - return ret; -} - -// TODO: optimize? -uint8 JPEGDecoder::readHuff(uint8 table) { - bool foundCode = false; - uint8 val = 0; - - uint8 cur = 0; - uint8 codeSize = 1; - uint16 code = readBit(); - while (!foundCode) { - // Prepare a code of the current size - while (codeSize < _huff[table].sizes[cur]) { - code = (code << 1) + readBit(); - codeSize++; - } - - // Compare the codes of the current size - while (!foundCode && (codeSize == _huff[table].sizes[cur])) { - if (code == _huff[table].codes[cur]) { - // Found the code - val = _huff[table].values[cur]; - foundCode = true; - } else { - // Continue reading - cur++; - } - } - } - - return val; -} - -uint8 JPEGDecoder::readBit() { - // Read a whole byte if necessary - if (_bitsNumber == 0) { - _bitsData = _stream->readByte(); - _bitsNumber = 8; - - // Detect markers - if (_bitsData == 0xFF) { - uint8 byte2 = _stream->readByte(); - - // A stuffed 0 validates the previous byte - if (byte2 != 0) { - if (byte2 == 0xDC) { - // DNL marker: Define Number of Lines - // TODO: terminate scan - warning("DNL marker detected: terminate scan"); - } else if (byte2 >= 0xD0 && byte2 <= 0xD7) { - debug(7, "RST%d marker detected", byte2 & 7); - _bitsData = _stream->readByte(); - } else { - warning("Error: marker 0x%02X read in entropy data", byte2); - } - } + const byte *src = buffer[0]; + for (int remaining = cinfo.output_width; remaining > 0; --remaining) { + *yPtr++ = *src++; + *uPtr++ = *src++; + *vPtr++ = *src++; } } - _bitsNumber--; - return (_bitsData & (1 << _bitsNumber)) ? 1 : 0; -} - -const Surface *JPEGDecoder::getComponent(uint c) const { - for (int i = 0; i < _numComp; i++) - if (_components[i].id == c) // We found the desired component - return &_components[i].surface; + // We are done with decompressing, thus free all the data + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); - error("JPEGDecoder::getComponent: No component %d present", c); - return NULL; + return true; +#else + return false; +#endif } } // End of Graphics namespace diff --git a/graphics/decoders/jpeg.h b/graphics/decoders/jpeg.h index d59b72adf4..53e7bb10c8 100644 --- a/graphics/decoders/jpeg.h +++ b/graphics/decoders/jpeg.h @@ -40,11 +40,6 @@ class SeekableReadStream; namespace Graphics { -struct PixelFormat; - -#define JPEG_MAX_QUANT_TABLES 4 -#define JPEG_MAX_HUFF_TABLES 2 - class JPEGDecoder : public ImageDecoder { public: JPEGDecoder(); @@ -55,85 +50,19 @@ public: bool loadStream(Common::SeekableReadStream &str); const Surface *getSurface() const; - bool isLoaded() const { return _numComp && _w && _h; } - uint16 getWidth() const { return _w; } - uint16 getHeight() const { return _h; } - const Surface *getComponent(uint c) const; + const Surface &getYComponent() const { return _yComponent; } + const Surface &getUComponent() const { return _uComponent; } + const Surface &getVComponent() const { return _vComponent; } private: - Common::SeekableReadStream *_stream; - uint16 _w, _h; - uint16 _restartInterval; - // mutable so that we can convert to RGB only during // a getSurface() call while still upholding the // const requirement in other ImageDecoders mutable Graphics::Surface *_rgbSurface; - // Image components - uint8 _numComp; - struct Component { - // Global values - uint8 id; - uint8 factorH; - uint8 factorV; - uint8 quantTableSelector; - - // Scan specific values - uint8 DCentropyTableSelector; - uint8 ACentropyTableSelector; - int16 DCpredictor; - - // Result image for this component - Surface surface; - }; - - Component *_components; - - // Scan components - uint8 _numScanComp; - Component **_scanComp; - Component *_currentComp; - - // Maximum sampling factors, used to calculate the interleaving of the MCU - uint8 _maxFactorV; - uint8 _maxFactorH; - - // Quantization tables - uint16 *_quant[JPEG_MAX_QUANT_TABLES]; - - // Huffman tables - struct HuffmanTable { - uint8 count; - uint8 *values; - uint8 *sizes; - uint16 *codes; - } _huff[2 * JPEG_MAX_HUFF_TABLES]; - - // Marker read functions - bool readJFIF(); - bool readSOF0(); - bool readDHT(); - bool readSOS(); - bool readDQT(); - bool readDRI(); - - // Helper functions - bool readMCU(uint16 xMCU, uint16 yMCU); - bool readDataUnit(uint16 x, uint16 y); - int16 readDC(); - void readAC(int16 *out); - int16 readSignedBits(uint8 numBits); - - // Huffman decoding - uint8 readHuff(uint8 table); - uint8 readBit(); - uint8 _bitsData; - uint8 _bitsNumber; - - // Inverse Discrete Cosine Transformation - static void idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half); - static void idct2D8x8(int32 block[64]); + Graphics::Surface _yComponent; + Graphics::Surface _uComponent; + Graphics::Surface _vComponent; }; } // End of Graphics namespace -- cgit v1.2.3 From 4809294b43e1c43957874bdfcdadfc299fd7ace4 Mon Sep 17 00:00:00 2001 From: Johannes Schickel Date: Mon, 12 Aug 2013 00:53:34 +0200 Subject: GRAPHICS: Make JPEGDecoder request RGB output from libjpeg by default. This fixes loading of JPEG files which contain RGB color space instead of YUV. It is a pretty odd extension of JPEG files by Adobe which is indicated by this: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe To still support Groovie's need for YUV data I added some possibility to request direct YUV output. --- graphics/decoders/jpeg.cpp | 88 +++++++++++++++++++++++++++------------------- graphics/decoders/jpeg.h | 53 ++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 51 deletions(-) (limited to 'graphics') diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp index 891542c415..ba6292bbbd 100644 --- a/graphics/decoders/jpeg.cpp +++ b/graphics/decoders/jpeg.cpp @@ -25,7 +25,6 @@ #define FORBIDDEN_SYMBOL_ALLOW_ALL #include "graphics/pixelformat.h" -#include "graphics/yuv_to_rgb.h" #include "graphics/decoders/jpeg.h" #include "common/debug.h" @@ -45,7 +44,7 @@ extern "C" { namespace Graphics { -JPEGDecoder::JPEGDecoder() : ImageDecoder(), _rgbSurface(nullptr) { +JPEGDecoder::JPEGDecoder() : ImageDecoder(), _surface(), _colorSpace(kColorSpaceRGBA) { } JPEGDecoder::~JPEGDecoder() { @@ -53,31 +52,11 @@ JPEGDecoder::~JPEGDecoder() { } const Surface *JPEGDecoder::getSurface() const { - // Make sure we have loaded data - if (!_yComponent.getPixels()) - return 0; - - if (_rgbSurface) - return _rgbSurface; - - // Create an RGBA8888 surface - _rgbSurface = new Graphics::Surface(); - _rgbSurface->create(_yComponent.w, _yComponent.h, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0)); - - YUVToRGBMan.convert444(_rgbSurface, Graphics::YUVToRGBManager::kScaleFull, (const byte *)_yComponent.getPixels(), (const byte *)_uComponent.getPixels(), (const byte *)_vComponent.getPixels(), _yComponent.w, _yComponent.h, _yComponent.pitch, _uComponent.pitch); - - return _rgbSurface; + return &_surface; } void JPEGDecoder::destroy() { - if (_rgbSurface) { - _rgbSurface->free(); - delete _rgbSurface; - } - - _yComponent.free(); - _uComponent.free(); - _vComponent.free(); + _surface.free(); } #ifdef USE_JPEG @@ -206,16 +185,33 @@ bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { // Read the file header jpeg_read_header(&cinfo, TRUE); - // We request YUV output because Groovie requires it - cinfo.out_color_space = JCS_YCbCr; + // We can request YUV output because Groovie requires it + switch (_colorSpace) { + case kColorSpaceRGBA: + cinfo.out_color_space = JCS_RGB; + break; + + case kColorSpaceYUV: + cinfo.out_color_space = JCS_YCbCr; + break; + } // Actually start decompressing the image jpeg_start_decompress(&cinfo); - // Allocate buffers for the YUV components - _yComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); - _uComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); - _vComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); + // Allocate buffers for the output data + switch (_colorSpace) { + case kColorSpaceRGBA: + // We use RGBA8888 in this scenario + _surface.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0)); + break; + + case kColorSpaceYUV: + // We use YUV with 3 bytes per pixel otherwise. + // This is pretty ugly since our PixelFormat cannot express YUV... + _surface.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat(3, 0, 0, 0, 0, 0, 0, 0, 0)); + break; + } // Allocate buffer for one scanline JDIMENSION pitch = cinfo.output_width * cinfo.output_components; @@ -223,17 +219,35 @@ bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { // Go through the image data scanline by scanline while (cinfo.output_scanline < cinfo.output_height) { - byte *yPtr = (byte *)_yComponent.getBasePtr(0, cinfo.output_scanline); - byte *uPtr = (byte *)_uComponent.getBasePtr(0, cinfo.output_scanline); - byte *vPtr = (byte *)_vComponent.getBasePtr(0, cinfo.output_scanline); + byte *dst = (byte *)_surface.getBasePtr(0, cinfo.output_scanline); jpeg_read_scanlines(&cinfo, buffer, 1); const byte *src = buffer[0]; - for (int remaining = cinfo.output_width; remaining > 0; --remaining) { - *yPtr++ = *src++; - *uPtr++ = *src++; - *vPtr++ = *src++; + switch (_colorSpace) { + case kColorSpaceRGBA: { + for (int remaining = cinfo.output_width; remaining > 0; --remaining) { + byte r = *src++; + byte g = *src++; + byte b = *src++; + // We need to insert a alpha value of 255 (opaque) here. +#ifdef SCUMM_BIG_ENDIAN + *dst++ = r; + *dst++ = g; + *dst++ = b; + *dst++ = 0xFF; +#else + *dst++ = 0xFF; + *dst++ = b; + *dst++ = g; + *dst++ = r; +#endif + } + } break; + + case kColorSpaceYUV: + memcpy(dst, src, _surface.pitch); + break; } } diff --git a/graphics/decoders/jpeg.h b/graphics/decoders/jpeg.h index 53e7bb10c8..8460bc2698 100644 --- a/graphics/decoders/jpeg.h +++ b/graphics/decoders/jpeg.h @@ -46,23 +46,48 @@ public: ~JPEGDecoder(); // ImageDecoder API - void destroy(); - bool loadStream(Common::SeekableReadStream &str); - const Surface *getSurface() const; + virtual void destroy(); + virtual bool loadStream(Common::SeekableReadStream &str); + virtual const Surface *getSurface() const; - const Surface &getYComponent() const { return _yComponent; } - const Surface &getUComponent() const { return _uComponent; } - const Surface &getVComponent() const { return _vComponent; } + // Special API for JPEG + enum ColorSpace { + /** + * Output 32bit RGBA data. + * + * This is the default output. + */ + kColorSpaceRGBA, -private: - // mutable so that we can convert to RGB only during - // a getSurface() call while still upholding the - // const requirement in other ImageDecoders - mutable Graphics::Surface *_rgbSurface; + /** + * Output (interleaved) YUV data. + * + * Be aware that some images cannot be output in YUV mode. + * These are (non-standard) JPEG images which are in RGB colorspace. + * + * The resulting Surface will have a PixelFormat with 3 bytes per + * pixel and the remaining entries are completely zeroed. This works + * around the fact that PixelFormat can only describe RGB formats. + * + * You should only use this when you are really aware of what you are + * doing! + */ + kColorSpaceYUV + }; + + /** + * Request the output color space. This can be used to obtain raw YUV + * data from the JPEG file. But this might not work for all files! + * + * The decoder itself defaults to RGBA. + * + * @param outSpace The color space to output. + */ + void setOutputColorSpace(ColorSpace outSpace) { _colorSpace = outSpace; } - Graphics::Surface _yComponent; - Graphics::Surface _uComponent; - Graphics::Surface _vComponent; +private: + Graphics::Surface _surface; + ColorSpace _colorSpace; }; } // End of Graphics namespace -- cgit v1.2.3 From 4063de40705c2b21e092f2a275182b0f41070b15 Mon Sep 17 00:00:00 2001 From: Johannes Schickel Date: Sun, 18 Aug 2013 18:21:19 +0200 Subject: GRAPHICS: Add some paranoia asserts in JPEGDecoder. --- graphics/decoders/jpeg.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'graphics') diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp index ba6292bbbd..c858884095 100644 --- a/graphics/decoders/jpeg.cpp +++ b/graphics/decoders/jpeg.cpp @@ -214,7 +214,9 @@ bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { } // Allocate buffer for one scanline + assert(cinfo.output_components == 3); JDIMENSION pitch = cinfo.output_width * cinfo.output_components; + assert(_surface.pitch >= pitch); JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, pitch, 1); // Go through the image data scanline by scanline @@ -246,7 +248,7 @@ bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { } break; case kColorSpaceYUV: - memcpy(dst, src, _surface.pitch); + memcpy(dst, src, pitch); break; } } -- cgit v1.2.3