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.
This commit is contained in:
Johannes Schickel 2013-08-12 00:53:34 +02:00
parent ac66cc9219
commit 4809294b43
3 changed files with 94 additions and 59 deletions

View file

@ -435,17 +435,13 @@ bool ROQPlayer::processBlockStill(ROQBlockHeader &blockHeader) {
warning("Groovie::ROQ: JPEG frame (unfinished)"); warning("Groovie::ROQ: JPEG frame (unfinished)");
Graphics::JPEGDecoder *jpg = new Graphics::JPEGDecoder(); Graphics::JPEGDecoder *jpg = new Graphics::JPEGDecoder();
jpg->setOutputColorSpace(Graphics::JPEGDecoder::kColorSpaceYUV);
jpg->loadStream(*_file); jpg->loadStream(*_file);
const byte *y = (const byte *)jpg->getYComponent().getPixels();
const byte *u = (const byte *)jpg->getUComponent().getPixels();
const byte *v = (const byte *)jpg->getVComponent().getPixels();
const Graphics::Surface *srcSurf = jpg->getSurface();
const byte *src = (const byte *)srcSurf->getPixels();
byte *ptr = (byte *)_currBuf->getPixels(); byte *ptr = (byte *)_currBuf->getPixels();
for (int i = 0; i < _currBuf->w * _currBuf->h; i++) { memcpy(ptr, src, _currBuf->w * _currBuf->h);
*ptr++ = *y++;
*ptr++ = *u++;
*ptr++ = *v++;
}
delete jpg; delete jpg;
return true; return true;

View file

@ -25,7 +25,6 @@
#define FORBIDDEN_SYMBOL_ALLOW_ALL #define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "graphics/pixelformat.h" #include "graphics/pixelformat.h"
#include "graphics/yuv_to_rgb.h"
#include "graphics/decoders/jpeg.h" #include "graphics/decoders/jpeg.h"
#include "common/debug.h" #include "common/debug.h"
@ -45,7 +44,7 @@ extern "C" {
namespace Graphics { namespace Graphics {
JPEGDecoder::JPEGDecoder() : ImageDecoder(), _rgbSurface(nullptr) { JPEGDecoder::JPEGDecoder() : ImageDecoder(), _surface(), _colorSpace(kColorSpaceRGBA) {
} }
JPEGDecoder::~JPEGDecoder() { JPEGDecoder::~JPEGDecoder() {
@ -53,31 +52,11 @@ JPEGDecoder::~JPEGDecoder() {
} }
const Surface *JPEGDecoder::getSurface() const { const Surface *JPEGDecoder::getSurface() const {
// Make sure we have loaded data return &_surface;
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;
} }
void JPEGDecoder::destroy() { void JPEGDecoder::destroy() {
if (_rgbSurface) { _surface.free();
_rgbSurface->free();
delete _rgbSurface;
}
_yComponent.free();
_uComponent.free();
_vComponent.free();
} }
#ifdef USE_JPEG #ifdef USE_JPEG
@ -206,16 +185,33 @@ bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) {
// Read the file header // Read the file header
jpeg_read_header(&cinfo, TRUE); jpeg_read_header(&cinfo, TRUE);
// We request YUV output because Groovie requires it // We can request YUV output because Groovie requires it
cinfo.out_color_space = JCS_YCbCr; 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 // Actually start decompressing the image
jpeg_start_decompress(&cinfo); jpeg_start_decompress(&cinfo);
// Allocate buffers for the YUV components // Allocate buffers for the output data
_yComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); switch (_colorSpace) {
_uComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); case kColorSpaceRGBA:
_vComponent.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat::createFormatCLUT8()); // 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 // Allocate buffer for one scanline
JDIMENSION pitch = cinfo.output_width * cinfo.output_components; 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 // Go through the image data scanline by scanline
while (cinfo.output_scanline < cinfo.output_height) { while (cinfo.output_scanline < cinfo.output_height) {
byte *yPtr = (byte *)_yComponent.getBasePtr(0, cinfo.output_scanline); byte *dst = (byte *)_surface.getBasePtr(0, cinfo.output_scanline);
byte *uPtr = (byte *)_uComponent.getBasePtr(0, cinfo.output_scanline);
byte *vPtr = (byte *)_vComponent.getBasePtr(0, cinfo.output_scanline);
jpeg_read_scanlines(&cinfo, buffer, 1); jpeg_read_scanlines(&cinfo, buffer, 1);
const byte *src = buffer[0]; const byte *src = buffer[0];
for (int remaining = cinfo.output_width; remaining > 0; --remaining) { switch (_colorSpace) {
*yPtr++ = *src++; case kColorSpaceRGBA: {
*uPtr++ = *src++; for (int remaining = cinfo.output_width; remaining > 0; --remaining) {
*vPtr++ = *src++; 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;
} }
} }

View file

@ -46,23 +46,48 @@ public:
~JPEGDecoder(); ~JPEGDecoder();
// ImageDecoder API // ImageDecoder API
void destroy(); virtual void destroy();
bool loadStream(Common::SeekableReadStream &str); virtual bool loadStream(Common::SeekableReadStream &str);
const Surface *getSurface() const; virtual const Surface *getSurface() const;
const Surface &getYComponent() const { return _yComponent; } // Special API for JPEG
const Surface &getUComponent() const { return _uComponent; } enum ColorSpace {
const Surface &getVComponent() const { return _vComponent; } /**
* Output 32bit RGBA data.
*
* This is the default output.
*/
kColorSpaceRGBA,
/**
* 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; }
private: private:
// mutable so that we can convert to RGB only during Graphics::Surface _surface;
// a getSurface() call while still upholding the ColorSpace _colorSpace;
// const requirement in other ImageDecoders
mutable Graphics::Surface *_rgbSurface;
Graphics::Surface _yComponent;
Graphics::Surface _uComponent;
Graphics::Surface _vComponent;
}; };
} // End of Graphics namespace } // End of Graphics namespace