⛏️ index : haiku.git

////////////////////////////////////////////////////////////////////////////////
//
//	File: GIFLoad.cpp
//
//	Date: December 1999
//
//	Author: Daniel Switkin
//
//	Copyright 2003 (c) by Daniel Switkin. This file is made publically available
//	under the BSD license, with the stipulations that this complete header must
//	remain at the top of the file indefinitely, and credit must be given to the
//	original author in any about box using this software.
//
////////////////////////////////////////////////////////////////////////////////

// Additional authors:	John Scipione, <jscipione@gmail.com>


#include "GIFLoad.h"

#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>

#include <new>

#include <ByteOrder.h>
#include <InterfaceDefs.h>
#include <TranslatorFormats.h>

#include "GIFPrivate.h"


extern bool debug;


GIFLoad::GIFLoad(BPositionIO* input, BPositionIO* output)
	:
	fatalerror(false),
	fInput(input),
	fOutput(output),
	fPalette(NULL),
 	fHeadMemblock(NULL),
	fScanLine(NULL)
{
	fInput->Seek(0, SEEK_SET);

	if (!ReadGIFHeader()) {
		fatalerror = true;
		return;
	}

	if (debug) {
		syslog(LOG_INFO, "GIFLoad::GIFLoad() - Image dimensions are %d x %d\n",
			fWidth, fHeight);
	}

	unsigned char c;
	if (fInput->Read(&c, 1) < 1) {
		fatalerror = true;
		return;
	}

	while (c != TERMINATOR_INTRODUCER) {
		if (c == DESCRIPTOR_INTRODUCER) {
			if ((!ReadGIFImageHeader()) || (!ReadGIFImageData())) {
				if (debug) {
					syslog(LOG_ERR, "GIFLoad::GIFLoad() - "
						"A fatal error occurred\n");
				}

				fatalerror = true;
			} else {
				if (debug) {
					syslog(LOG_INFO, "GIFLoad::GIFLoad() - "
						"Found a single image and leaving\n");
				}
			}
			free(fScanLine);
			fScanLine = NULL;
			return;
		} else if (c == EXTENSION_INTRODUCER) {
			unsigned char d;
			if (fInput->Read(&d, 1) < 1) {
				fatalerror = true;
				return;
			}
			if (d == LOOP_BLOCK_LABEL) {
				if (!ReadGIFLoopBlock()) {
					fatalerror = true;
					return;
				}
			} else if (d == GRAPHIC_CONTROL_LABEL) {
				if (!ReadGIFControlBlock()) {
					fatalerror = true;
					return;
				}
			} else if (d == COMMENT_EXTENSION_LABEL) {
				if (!ReadGIFCommentBlock()) {
					fatalerror = true;
					return;
				}
			} else {
				if (!ReadGIFUnknownBlock(d)) {
					fatalerror = true;
					return;
				}
			}
		} else if (c != BLOCK_TERMINATOR) {
			if (!ReadGIFUnknownBlock(c)) {
				fatalerror = true;
				return;
			}
		}

		if (fInput->Read(&c, 1) < 1) {
			fatalerror = true;
			return;
		}
	}

	if (debug)
		syslog(LOG_INFO, "GIFLoad::GIFLoad() - Done\n");
}


GIFLoad::~GIFLoad()
{
	delete fPalette;
}


bool
GIFLoad::ReadGIFHeader()
{
	// standard header
	unsigned char header[13];
	if (fInput->Read(header, 13) < 13)
		return false;

	fWidth = header[6] + (header[7] << 8);
	fHeight = header[8] + (header[9] << 8);

	fPalette = new(std::nothrow) LoadPalette();
	if (fPalette == NULL)
		return false;

	// Global palette
	if (header[10] & GIF_LOCALCOLORMAP) {
		fPalette->size_in_bits = (header[10] & 0x07) + 1;
		if (debug) {
			syslog(LOG_INFO, "GIFLoad::ReadGIFHeader() - "
				"Found %d bit global palette\n",
				fPalette->size_in_bits);
		}
		int s = 1 << fPalette->size_in_bits;
		fPalette->size = s;

		unsigned char gp[256 * 3];
		if (fInput->Read(gp, s * 3) < s * 3)
			return false;

		for (int x = 0; x < s; x++)
			fPalette->SetColor(x, gp[x * 3], gp[x * 3 + 1], gp[x * 3 + 2]);

		fPalette->backgroundindex = header[11];
	} else {
		// install BeOS system palette in case local palette isn't present
		color_map* map = (color_map*)system_colors();
		for (int x = 0; x < 256; x++) {
			fPalette->SetColor(x, map->color_list[x].red,
				map->color_list[x].green, map->color_list[x].blue);
		}
		fPalette->size = 256;
		fPalette->size_in_bits = 8;
	}

	return true;
}


bool
GIFLoad::ReadGIFLoopBlock()
{
	unsigned char length;
	if (fInput->Read(&length, 1) < 1)
		return false;

	fInput->Seek(length, SEEK_CUR);
	do {
		if (fInput->Read(&length, 1) < 1)
			return false;

		fInput->Seek(length, SEEK_CUR);
	} while (length != 0);

	return true;
}


bool
GIFLoad::ReadGIFControlBlock()
{
	unsigned char data[6];
	if (fInput->Read(data, 6) < 6)
		return false;

	if ((data[1] & 0x01) != 0) {
		fPalette->usetransparent = true;
		fPalette->transparentindex = data[4];
		if (debug) {
			syslog(LOG_INFO, "GIFLoad::ReadGIFControlBlock() - "
				"Transparency active, using palette index %d\n", data[4]);
		}
	}

	return true;
}


bool
GIFLoad::ReadGIFCommentBlock()
{
	if (debug)
		syslog(LOG_INFO, "GIFLoad::ReadGIFCommentBlock() - Found:\n");

	unsigned char length;
	char comment_data[256];
	do {
		if (fInput->Read(&length, 1) < 1)
			return false;

		if (fInput->Read(comment_data, length) < length)
			return false;

		comment_data[length] = BLOCK_TERMINATOR;
		if (debug)
			syslog(LOG_INFO, "%s", comment_data);
	} while (length != BLOCK_TERMINATOR);

	if (debug)
		syslog(LOG_INFO, "\n");

	return true;
}


bool
GIFLoad::ReadGIFUnknownBlock(unsigned char c)
{
	if (debug)
		syslog(LOG_INFO, "GIFLoad::ReadGIFUnknownBlock() - Found: %d\n", c);

	unsigned char length;
	do {
		if (fInput->Read(&length, 1) < 1)
			return false;

		fInput->Seek(length, SEEK_CUR);
	} while (length != BLOCK_TERMINATOR);

	return true;
}


bool
GIFLoad::ReadGIFImageHeader()
{
	unsigned char data[9];
	if (fInput->Read(data, 9) < 9)
		return false;

	int left = data[0] + (data[1] << 8);
	int top = data[2] + (data[3] << 8);
	int localWidth = data[4] + (data[5] << 8);
	int localHeight = data[6] + (data[7] << 8);
	if (fWidth != localWidth || fHeight != localHeight) {
		if (debug) {
			syslog(LOG_ERR, "GIFLoad::ReadGIFImageHeader() - "
				"Local dimensions do not match global, setting to %d x %d\n",
				localWidth, localHeight);
		}
		fWidth = localWidth;
		fHeight = localHeight;
	}

	fScanLine = (uint32*)malloc(fWidth * 4);
	if (fScanLine == NULL) {
		if (debug) {
			syslog(LOG_ERR, "GIFLoad::ReadGIFImageHeader() - "
				"Could not allocate scanline\n");
		}
		return false;
	}

	BRect rect(left, top, left + fWidth - 1, top + fHeight - 1);
	TranslatorBitmap header;
	header.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
	header.bounds.left = B_HOST_TO_BENDIAN_FLOAT(rect.left);
	header.bounds.top = B_HOST_TO_BENDIAN_FLOAT(rect.top);
	header.bounds.right = B_HOST_TO_BENDIAN_FLOAT(rect.right);
	header.bounds.bottom = B_HOST_TO_BENDIAN_FLOAT(rect.bottom);
	header.rowBytes = B_HOST_TO_BENDIAN_INT32(fWidth * 4);
	header.colors = (color_space)B_HOST_TO_BENDIAN_INT32(B_RGBA32);
	header.dataSize = B_HOST_TO_BENDIAN_INT32(fWidth * 4 * fHeight);
	if (fOutput->Write(&header, 32) < 32)
		return false;

	if (data[8] & GIF_LOCALCOLORMAP) {
		// has local palette
		fPalette->size_in_bits = (data[8] & 0x07) + 1;
		int s = 1 << fPalette->size_in_bits;
		fPalette->size = s;
		if (debug) {
			syslog(LOG_INFO, "GIFLoad::ReadGIFImageHeader() - "
				"Found %d bit local palette\n", fPalette->size_in_bits);
		}

		unsigned char lp[256 * 3];
		if (fInput->Read(lp, s * 3) < s * 3)
			return false;

		for (int x = 0; x < s; x++)
			fPalette->SetColor(x, lp[x * 3], lp[x * 3 + 1], lp[x * 3 + 2]);
	}

	fInterlaced = data[8] & GIF_INTERLACED;
	if (debug) {
		if (fInterlaced) {
			syslog(LOG_INFO, "GIFLoad::ReadGIFImageHeader() - "
				"Image is interlaced\n");
		} else {
			syslog(LOG_INFO, "GIFLoad::ReadGIFImageHeader() - "
				"Image is not interlaced\n");
		}
	}

	return true;
}


bool
GIFLoad::ReadGIFImageData()
{
	unsigned char newEntry[ENTRY_COUNT];
	unsigned char codeSize;
	if (fInput->Read(&codeSize, 1) < 1)
		return false;

	if (codeSize > fPalette->size_in_bits) {
		if (debug) {
			syslog(LOG_ERR, "GIFLoad::ReadGIFImageData() - "
				"Code_size should be %d, not %d, allowing it\n",
				fCodeSize, codeSize);
		}
		if (!InitFrame(codeSize))
			return false;
	} else if (codeSize < fPalette->size_in_bits) {
		if (debug) {
			syslog(LOG_ERR, "GIFLoad::ReadGIFImageData() - "
				"Code_size should be %d, not %d\n", fCodeSize, codeSize);
		}
		return false;
	} else if (!InitFrame(fPalette->size_in_bits))
		return false;

	if (debug)
		syslog(LOG_INFO, "GIFLoad::ReadGIFImageData() - Starting LZW\n");

	while ((fNewCode = NextCode()) != -1 && fNewCode != fEndCode) {
		if (fNewCode == fClearCode) {
			ResetTable();
			fNewCode = NextCode();
			fOldCode[0] = fNewCode;
			fOldCodeLength = 1;
			if (!OutputColor(fOldCode, 1))
				goto bad_end;

			if (fNewCode == -1 || fNewCode == fEndCode) {
				if (debug) {
					syslog(LOG_ERR, "GIFLoad::ReadGIFImageData() - "
						"Premature fEndCode or error reading fNewCode\n");
				}
				goto bad_end;
			}
			continue;
		}

		// explicitly check for lack of clear code at start of file
		if (fOldCodeLength == 0) {
			fOldCode[0] = fNewCode;
			fOldCodeLength = 1;
			if (!OutputColor(fOldCode, 1))
				goto bad_end;

			continue;
		}

		// error out if we're trying to access an out-of-bounds index
		if (fNextCode >= ENTRY_COUNT)
			goto bad_end;

		if (fTable[fNewCode] != NULL) {
			// exists in table

			if (!OutputColor(fTable[fNewCode], fEntrySize[fNewCode]))
				goto bad_end;

			//memcpy(newEntry, fOldCode, fOldCodeLength);
			for (unsigned int x = 0; x < fOldCodeLength; x++)
				newEntry[x] = fOldCode[x];

			//memcpy(newEntry + fOldCodeLength, fTable[fNewCode], 1);
			newEntry[fOldCodeLength] = fTable[fNewCode][0];
		} else {
			// does not exist in table

			//memcpy(newEntry, fOldCode, fOldCodeLength);
			for (unsigned int x = 0; x < fOldCodeLength; x++)
				newEntry[x] = fOldCode[x];

			//memcpy(newEntry + fOldCodeLength, fOldCode, 1);
			newEntry[fOldCodeLength] = fOldCode[0];

			if (!OutputColor(newEntry, fOldCodeLength + 1))
				goto bad_end;
		}
		fTable[fNextCode] = MemblockAllocate(fOldCodeLength + 1);
		if (fTable[fNextCode] == NULL)
			goto bad_end;

		//memcpy(fTable[fNextCode], newEntry, fOldCodeLength + 1);
		for (unsigned int x = 0; x < fOldCodeLength + 1; x++)
			fTable[fNextCode][x] = newEntry[x];

		fEntrySize[fNextCode] = fOldCodeLength + 1;

		//memcpy(fOldCode, fTable[fNewCode], fEntrySize[fNewCode]);
		for (int x = 0; x < fEntrySize[fNewCode]; x++)
			fOldCode[x] = fTable[fNewCode][x];

		fOldCodeLength = fEntrySize[fNewCode];
		fNextCode++;

		if (fNextCode > fMaxCode && fBits < LZ_MAX_BITS) {
			fBits++;
			fMaxCode = (1 << fBits) - 1;
		}
	}

	MemblockDeleteAll();
	if (fNewCode == -1)
		return false;

	if (debug)
		syslog(LOG_INFO, "GIFLoad::ReadGIFImageData() - Done\n");

	return true;

bad_end:
	if (debug)
		syslog(LOG_ERR, "GIFLoad::ReadGIFImageData() - Reached a bad end\n");

	MemblockDeleteAll();
	return false;
}


short
GIFLoad::NextCode()
{
	while (fBitCount < fBits) {
		if (fByteCount == 0) {
			if (fInput->Read(&fByteCount, 1) < 1)
				return -1;

			if (fByteCount == 0)
				return fEndCode;

			if (fInput->Read(fByteBuffer + (255 - fByteCount), fByteCount)
					< fByteCount) {
				return -1;
			}
		}
		fBitBuffer |= (unsigned int)fByteBuffer[255 - fByteCount] << fBitCount;
		fByteCount--;
		fBitCount += 8;
	}

	short s = fBitBuffer & ((1 << fBits) - 1);
	fBitBuffer >>= fBits;
	fBitCount -= fBits;

	return s;
}


void
GIFLoad::ResetTable()
{
	fBits = fCodeSize + 1;
	fNextCode = fClearCode + 2;
	fMaxCode = (1 << fBits) - 1;

	MemblockDeleteAll();
	for (int x = 0; x < ENTRY_COUNT; x++) {
		fTable[x] = NULL;
		if (x < (1 << fCodeSize)) {
			fTable[x] = MemblockAllocate(1);
			if (fTable[x] != NULL) {
				fTable[x][0] = x;
				fEntrySize[x] = 1;
			}
		}
	}
}


bool
GIFLoad::InitFrame(int codeSize)
{
	fCodeSize = codeSize;
	if (fCodeSize == 1)
		fCodeSize++;

	fBits = fCodeSize + 1;
	fClearCode = 1 << fCodeSize;
	fEndCode = fClearCode + 1;
	fNextCode = fClearCode + 2;
	fMaxCode = (1 << fBits) - 1;

	fPass = 0;

	if (fInterlaced)
		fRow = gl_pass_starts_at[0];
	else
		fRow = 0;

	fBitCount = 0;
	fBitBuffer = 0;
	fByteCount = 0;
	fOldCodeLength = 0;
	fNewCode = 0;
	fScanlinePosition = 0;

	ResetTable();
	return true;
}


unsigned char*
GIFLoad::MemblockAllocate(int size)
{
	// Do 4k mallocs, keep them in a linked list, do a first fit across
	// them when a new request comes along.

	if (fHeadMemblock == NULL) {
		fHeadMemblock = (Memblock*)malloc(sizeof(Memblock));
		if (fHeadMemblock == NULL)
			return NULL;

		unsigned char* value = fHeadMemblock->data;
		fHeadMemblock->offset = size;
		fHeadMemblock->next = NULL;

		return value;
	} else {
		Memblock* block = fHeadMemblock;
		Memblock* last = NULL;
		while (block != NULL) {
			if (ENTRY_COUNT - block->offset > size) {
				unsigned char* value = block->data + block->offset;
				block->offset += size;

				return value;
			}
			last = block;
			block = block->next;
		}

		block = (Memblock*)malloc(sizeof(Memblock));
		if (block == NULL)
			return NULL;

		unsigned char* value = block->data;
		block->offset = size;
		block->next = NULL;
		if (last != NULL)
			last->next = block;

		return value;
	}
}


void
GIFLoad::MemblockDeleteAll()
{
	Memblock* block = NULL;

	while (fHeadMemblock != NULL) {
		// delete the linked list
		block = fHeadMemblock->next;
		free(fHeadMemblock);
		fHeadMemblock = block;
	}
}


bool
GIFLoad::OutputColor(unsigned char* string, int size)
{
	int bpr = fWidth << 2;

	for (int x = 0; x < size; x++) {
		fScanLine[fScanlinePosition] = fPalette->ColorForIndex(string[x]);
		fScanlinePosition++;

		if (fScanlinePosition >= fWidth) {
			if (fOutput->WriteAt(32 + (fRow * bpr), fScanLine, bpr) < bpr)
				return false;

			fScanlinePosition = 0;
			if (fInterlaced) {
				fRow += gl_increment_pass_by[fPass];
				while (fRow >= fHeight) {
					fPass++;
					if (fPass > 3)
						return true;

					fRow = gl_pass_starts_at[fPass];
				}
			} else
				fRow++;
		}
	}

	return true;
}