[Scummvm-git-logs] scummvm master -> 7294a5eff2da4732453f4e3d06391ab9f455c561

sev- noreply at scummvm.org
Wed Apr 30 11:20:35 UTC 2025


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
1e62c54dcd IMAGE: Improved dithering support for QuickTime RLE
9bbb019950 IMAGE: Split the generic QuickTime dithering into a separate class
320865e802 IMAGE: Use setOutputPixelFormat to avoid extra conversion when dithering
7294a5eff2 IMAGE: Use the palette class in the dithered codec wrapper


Commit: 1e62c54dcd8f7f79c207c60f4badfb46ad5a8e3d
    https://github.com/scummvm/scummvm/commit/1e62c54dcd8f7f79c207c60f4badfb46ad5a8e3d
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2025-04-30T19:20:29+08:00

Commit Message:
IMAGE: Improved dithering support for QuickTime RLE

Changed paths:
    image/codecs/qtrle.cpp
    image/codecs/qtrle.h


diff --git a/image/codecs/qtrle.cpp b/image/codecs/qtrle.cpp
index 269fbe3be60..cd5428146e6 100644
--- a/image/codecs/qtrle.cpp
+++ b/image/codecs/qtrle.cpp
@@ -260,6 +260,68 @@ void QTRLEDecoder::decode16(Common::SeekableReadStream &stream, uint32 rowPtr, u
 	}
 }
 
+namespace {
+
+inline uint16 readDitherColor16(Common::ReadStream &stream) {
+	return stream.readUint16BE() >> 1;
+}
+
+} // End of anonymous namespace
+
+void QTRLEDecoder::dither16(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange) {
+	uint32 pixelPtr = 0;
+	byte *output = (byte *)_surface->getPixels();
+
+	static const uint16 colorTableOffsets[] = { 0x0000, 0xC000, 0x4000, 0x8000 };
+
+	// clone2727 thinks this should be startLine & 3, but the original definitely
+	// isn't doing this. Unless startLine & 3 is always 0? Kinda defeats the
+	// purpose of the compression then.
+	byte curColorTableOffset = 0;
+
+	while (linesToChange--) {
+		CHECK_STREAM_PTR(2);
+
+		byte rowOffset = stream.readByte() - 1;
+		pixelPtr = rowPtr + rowOffset;
+		uint16 colorTableOffset = colorTableOffsets[curColorTableOffset] + (rowOffset << 14);
+
+		for (int rleCode = stream.readSByte(); rleCode != -1; rleCode = stream.readSByte()) {
+			if (rleCode == 0) {
+				// there's another skip code in the stream
+				CHECK_STREAM_PTR(1);
+				pixelPtr += stream.readByte() - 1;
+			} else if (rleCode < 0) {
+				// decode the run length code
+				rleCode = -rleCode;
+				CHECK_STREAM_PTR(2);
+
+				uint16 color = readDitherColor16(stream);
+
+				CHECK_PIXEL_PTR(rleCode);
+
+				while (rleCode--) {
+					output[pixelPtr++] = _colorMap[colorTableOffset + color];
+					colorTableOffset += 0x4000;
+				}
+			} else {
+				CHECK_STREAM_PTR(rleCode * 2);
+				CHECK_PIXEL_PTR(rleCode);
+
+				// copy pixels directly to output
+				while (rleCode--) {
+					uint16 color = readDitherColor16(stream);
+					output[pixelPtr++] = _colorMap[colorTableOffset + color];
+					colorTableOffset += 0x4000;
+				}
+			}
+		}
+
+		rowPtr += _paddedWidth;
+		curColorTableOffset = (curColorTableOffset + 1) & 3;
+	}
+}
+
 void QTRLEDecoder::decode24(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange) {
 	uint32 pixelPtr = 0;
 	uint32 *rgb = (uint32 *)_surface->getPixels();
@@ -309,9 +371,12 @@ void QTRLEDecoder::decode24(Common::SeekableReadStream &stream, uint32 rowPtr, u
 namespace {
 
 inline uint16 readDitherColor24(Common::ReadStream &stream) {
-	uint16 color = (stream.readByte() & 0xF8) << 6;
-	color |= (stream.readByte() & 0xF8) << 1;
-	color |= stream.readByte() >> 4;
+	uint8 rgb[3];
+	stream.read(rgb, 3);
+
+	uint16 color = (rgb[0] & 0xF8) << 6;
+	color |= (rgb[1] & 0xF8) << 1;
+	color |= rgb[2] >> 4;
 	return color;
 }
 
@@ -343,12 +408,12 @@ void QTRLEDecoder::dither24(Common::SeekableReadStream &stream, uint32 rowPtr, u
 			} else if (rleCode < 0) {
 				// decode the run length code
 				rleCode = -rleCode;
-
 				CHECK_STREAM_PTR(3);
-				CHECK_PIXEL_PTR(rleCode);
 
 				uint16 color = readDitherColor24(stream);
 
+				CHECK_PIXEL_PTR(rleCode);
+
 				while (rleCode--) {
 					output[pixelPtr++] = _colorMap[colorTableOffset + color];
 					colorTableOffset += 0x4000;
@@ -419,6 +484,74 @@ void QTRLEDecoder::decode32(Common::SeekableReadStream &stream, uint32 rowPtr, u
 	}
 }
 
+namespace {
+
+inline uint16 readDitherColor32(Common::ReadStream &stream) {
+	uint8 argb[4];
+	stream.read(argb, 4);
+
+	uint16 color = (argb[1] & 0xF8) << 6;
+	color |= (argb[2] & 0xF8) << 1;
+	color |= argb[3] >> 4;
+	return color;
+}
+
+} // End of anonymous namespace
+
+void QTRLEDecoder::dither32(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange) {
+	uint32 pixelPtr = 0;
+	byte *output = (byte *)_surface->getPixels();
+
+	static const uint16 colorTableOffsets[] = { 0x0000, 0xC000, 0x4000, 0x8000 };
+
+	// clone2727 thinks this should be startLine & 3, but the original definitely
+	// isn't doing this. Unless startLine & 3 is always 0? Kinda defeats the
+	// purpose of the compression then.
+	byte curColorTableOffset = 0;
+
+	while (linesToChange--) {
+		CHECK_STREAM_PTR(2);
+
+		byte rowOffset = stream.readByte() - 1;
+		pixelPtr = rowPtr + rowOffset;
+		uint16 colorTableOffset = colorTableOffsets[curColorTableOffset] + (rowOffset << 14);
+
+		for (int rleCode = stream.readSByte(); rleCode != -1; rleCode = stream.readSByte()) {
+			if (rleCode == 0) {
+				// there's another skip code in the stream
+				CHECK_STREAM_PTR(1);
+				pixelPtr += stream.readByte() - 1;
+			} else if (rleCode < 0) {
+				// decode the run length code
+				rleCode = -rleCode;
+				CHECK_STREAM_PTR(4);
+
+				uint16 color = readDitherColor32(stream);
+
+				CHECK_PIXEL_PTR(rleCode);
+
+				while (rleCode--) {
+					output[pixelPtr++] = _colorMap[colorTableOffset + color];
+					colorTableOffset += 0x4000;
+				}
+			} else {
+				CHECK_STREAM_PTR(rleCode * 4);
+				CHECK_PIXEL_PTR(rleCode);
+
+				// copy pixels directly to output
+				while (rleCode--) {
+					uint16 color = readDitherColor32(stream);
+					output[pixelPtr++] = _colorMap[colorTableOffset + color];
+					colorTableOffset += 0x4000;
+				}
+			}
+		}
+
+		rowPtr += _paddedWidth;
+		curColorTableOffset = (curColorTableOffset + 1) & 3;
+	}
+}
+
 const Graphics::Surface *QTRLEDecoder::decodeFrame(Common::SeekableReadStream &stream) {
 	if (!_surface)
 		createSurface();
@@ -467,7 +600,10 @@ const Graphics::Surface *QTRLEDecoder::decodeFrame(Common::SeekableReadStream &s
 		decode8(stream, rowPtr, height);
 		break;
 	case 16:
-		decode16(stream, rowPtr, height);
+		if (_ditherPalette.size() > 0)
+			dither16(stream, rowPtr, height);
+		else
+			decode16(stream, rowPtr, height);
 		break;
 	case 24:
 		if (_ditherPalette.size() > 0)
@@ -476,7 +612,10 @@ const Graphics::Surface *QTRLEDecoder::decodeFrame(Common::SeekableReadStream &s
 			decode24(stream, rowPtr, height);
 		break;
 	case 32:
-		decode32(stream, rowPtr, height);
+		if (_ditherPalette.size() > 0)
+			dither32(stream, rowPtr, height);
+		else
+			decode32(stream, rowPtr, height);
 		break;
 	default:
 		error("Unsupported QTRLE bits per pixel %d", _bitsPerPixel);
@@ -512,8 +651,7 @@ Graphics::PixelFormat QTRLEDecoder::getPixelFormat() const {
 }
 
 bool QTRLEDecoder::canDither(DitherType type) const {
-	// Only 24-bit dithering is implemented at the moment
-	return type == kDitherTypeQT && _bitsPerPixel == 24;
+	return type == kDitherTypeQT && (_bitsPerPixel == 16 || _bitsPerPixel == 24 || _bitsPerPixel == 32);
 }
 
 void QTRLEDecoder::setDither(DitherType type, const byte *palette) {
diff --git a/image/codecs/qtrle.h b/image/codecs/qtrle.h
index 12d37ab5e81..dc9290d229a 100644
--- a/image/codecs/qtrle.h
+++ b/image/codecs/qtrle.h
@@ -62,9 +62,11 @@ private:
 	void decode2_4(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange, byte bpp);
 	void decode8(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange);
 	void decode16(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange);
+	void dither16(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange);
 	void decode24(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange);
 	void dither24(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange);
 	void decode32(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange);
+	void dither32(Common::SeekableReadStream &stream, uint32 rowPtr, uint32 linesToChange);
 };
 
 } // End of namespace Image


Commit: 9bbb019950141308e16fd8df657310cb9392abfb
    https://github.com/scummvm/scummvm/commit/9bbb019950141308e16fd8df657310cb9392abfb
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2025-04-30T19:20:29+08:00

Commit Message:
IMAGE: Split the generic QuickTime dithering into a separate class

Changed paths:
  A image/codecs/dither.cpp
  A image/codecs/dither.h
    image/codecs/cinepak.cpp
    image/codecs/codec.cpp
    image/codecs/codec.h
    image/codecs/qtrle.cpp
    image/codecs/rpza.cpp
    image/module.mk
    video/qt_decoder.cpp
    video/qt_decoder.h


diff --git a/image/codecs/cinepak.cpp b/image/codecs/cinepak.cpp
index b2701bc97a8..e3cf84537d9 100644
--- a/image/codecs/cinepak.cpp
+++ b/image/codecs/cinepak.cpp
@@ -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);
 	}
 }
 
diff --git a/image/codecs/codec.cpp b/image/codecs/codec.cpp
index 94103390763..be7858183e1 100644
--- a/image/codecs/codec.cpp
+++ b/image/codecs/codec.cpp
@@ -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
diff --git a/image/codecs/codec.h b/image/codecs/codec.h
index a8dd7107a3a..50916aaebc0 100644
--- a/image/codecs/codec.h
+++ b/image/codecs/codec.h
@@ -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);
 };
 
 /**
diff --git a/image/codecs/dither.cpp b/image/codecs/dither.cpp
new file mode 100644
index 00000000000..88dc5c96bd5
--- /dev/null
+++ b/image/codecs/dither.cpp
@@ -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
+
diff --git a/image/codecs/dither.h b/image/codecs/dither.h
new file mode 100644
index 00000000000..1275154c5cc
--- /dev/null
+++ b/image/codecs/dither.h
@@ -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
diff --git a/image/codecs/qtrle.cpp b/image/codecs/qtrle.cpp
index cd5428146e6..c780bcb7021 100644
--- a/image/codecs/qtrle.cpp
+++ b/image/codecs/qtrle.cpp
@@ -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() {
diff --git a/image/codecs/rpza.cpp b/image/codecs/rpza.cpp
index 595cd2f8403..d33c17627d2 100644
--- a/image/codecs/rpza.cpp
+++ b/image/codecs/rpza.cpp
@@ -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
diff --git a/image/module.mk b/image/module.mk
index 60e74270b65..67d0c733c13 100644
--- a/image/module.mk
+++ b/image/module.mk
@@ -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 \
diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp
index 917474b4436..980cd8a0777 100644
--- a/video/qt_decoder.cpp
+++ b/video/qt_decoder.cpp
@@ -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;
-		}
-	}
-}
-
-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;
+			desc->_videoCodec = new Image::DitherCodec(desc->_videoCodec);
+			desc->_videoCodec->setDither(Image::Codec::kDitherTypeQT, palette);
 		}
 	}
 }
 
-} // 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
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index 02b4a595450..3a17adf65bb 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -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;


Commit: 320865e802c4d7143285d99e282296ec2c02f54e
    https://github.com/scummvm/scummvm/commit/320865e802c4d7143285d99e282296ec2c02f54e
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2025-04-30T19:20:29+08:00

Commit Message:
IMAGE: Use setOutputPixelFormat to avoid extra conversion when dithering

Changed paths:
    image/codecs/dither.cpp


diff --git a/image/codecs/dither.cpp b/image/codecs/dither.cpp
index 88dc5c96bd5..87a97a19410 100644
--- a/image/codecs/dither.cpp
+++ b/image/codecs/dither.cpp
@@ -70,21 +70,29 @@ DitherCodec::~DitherCodec() {
 namespace {
 
 // Default template to convert a dither color
-template<typename PixelInt>
-inline uint16 readQuickTimeDitherColor(PixelInt srcColor, const Graphics::PixelFormat& format, const byte *palette) {
+inline uint16 readQT_RGB(uint32 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) {
+inline uint16 readQT_Palette(uint8 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) {
+// Specialized version for RGB554
+inline uint16 readQT_RGB554(uint16 srcColor, const Graphics::PixelFormat& format, const byte *palette) {
+	return srcColor;
+}
+
+// Specialized version for RGB555 and ARGB1555
+inline uint16 readQT_RGB555(uint16 srcColor, const Graphics::PixelFormat& format, const byte *palette) {
+	return (srcColor >> 1) & 0x3FFF;
+}
+
+template<typename PixelInt, class Fn>
+void ditherQuickTimeFrame(const Graphics::Surface &src, Graphics::Surface &dst, const byte *ditherTable, Fn fn, const byte *palette = 0) {
 	static const uint16 colorTableOffsets[] = { 0x0000, 0xC000, 0x4000, 0x8000 };
 
 	for (int y = 0; y < dst.h; y++) {
@@ -93,7 +101,7 @@ void ditherQuickTimeFrame(const Graphics::Surface &src, Graphics::Surface &dst,
 		uint16 colorTableOffset = colorTableOffsets[y & 3];
 
 		for (int x = 0; x < dst.w; x++) {
-			uint16 color = readQuickTimeDitherColor(*srcPtr++, src.format, palette);
+			uint16 color = fn(*srcPtr++, src.format, palette);
 			*dstPtr++ = ditherTable[colorTableOffset + color];
 			colorTableOffset += 0x4000;
 		}
@@ -127,11 +135,16 @@ const Graphics::Surface *DitherCodec::decodeFrame(Common::SeekableReadStream &st
 	}
 
 	if (frame->format.isCLUT8() && curPalette)
-		ditherQuickTimeFrame<byte>(*frame, *_ditherFrame, _ditherTable, curPalette);
+		ditherQuickTimeFrame<byte>(*frame, *_ditherFrame, _ditherTable, readQT_Palette, curPalette);
+	else if (frame->format == Graphics::PixelFormat(2, 5, 5, 4, 0, 9, 4, 0, 0))
+		ditherQuickTimeFrame<uint16>(*frame, *_ditherFrame, _ditherTable, readQT_RGB554);
+	else if (frame->format == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0) ||
+	         frame->format == Graphics::PixelFormat(2, 5, 5, 5, 1, 10, 5, 0, 1))
+		ditherQuickTimeFrame<uint16>(*frame, *_ditherFrame, _ditherTable, readQT_RGB555);
 	else if (frame->format.bytesPerPixel == 2)
-		ditherQuickTimeFrame<uint16>(*frame, *_ditherFrame, _ditherTable);
+		ditherQuickTimeFrame<uint16>(*frame, *_ditherFrame, _ditherTable, readQT_RGB);
 	else if (frame->format.bytesPerPixel == 4)
-		ditherQuickTimeFrame<uint32>(*frame, *_ditherFrame, _ditherTable);
+		ditherQuickTimeFrame<uint32>(*frame, *_ditherFrame, _ditherTable, readQT_RGB);
 
 	return _ditherFrame;
 }
@@ -184,6 +197,10 @@ void DitherCodec::setDither(DitherType type, const byte *palette) {
 		_dirtyPalette = true;
 
 		_ditherTable = createQuickTimeDitherTable(_forcedDitherPalette, 256);
+
+		// Prefer RGB554 or RGB555 to avoid extra conversion when dithering
+		if (!_codec->setOutputPixelFormat(Graphics::PixelFormat(2, 5, 5, 4, 0, 9, 4, 0, 0)))
+			_codec->setOutputPixelFormat(Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
 	}
 }
 


Commit: 7294a5eff2da4732453f4e3d06391ab9f455c561
    https://github.com/scummvm/scummvm/commit/7294a5eff2da4732453f4e3d06391ab9f455c561
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2025-04-30T19:20:29+08:00

Commit Message:
IMAGE: Use the palette class in the dithered codec wrapper

Changed paths:
    image/codecs/dither.cpp
    image/codecs/dither.h


diff --git a/image/codecs/dither.cpp b/image/codecs/dither.cpp
index 87a97a19410..b0480cac49e 100644
--- a/image/codecs/dither.cpp
+++ b/image/codecs/dither.cpp
@@ -58,7 +58,6 @@ DitherCodec::~DitherCodec() {
 	if (_disposeAfterUse == DisposeAfterUse::YES)
 		delete _codec;
 
-	delete[] _forcedDitherPalette;
 	delete[] _ditherTable;
 
 	if (_ditherFrame) {
@@ -112,7 +111,7 @@ void ditherQuickTimeFrame(const Graphics::Surface &src, Graphics::Surface &dst,
 
 const Graphics::Surface *DitherCodec::decodeFrame(Common::SeekableReadStream &stream) {
 	const Graphics::Surface *frame = _codec->decodeFrame(stream);
-	if (!frame || !_forcedDitherPalette)
+	if (!frame || _forcedDitherPalette.empty())
 		return frame;
 
 	// TODO: Handle palettes that are owned by the container instead of the codec
@@ -124,7 +123,7 @@ const Graphics::Surface *DitherCodec::decodeFrame(Common::SeekableReadStream &st
 			return frame;
 
 		// If the palettes match, bail out
-		if (memcmp(_forcedDitherPalette, curPalette, 256 * 3) == 0)
+		if (memcmp(_forcedDitherPalette.data(), curPalette, 256 * 3) == 0)
 			return frame;
 	}
 
@@ -150,32 +149,32 @@ const Graphics::Surface *DitherCodec::decodeFrame(Common::SeekableReadStream &st
 }
 
 Graphics::PixelFormat DitherCodec::getPixelFormat() const {
-	if (!_forcedDitherPalette)
+	if (_forcedDitherPalette.empty())
 		return _codec->getPixelFormat();
 	return Graphics::PixelFormat::createFormatCLUT8();
 }
 
 bool DitherCodec::setOutputPixelFormat(const Graphics::PixelFormat &format) {
-	if (!_forcedDitherPalette)
+	if (_forcedDitherPalette.empty())
 		return _codec->setOutputPixelFormat(format);
 	return format.isCLUT8();
 }
 
 bool DitherCodec::containsPalette() const {
-	if (!_forcedDitherPalette)
+	if (_forcedDitherPalette.empty())
 		return _codec->containsPalette();
 	return true;
 }
 
 const byte *DitherCodec::getPalette() {
-	if (!_forcedDitherPalette)
+	if (_forcedDitherPalette.empty())
 		return _codec->getPalette();
 	_dirtyPalette = false;
-	return _forcedDitherPalette;
+	return _forcedDitherPalette.data();
 }
 
 bool DitherCodec::hasDirtyPalette() const {
-	if (!_forcedDitherPalette)
+	if (_forcedDitherPalette.empty())
 		return _codec->hasDirtyPalette();
 	return _dirtyPalette;
 }
@@ -189,14 +188,14 @@ void DitherCodec::setDither(DitherType type, const byte *palette) {
 		_codec->setDither(type, palette);
 	} else {
 		assert(type == kDitherTypeQT);
-		assert(!_forcedDitherPalette);
+		assert(_forcedDitherPalette.empty());
 
 		// Forced dither
-		_forcedDitherPalette = new byte[256 * 3];
-		memcpy(_forcedDitherPalette, palette, 256 * 3);
+		_forcedDitherPalette.resize(256, false);
+		_forcedDitherPalette.set(palette, 0, 256);
 		_dirtyPalette = true;
 
-		_ditherTable = createQuickTimeDitherTable(_forcedDitherPalette, 256);
+		_ditherTable = createQuickTimeDitherTable(_forcedDitherPalette.data(), 256);
 
 		// Prefer RGB554 or RGB555 to avoid extra conversion when dithering
 		if (!_codec->setOutputPixelFormat(Graphics::PixelFormat(2, 5, 5, 4, 0, 9, 4, 0, 0)))
diff --git a/image/codecs/dither.h b/image/codecs/dither.h
index 1275154c5cc..b4aef09c049 100644
--- a/image/codecs/dither.h
+++ b/image/codecs/dither.h
@@ -25,6 +25,7 @@
 #include "image/codecs/codec.h"
 
 #include "common/types.h"
+#include "graphics/palette.h"
 
 namespace Image {
 
@@ -54,7 +55,7 @@ private:
 	Codec *_codec;
 
 	Graphics::Surface *_ditherFrame;
-	byte *_forcedDitherPalette;
+	Graphics::Palette _forcedDitherPalette;
 	byte *_ditherTable;
 	bool _dirtyPalette;
 };




More information about the Scummvm-git-logs mailing list