mirror of
https://github.com/scummvm/scummvm.git
synced 2026-05-21 05:40:43 +00:00
IMAGE: Split the generic QuickTime dithering into a separate class
This commit is contained in:
committed by
Eugene Sandulenko
parent
1e62c54dcd
commit
9bbb019950
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "image/codecs/cinepak.h"
|
||||
#include "image/codecs/cinepak_tables.h"
|
||||
#include "image/codecs/dither.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/stream.h"
|
||||
@@ -680,7 +681,7 @@ void CinepakDecoder::setDither(DitherType type, const byte *palette) {
|
||||
} else {
|
||||
// Generate QuickTime dither table
|
||||
// 4 blocks of 0x4000 bytes (RGB554 lookup)
|
||||
_colorMap = createQuickTimeDitherTable(palette, 256);
|
||||
_colorMap = DitherCodec::createQuickTimeDitherTable(palette, 256);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/list.h"
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#include "image/codecs/codec.h"
|
||||
|
||||
#include "image/jpeg.h"
|
||||
@@ -55,152 +52,6 @@
|
||||
|
||||
namespace Image {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Add a color to the QuickTime dither table check queue if it hasn't already been found.
|
||||
*/
|
||||
inline void addColorToQueue(uint16 color, uint16 index, byte *checkBuffer, Common::List<uint16> &checkQueue) {
|
||||
if ((READ_UINT16(checkBuffer + color * 2) & 0xFF) == 0) {
|
||||
// Previously unfound color
|
||||
WRITE_UINT16(checkBuffer + color * 2, index);
|
||||
checkQueue.push_back(color);
|
||||
}
|
||||
}
|
||||
|
||||
inline byte adjustColorRange(byte currentColor, byte correctColor, byte palColor) {
|
||||
return CLIP<int>(currentColor - palColor + correctColor, 0, 255);
|
||||
}
|
||||
|
||||
inline uint16 makeQuickTimeDitherColor(byte r, byte g, byte b) {
|
||||
// RGB554
|
||||
return ((r & 0xF8) << 6) | ((g & 0xF8) << 1) | (b >> 4);
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
byte *Codec::createQuickTimeDitherTable(const byte *palette, uint colorCount) {
|
||||
byte *buf = new byte[0x10000]();
|
||||
|
||||
Common::List<uint16> checkQueue;
|
||||
|
||||
bool foundBlack = false;
|
||||
bool foundWhite = false;
|
||||
|
||||
const byte *palPtr = palette;
|
||||
|
||||
// See what colors we have, and add them to the queue to check
|
||||
for (uint i = 0; i < colorCount; i++) {
|
||||
byte r = *palPtr++;
|
||||
byte g = *palPtr++;
|
||||
byte b = *palPtr++;
|
||||
uint16 n = (i << 8) | 1;
|
||||
uint16 col = makeQuickTimeDitherColor(r, g, b);
|
||||
|
||||
if (col == 0) {
|
||||
// Special case for close-to-black
|
||||
// The original did more here, but it effectively discarded the value
|
||||
// due to a poor if-check (whole 16-bit value instead of lower 8-bits).
|
||||
WRITE_UINT16(buf, n);
|
||||
foundBlack = true;
|
||||
} else if (col == 0x3FFF) {
|
||||
// Special case for close-to-white
|
||||
// The original did more here, but it effectively discarded the value
|
||||
// due to a poor if-check (whole 16-bit value instead of lower 8-bits).
|
||||
WRITE_UINT16(buf + 0x7FFE, n);
|
||||
foundWhite = true;
|
||||
} else {
|
||||
// Previously unfound color
|
||||
addColorToQueue(col, n, buf, checkQueue);
|
||||
}
|
||||
}
|
||||
|
||||
// More special handling for white
|
||||
if (foundWhite)
|
||||
checkQueue.push_front(0x3FFF);
|
||||
|
||||
// More special handling for black
|
||||
if (foundBlack)
|
||||
checkQueue.push_front(0);
|
||||
|
||||
// Go through the list of colors we have and match up similar colors
|
||||
// to fill in the table as best as we can.
|
||||
while (!checkQueue.empty()) {
|
||||
uint16 col = checkQueue.front();
|
||||
checkQueue.pop_front();
|
||||
uint16 index = READ_UINT16(buf + col * 2);
|
||||
|
||||
uint32 x = col << 4;
|
||||
if ((x & 0xFF) < 0xF0)
|
||||
addColorToQueue((x + 0x10) >> 4, index, buf, checkQueue);
|
||||
if ((x & 0xFF) >= 0x10)
|
||||
addColorToQueue((x - 0x10) >> 4, index, buf, checkQueue);
|
||||
|
||||
uint32 y = col << 7;
|
||||
if ((y & 0xFF00) < 0xF800)
|
||||
addColorToQueue((y + 0x800) >> 7, index, buf, checkQueue);
|
||||
if ((y & 0xFF00) >= 0x800)
|
||||
addColorToQueue((y - 0x800) >> 7, index, buf, checkQueue);
|
||||
|
||||
uint32 z = col << 2;
|
||||
if ((z & 0xFF00) < 0xF800)
|
||||
addColorToQueue((z + 0x800) >> 2, index, buf, checkQueue);
|
||||
if ((z & 0xFF00) >= 0x800)
|
||||
addColorToQueue((z - 0x800) >> 2, index, buf, checkQueue);
|
||||
}
|
||||
|
||||
// Contract the table back to just palette entries
|
||||
for (int i = 0; i < 0x4000; i++)
|
||||
buf[i] = READ_UINT16(buf + i * 2) >> 8;
|
||||
|
||||
// Now go through and distribute the error to three more pixels
|
||||
byte *bufPtr = buf;
|
||||
for (uint realR = 0; realR < 0x100; realR += 8) {
|
||||
for (uint realG = 0; realG < 0x100; realG += 8) {
|
||||
for (uint realB = 0; realB < 0x100; realB += 16) {
|
||||
byte palIndex = *bufPtr;
|
||||
byte r = realR;
|
||||
byte g = realG;
|
||||
byte b = realB;
|
||||
|
||||
byte palR = palette[palIndex * 3] & 0xF8;
|
||||
byte palG = palette[palIndex * 3 + 1] & 0xF8;
|
||||
byte palB = palette[palIndex * 3 + 2] & 0xF0;
|
||||
|
||||
r = adjustColorRange(r, realR, palR);
|
||||
g = adjustColorRange(g, realG, palG);
|
||||
b = adjustColorRange(b, realB, palB);
|
||||
palIndex = buf[makeQuickTimeDitherColor(r, g, b)];
|
||||
bufPtr[0x4000] = palIndex;
|
||||
|
||||
palR = palette[palIndex * 3] & 0xF8;
|
||||
palG = palette[palIndex * 3 + 1] & 0xF8;
|
||||
palB = palette[palIndex * 3 + 2] & 0xF0;
|
||||
|
||||
r = adjustColorRange(r, realR, palR);
|
||||
g = adjustColorRange(g, realG, palG);
|
||||
b = adjustColorRange(b, realB, palB);
|
||||
palIndex = buf[makeQuickTimeDitherColor(r, g, b)];
|
||||
bufPtr[0x8000] = palIndex;
|
||||
|
||||
palR = palette[palIndex * 3] & 0xF8;
|
||||
palG = palette[palIndex * 3 + 1] & 0xF8;
|
||||
palB = palette[palIndex * 3 + 2] & 0xF0;
|
||||
|
||||
r = adjustColorRange(r, realR, palR);
|
||||
g = adjustColorRange(g, realG, palG);
|
||||
b = adjustColorRange(b, realB, palB);
|
||||
palIndex = buf[makeQuickTimeDitherColor(r, g, b)];
|
||||
bufPtr[0xC000] = palIndex;
|
||||
|
||||
bufPtr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
Codec *createBitmapCodec(uint32 tag, uint32 streamTag, int width, int height, int bitsPerPixel) {
|
||||
// Crusader videos are special cased here because the frame type is not in the "compression"
|
||||
// tag but in the "stream handler" tag for these files
|
||||
|
||||
@@ -122,11 +122,6 @@ public:
|
||||
* Set the decoding accuracy of the codec, if supported
|
||||
*/
|
||||
virtual void setCodecAccuracy(CodecAccuracy accuracy) {}
|
||||
|
||||
/**
|
||||
* Create a dither table, as used by QuickTime codecs.
|
||||
*/
|
||||
static byte *createQuickTimeDitherTable(const byte *palette, uint colorCount);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "image/codecs/dither.h"
|
||||
|
||||
#include "common/list.h"
|
||||
|
||||
namespace Image {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Add a color to the QuickTime dither table check queue if it hasn't already been found.
|
||||
*/
|
||||
inline void addColorToQueue(uint16 color, uint16 index, byte *checkBuffer, Common::List<uint16> &checkQueue) {
|
||||
if ((READ_UINT16(checkBuffer + color * 2) & 0xFF) == 0) {
|
||||
// Previously unfound color
|
||||
WRITE_UINT16(checkBuffer + color * 2, index);
|
||||
checkQueue.push_back(color);
|
||||
}
|
||||
}
|
||||
|
||||
inline byte adjustColorRange(byte currentColor, byte correctColor, byte palColor) {
|
||||
return CLIP<int>(currentColor - palColor + correctColor, 0, 255);
|
||||
}
|
||||
|
||||
inline uint16 makeQuickTimeDitherColor(byte r, byte g, byte b) {
|
||||
// RGB554
|
||||
return ((r & 0xF8) << 6) | ((g & 0xF8) << 1) | (b >> 4);
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
DitherCodec::DitherCodec(Codec *codec, DisposeAfterUse::Flag disposeAfterUse)
|
||||
: _codec(codec), _disposeAfterUse(disposeAfterUse), _dirtyPalette(false),
|
||||
_forcedDitherPalette(0), _ditherTable(0), _ditherFrame(0) {
|
||||
}
|
||||
|
||||
DitherCodec::~DitherCodec() {
|
||||
if (_disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete _codec;
|
||||
|
||||
delete[] _forcedDitherPalette;
|
||||
delete[] _ditherTable;
|
||||
|
||||
if (_ditherFrame) {
|
||||
_ditherFrame->free();
|
||||
delete _ditherFrame;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Default template to convert a dither color
|
||||
template<typename PixelInt>
|
||||
inline uint16 readQuickTimeDitherColor(PixelInt srcColor, const Graphics::PixelFormat& format, const byte *palette) {
|
||||
byte r, g, b;
|
||||
format.colorToRGB(srcColor, r, g, b);
|
||||
return makeQuickTimeDitherColor(r, g, b);
|
||||
}
|
||||
|
||||
// Specialized version for 8bpp
|
||||
template<>
|
||||
inline uint16 readQuickTimeDitherColor(byte srcColor, const Graphics::PixelFormat& format, const byte *palette) {
|
||||
return makeQuickTimeDitherColor(palette[srcColor * 3], palette[srcColor * 3 + 1], palette[srcColor * 3 + 2]);
|
||||
}
|
||||
|
||||
template<typename PixelInt>
|
||||
void ditherQuickTimeFrame(const Graphics::Surface &src, Graphics::Surface &dst, const byte *ditherTable, const byte *palette = 0) {
|
||||
static const uint16 colorTableOffsets[] = { 0x0000, 0xC000, 0x4000, 0x8000 };
|
||||
|
||||
for (int y = 0; y < dst.h; y++) {
|
||||
const PixelInt *srcPtr = (const PixelInt *)src.getBasePtr(0, y);
|
||||
byte *dstPtr = (byte *)dst.getBasePtr(0, y);
|
||||
uint16 colorTableOffset = colorTableOffsets[y & 3];
|
||||
|
||||
for (int x = 0; x < dst.w; x++) {
|
||||
uint16 color = readQuickTimeDitherColor(*srcPtr++, src.format, palette);
|
||||
*dstPtr++ = ditherTable[colorTableOffset + color];
|
||||
colorTableOffset += 0x4000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
const Graphics::Surface *DitherCodec::decodeFrame(Common::SeekableReadStream &stream) {
|
||||
const Graphics::Surface *frame = _codec->decodeFrame(stream);
|
||||
if (!frame || !_forcedDitherPalette)
|
||||
return frame;
|
||||
|
||||
// TODO: Handle palettes that are owned by the container instead of the codec
|
||||
const byte *curPalette = _codec->getPalette();
|
||||
|
||||
if (frame->format.isCLUT8() && curPalette) {
|
||||
// This should always be true, but this is for sanity
|
||||
if (!curPalette)
|
||||
return frame;
|
||||
|
||||
// If the palettes match, bail out
|
||||
if (memcmp(_forcedDitherPalette, curPalette, 256 * 3) == 0)
|
||||
return frame;
|
||||
}
|
||||
|
||||
// Need to create a new one
|
||||
if (!_ditherFrame) {
|
||||
_ditherFrame = new Graphics::Surface();
|
||||
_ditherFrame->create(frame->w, frame->h, Graphics::PixelFormat::createFormatCLUT8());
|
||||
}
|
||||
|
||||
if (frame->format.isCLUT8() && curPalette)
|
||||
ditherQuickTimeFrame<byte>(*frame, *_ditherFrame, _ditherTable, curPalette);
|
||||
else if (frame->format.bytesPerPixel == 2)
|
||||
ditherQuickTimeFrame<uint16>(*frame, *_ditherFrame, _ditherTable);
|
||||
else if (frame->format.bytesPerPixel == 4)
|
||||
ditherQuickTimeFrame<uint32>(*frame, *_ditherFrame, _ditherTable);
|
||||
|
||||
return _ditherFrame;
|
||||
}
|
||||
|
||||
Graphics::PixelFormat DitherCodec::getPixelFormat() const {
|
||||
if (!_forcedDitherPalette)
|
||||
return _codec->getPixelFormat();
|
||||
return Graphics::PixelFormat::createFormatCLUT8();
|
||||
}
|
||||
|
||||
bool DitherCodec::setOutputPixelFormat(const Graphics::PixelFormat &format) {
|
||||
if (!_forcedDitherPalette)
|
||||
return _codec->setOutputPixelFormat(format);
|
||||
return format.isCLUT8();
|
||||
}
|
||||
|
||||
bool DitherCodec::containsPalette() const {
|
||||
if (!_forcedDitherPalette)
|
||||
return _codec->containsPalette();
|
||||
return true;
|
||||
}
|
||||
|
||||
const byte *DitherCodec::getPalette() {
|
||||
if (!_forcedDitherPalette)
|
||||
return _codec->getPalette();
|
||||
_dirtyPalette = false;
|
||||
return _forcedDitherPalette;
|
||||
}
|
||||
|
||||
bool DitherCodec::hasDirtyPalette() const {
|
||||
if (!_forcedDitherPalette)
|
||||
return _codec->hasDirtyPalette();
|
||||
return _dirtyPalette;
|
||||
}
|
||||
|
||||
bool DitherCodec::canDither(DitherType type) const {
|
||||
return _codec->canDither(type) || (type == kDitherTypeQT);
|
||||
}
|
||||
|
||||
void DitherCodec::setDither(DitherType type, const byte *palette) {
|
||||
if (_codec->canDither(type)) {
|
||||
_codec->setDither(type, palette);
|
||||
} else {
|
||||
assert(type == kDitherTypeQT);
|
||||
assert(!_forcedDitherPalette);
|
||||
|
||||
// Forced dither
|
||||
_forcedDitherPalette = new byte[256 * 3];
|
||||
memcpy(_forcedDitherPalette, palette, 256 * 3);
|
||||
_dirtyPalette = true;
|
||||
|
||||
_ditherTable = createQuickTimeDitherTable(_forcedDitherPalette, 256);
|
||||
}
|
||||
}
|
||||
|
||||
void DitherCodec::setCodecAccuracy(CodecAccuracy accuracy) {
|
||||
return _codec->setCodecAccuracy(accuracy);
|
||||
}
|
||||
|
||||
byte *DitherCodec::createQuickTimeDitherTable(const byte *palette, uint colorCount) {
|
||||
byte *buf = new byte[0x10000]();
|
||||
|
||||
Common::List<uint16> checkQueue;
|
||||
|
||||
bool foundBlack = false;
|
||||
bool foundWhite = false;
|
||||
|
||||
const byte *palPtr = palette;
|
||||
|
||||
// See what colors we have, and add them to the queue to check
|
||||
for (uint i = 0; i < colorCount; i++) {
|
||||
byte r = *palPtr++;
|
||||
byte g = *palPtr++;
|
||||
byte b = *palPtr++;
|
||||
uint16 n = (i << 8) | 1;
|
||||
uint16 col = makeQuickTimeDitherColor(r, g, b);
|
||||
|
||||
if (col == 0) {
|
||||
// Special case for close-to-black
|
||||
// The original did more here, but it effectively discarded the value
|
||||
// due to a poor if-check (whole 16-bit value instead of lower 8-bits).
|
||||
WRITE_UINT16(buf, n);
|
||||
foundBlack = true;
|
||||
} else if (col == 0x3FFF) {
|
||||
// Special case for close-to-white
|
||||
// The original did more here, but it effectively discarded the value
|
||||
// due to a poor if-check (whole 16-bit value instead of lower 8-bits).
|
||||
WRITE_UINT16(buf + 0x7FFE, n);
|
||||
foundWhite = true;
|
||||
} else {
|
||||
// Previously unfound color
|
||||
addColorToQueue(col, n, buf, checkQueue);
|
||||
}
|
||||
}
|
||||
|
||||
// More special handling for white
|
||||
if (foundWhite)
|
||||
checkQueue.push_front(0x3FFF);
|
||||
|
||||
// More special handling for black
|
||||
if (foundBlack)
|
||||
checkQueue.push_front(0);
|
||||
|
||||
// Go through the list of colors we have and match up similar colors
|
||||
// to fill in the table as best as we can.
|
||||
while (!checkQueue.empty()) {
|
||||
uint16 col = checkQueue.front();
|
||||
checkQueue.pop_front();
|
||||
uint16 index = READ_UINT16(buf + col * 2);
|
||||
|
||||
uint32 x = col << 4;
|
||||
if ((x & 0xFF) < 0xF0)
|
||||
addColorToQueue((x + 0x10) >> 4, index, buf, checkQueue);
|
||||
if ((x & 0xFF) >= 0x10)
|
||||
addColorToQueue((x - 0x10) >> 4, index, buf, checkQueue);
|
||||
|
||||
uint32 y = col << 7;
|
||||
if ((y & 0xFF00) < 0xF800)
|
||||
addColorToQueue((y + 0x800) >> 7, index, buf, checkQueue);
|
||||
if ((y & 0xFF00) >= 0x800)
|
||||
addColorToQueue((y - 0x800) >> 7, index, buf, checkQueue);
|
||||
|
||||
uint32 z = col << 2;
|
||||
if ((z & 0xFF00) < 0xF800)
|
||||
addColorToQueue((z + 0x800) >> 2, index, buf, checkQueue);
|
||||
if ((z & 0xFF00) >= 0x800)
|
||||
addColorToQueue((z - 0x800) >> 2, index, buf, checkQueue);
|
||||
}
|
||||
|
||||
// Contract the table back to just palette entries
|
||||
for (int i = 0; i < 0x4000; i++)
|
||||
buf[i] = READ_UINT16(buf + i * 2) >> 8;
|
||||
|
||||
// Now go through and distribute the error to three more pixels
|
||||
byte *bufPtr = buf;
|
||||
for (uint realR = 0; realR < 0x100; realR += 8) {
|
||||
for (uint realG = 0; realG < 0x100; realG += 8) {
|
||||
for (uint realB = 0; realB < 0x100; realB += 16) {
|
||||
byte palIndex = *bufPtr;
|
||||
byte r = realR;
|
||||
byte g = realG;
|
||||
byte b = realB;
|
||||
|
||||
byte palR = palette[palIndex * 3] & 0xF8;
|
||||
byte palG = palette[palIndex * 3 + 1] & 0xF8;
|
||||
byte palB = palette[palIndex * 3 + 2] & 0xF0;
|
||||
|
||||
r = adjustColorRange(r, realR, palR);
|
||||
g = adjustColorRange(g, realG, palG);
|
||||
b = adjustColorRange(b, realB, palB);
|
||||
palIndex = buf[makeQuickTimeDitherColor(r, g, b)];
|
||||
bufPtr[0x4000] = palIndex;
|
||||
|
||||
palR = palette[palIndex * 3] & 0xF8;
|
||||
palG = palette[palIndex * 3 + 1] & 0xF8;
|
||||
palB = palette[palIndex * 3 + 2] & 0xF0;
|
||||
|
||||
r = adjustColorRange(r, realR, palR);
|
||||
g = adjustColorRange(g, realG, palG);
|
||||
b = adjustColorRange(b, realB, palB);
|
||||
palIndex = buf[makeQuickTimeDitherColor(r, g, b)];
|
||||
bufPtr[0x8000] = palIndex;
|
||||
|
||||
palR = palette[palIndex * 3] & 0xF8;
|
||||
palG = palette[palIndex * 3 + 1] & 0xF8;
|
||||
palB = palette[palIndex * 3 + 2] & 0xF0;
|
||||
|
||||
r = adjustColorRange(r, realR, palR);
|
||||
g = adjustColorRange(g, realG, palG);
|
||||
b = adjustColorRange(b, realB, palB);
|
||||
palIndex = buf[makeQuickTimeDitherColor(r, g, b)];
|
||||
bufPtr[0xC000] = palIndex;
|
||||
|
||||
bufPtr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // End of namespace Image
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IMAGE_CODECS_DITHER_H
|
||||
#define IMAGE_CODECS_DITHER_H
|
||||
|
||||
#include "image/codecs/codec.h"
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Image {
|
||||
|
||||
class DitherCodec : public Codec {
|
||||
public:
|
||||
DitherCodec(Codec *codec, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
|
||||
virtual ~DitherCodec() override;
|
||||
|
||||
const Graphics::Surface *decodeFrame(Common::SeekableReadStream &stream) override;
|
||||
|
||||
Graphics::PixelFormat getPixelFormat() const override;
|
||||
bool setOutputPixelFormat(const Graphics::PixelFormat &format) override;
|
||||
bool containsPalette() const override;
|
||||
const byte *getPalette() override;
|
||||
bool hasDirtyPalette() const override;
|
||||
bool canDither(DitherType type) const override;
|
||||
void setDither(DitherType type, const byte *palette) override;
|
||||
void setCodecAccuracy(CodecAccuracy accuracy) override;
|
||||
|
||||
/**
|
||||
* Create a dither table, as used by QuickTime codecs.
|
||||
*/
|
||||
static byte *createQuickTimeDitherTable(const byte *palette, uint colorCount);
|
||||
|
||||
private:
|
||||
DisposeAfterUse::Flag _disposeAfterUse;
|
||||
Codec *_codec;
|
||||
|
||||
Graphics::Surface *_ditherFrame;
|
||||
byte *_forcedDitherPalette;
|
||||
byte *_ditherTable;
|
||||
bool _dirtyPalette;
|
||||
};
|
||||
|
||||
} // End of namespace Image
|
||||
|
||||
#endif
|
||||
@@ -23,6 +23,7 @@
|
||||
// Based off ffmpeg's QuickTime RLE decoder (written by Mike Melanson)
|
||||
|
||||
#include "image/codecs/qtrle.h"
|
||||
#include "image/codecs/dither.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/scummsys.h"
|
||||
@@ -662,7 +663,7 @@ void QTRLEDecoder::setDither(DitherType type, const byte *palette) {
|
||||
_dirtyPalette = true;
|
||||
|
||||
delete[] _colorMap;
|
||||
_colorMap = createQuickTimeDitherTable(palette, 256);
|
||||
_colorMap = DitherCodec::createQuickTimeDitherTable(palette, 256);
|
||||
}
|
||||
|
||||
void QTRLEDecoder::createSurface() {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
// Based off ffmpeg's RPZA decoder
|
||||
|
||||
#include "image/codecs/rpza.h"
|
||||
#include "image/codecs/dither.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/system.h"
|
||||
@@ -358,7 +359,7 @@ void RPZADecoder::setDither(DitherType type, const byte *palette) {
|
||||
_format = Graphics::PixelFormat::createFormatCLUT8();
|
||||
|
||||
delete[] _colorMap;
|
||||
_colorMap = createQuickTimeDitherTable(palette, 256);
|
||||
_colorMap = DitherCodec::createQuickTimeDitherTable(palette, 256);
|
||||
}
|
||||
|
||||
} // End of namespace Image
|
||||
|
||||
@@ -19,6 +19,7 @@ MODULE_OBJS := \
|
||||
codecs/cdtoons.o \
|
||||
codecs/cinepak.o \
|
||||
codecs/codec.o \
|
||||
codecs/dither.o \
|
||||
codecs/hlz.o \
|
||||
codecs/jyv1.o \
|
||||
codecs/mjpeg.o \
|
||||
|
||||
+5
-92
@@ -46,6 +46,7 @@
|
||||
|
||||
// Video codecs
|
||||
#include "image/codecs/codec.h"
|
||||
#include "image/codecs/dither.h"
|
||||
|
||||
namespace Video {
|
||||
|
||||
@@ -325,7 +326,7 @@ Audio::SeekableAudioStream *QuickTimeDecoder::AudioTrackHandler::getSeekableAudi
|
||||
return _audioTrack;
|
||||
}
|
||||
|
||||
QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder, Common::QuickTimeParser::Track *parent) : _decoder(decoder), _parent(parent), _forcedDitherPalette(0) {
|
||||
QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder, Common::QuickTimeParser::Track *parent) : _decoder(decoder), _parent(parent) {
|
||||
if (decoder->_enableEditListBoundsCheckQuirk) {
|
||||
checkEditListBounds();
|
||||
}
|
||||
@@ -343,8 +344,6 @@ QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder
|
||||
_curPalette = 0;
|
||||
_dirtyPalette = false;
|
||||
_reversed = false;
|
||||
_ditherTable = 0;
|
||||
_ditherFrame = 0;
|
||||
}
|
||||
|
||||
// FIXME: This check breaks valid QuickTime movies, such as the KQ6 Mac opening.
|
||||
@@ -389,13 +388,6 @@ QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() {
|
||||
_scaledSurface->free();
|
||||
delete _scaledSurface;
|
||||
}
|
||||
|
||||
delete[] _ditherTable;
|
||||
|
||||
if (_ditherFrame) {
|
||||
_ditherFrame->free();
|
||||
delete _ditherFrame;
|
||||
}
|
||||
}
|
||||
|
||||
bool QuickTimeDecoder::VideoTrackHandler::endOfTrack() const {
|
||||
@@ -492,17 +484,11 @@ uint16 QuickTimeDecoder::VideoTrackHandler::getHeight() const {
|
||||
}
|
||||
|
||||
Graphics::PixelFormat QuickTimeDecoder::VideoTrackHandler::getPixelFormat() const {
|
||||
if (_forcedDitherPalette.size() > 0)
|
||||
return Graphics::PixelFormat::createFormatCLUT8();
|
||||
|
||||
// TODO: What should happen if there are multiple codecs with different formats?
|
||||
return ((VideoSampleDesc *)_parent->sampleDescs[0])->_videoCodec->getPixelFormat();
|
||||
}
|
||||
|
||||
bool QuickTimeDecoder::VideoTrackHandler::setOutputPixelFormat(const Graphics::PixelFormat &format) {
|
||||
if (_forcedDitherPalette.size() > 0)
|
||||
return false;
|
||||
|
||||
bool success = true;
|
||||
|
||||
for (uint i = 0; i < _parent->sampleDescs.size(); i++) {
|
||||
@@ -597,10 +583,6 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle forced dithering
|
||||
if (frame && _forcedDitherPalette.size() > 0)
|
||||
frame = forceDither(*frame);
|
||||
|
||||
if (frame && (_parent->scaleFactorX != 1 || _parent->scaleFactorY != 1)) {
|
||||
if (!_scaledSurface) {
|
||||
_scaledSurface = new Graphics::Surface();
|
||||
@@ -641,7 +623,7 @@ Audio::Timestamp QuickTimeDecoder::VideoTrackHandler::getFrameTime(uint frame) c
|
||||
|
||||
const byte *QuickTimeDecoder::VideoTrackHandler::getPalette() const {
|
||||
_dirtyPalette = false;
|
||||
return _forcedDitherPalette.size() > 0 ? _forcedDitherPalette.data() : _curPalette;
|
||||
return _curPalette;
|
||||
}
|
||||
|
||||
bool QuickTimeDecoder::VideoTrackHandler::setReverse(bool reverse) {
|
||||
@@ -976,79 +958,10 @@ void QuickTimeDecoder::VideoTrackHandler::setDither(const byte *palette) {
|
||||
desc->_videoCodec->setDither(Image::Codec::kDitherTypeQT, palette);
|
||||
} else {
|
||||
// Forced dither
|
||||
_forcedDitherPalette.resize(256, false);
|
||||
_forcedDitherPalette.set(palette, 0, 256);
|
||||
_ditherTable = Image::Codec::createQuickTimeDitherTable(_forcedDitherPalette.data(), 256);
|
||||
_dirtyPalette = true;
|
||||
desc->_videoCodec = new Image::DitherCodec(desc->_videoCodec);
|
||||
desc->_videoCodec->setDither(Image::Codec::kDitherTypeQT, palette);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Return a pixel in RGB554
|
||||
uint16 makeDitherColor(byte r, byte g, byte b) {
|
||||
return ((r & 0xF8) << 6) | ((g & 0xF8) << 1) | (b >> 4);
|
||||
}
|
||||
|
||||
// Default template to convert a dither color
|
||||
template<typename PixelInt>
|
||||
inline uint16 readDitherColor(PixelInt srcColor, const Graphics::PixelFormat& format, const byte *palette) {
|
||||
byte r, g, b;
|
||||
format.colorToRGB(srcColor, r, g, b);
|
||||
return makeDitherColor(r, g, b);
|
||||
}
|
||||
|
||||
// Specialized version for 8bpp
|
||||
template<>
|
||||
inline uint16 readDitherColor(byte srcColor, const Graphics::PixelFormat& format, const byte *palette) {
|
||||
return makeDitherColor(palette[srcColor * 3], palette[srcColor * 3 + 1], palette[srcColor * 3 + 2]);
|
||||
}
|
||||
|
||||
template<typename PixelInt>
|
||||
void ditherFrame(const Graphics::Surface &src, Graphics::Surface &dst, const byte *ditherTable, const byte *palette = 0) {
|
||||
static const uint16 colorTableOffsets[] = { 0x0000, 0xC000, 0x4000, 0x8000 };
|
||||
|
||||
for (int y = 0; y < dst.h; y++) {
|
||||
const PixelInt *srcPtr = (const PixelInt *)src.getBasePtr(0, y);
|
||||
byte *dstPtr = (byte *)dst.getBasePtr(0, y);
|
||||
uint16 colorTableOffset = colorTableOffsets[y & 3];
|
||||
|
||||
for (int x = 0; x < dst.w; x++) {
|
||||
uint16 color = readDitherColor(*srcPtr++, src.format, palette);
|
||||
*dstPtr++ = ditherTable[colorTableOffset + color];
|
||||
colorTableOffset += 0x4000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::forceDither(const Graphics::Surface &frame) {
|
||||
if (frame.format.bytesPerPixel == 1) {
|
||||
// This should always be true, but this is for sanity
|
||||
if (!_curPalette)
|
||||
return &frame;
|
||||
|
||||
// If the palettes match, bail out
|
||||
if (memcmp(_forcedDitherPalette.data(), _curPalette, 256 * 3) == 0)
|
||||
return &frame;
|
||||
}
|
||||
|
||||
// Need to create a new one
|
||||
if (!_ditherFrame) {
|
||||
_ditherFrame = new Graphics::Surface();
|
||||
_ditherFrame->create(frame.w, frame.h, Graphics::PixelFormat::createFormatCLUT8());
|
||||
}
|
||||
|
||||
if (frame.format.bytesPerPixel == 1)
|
||||
ditherFrame<byte>(frame, *_ditherFrame, _ditherTable, _curPalette);
|
||||
else if (frame.format.bytesPerPixel == 2)
|
||||
ditherFrame<uint16>(frame, *_ditherFrame, _ditherTable);
|
||||
else if (frame.format.bytesPerPixel == 4)
|
||||
ditherFrame<uint32>(frame, *_ditherFrame, _ditherTable);
|
||||
|
||||
return _ditherFrame;
|
||||
}
|
||||
|
||||
} // End of namespace Video
|
||||
|
||||
@@ -369,12 +369,6 @@ private:
|
||||
mutable bool _dirtyPalette;
|
||||
bool _reversed;
|
||||
|
||||
// Forced dithering of frames
|
||||
Graphics::Palette _forcedDitherPalette;
|
||||
byte *_ditherTable;
|
||||
Graphics::Surface *_ditherFrame;
|
||||
const Graphics::Surface *forceDither(const Graphics::Surface &frame);
|
||||
|
||||
Common::SeekableReadStream *getNextFramePacket(uint32 &descId);
|
||||
uint32 getCurFrameDuration(); // media time
|
||||
uint32 findKeyFrame(uint32 frame) const;
|
||||
|
||||
Reference in New Issue
Block a user