⛏️ index : haiku.git

/*
 * Copyright 2013, Gerasim Troeglazov, 3dEyes@gmail.com. All rights reserved.
 * Distributed under the terms of the MIT License.
 */ 


#include "PSDLoader.h"

#include <Catalog.h>

#include "BaseTranslator.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PSDLoader"


PSDLoader::PSDLoader(BPositionIO *src)
{
	fLoaded = false;
	fStream = src;

	fStream->Seek(0, SEEK_END);
	fStreamSize = fStream->Position();
	fStream->Seek(0, SEEK_SET);

	if (fStreamSize <= 0)
		return;

	fStream->Seek(0, SEEK_SET);

	fSignature = _GetInt32FromStream(fStream);
	if (fSignature != 0x38425053) // 8BPS
		return;

	fVersion = _GetInt16FromStream(fStream);
	
	// Skip reserved data
	_SkipStreamBlock(fStream, 6);

	fChannels = _GetInt16FromStream(fStream);
	fHeight = _GetInt32FromStream(fStream);
	fWidth = _GetInt32FromStream(fStream);
	fDepth = _GetInt16FromStream(fStream);	
	fColorFormat = _GetInt16FromStream(fStream);

	fColorModeDataSize = _GetInt32FromStream(fStream);
	fColorModeDataPos = fStream->Position();
	_SkipStreamBlock(fStream, fColorModeDataSize);

	fImageResourceSectionSize = _GetInt32FromStream(fStream);
	fImageResourceSectionPos = fStream->Position();
	_SkipStreamBlock(fStream, fImageResourceSectionSize);

	// Skip [layer and mask] block
	if (fVersion == PSD_FILE)
		_SkipStreamBlock(fStream, _GetInt32FromStream(fStream));
	else if (fVersion == PSB_FILE)
		_SkipStreamBlock(fStream, _GetInt64FromStream(fStream));
	else
		return;

	fCompression = _GetInt16FromStream(fStream);

	fStreamPos = fStream->Position();

	fLoaded = true;
}


PSDLoader::~PSDLoader()
{
}


bool
PSDLoader::IsLoaded(void)
{
	return fLoaded;
}


bool
PSDLoader::IsSupported(void)
{
	if (!fLoaded)
		return false;

	if (fVersion != PSD_FILE
		&& fVersion != PSB_FILE) {
		return false;
	}

	if (fChannels < 0 || fChannels > PSD_MAX_CHANNELS)
		return false;

	if (fDepth > 16)
		return false;

	if (_ColorFormat() == PSD_COLOR_FORMAT_UNSUPPORTED)
		return false;

	if (fCompression != PSD_COMPRESSED_RAW
		&& fCompression != PSD_COMPRESSED_RLE) {
		return false;
	}

	return true;
}


BString
PSDLoader::ColorFormatName(void)
{
	switch (fColorFormat) {
		case PSD_COLOR_MODE_BITS:
			return B_TRANSLATE("Bitmap");
		case PSD_COLOR_MODE_GRAYSCALE:
			return B_TRANSLATE("Grayscale");
		case PSD_COLOR_MODE_INDEXED:
			return B_TRANSLATE("Indexed");
		case PSD_COLOR_MODE_RGB:
			return fChannels > 3 ? B_TRANSLATE("RGBA") : B_TRANSLATE("RGB");
		case PSD_COLOR_MODE_CMYK:
			return B_TRANSLATE("CMYK");
		case PSD_COLOR_MODE_MULTICHANNEL:
			return B_TRANSLATE("Multichannel");
		case PSD_COLOR_MODE_LAB:
			return B_TRANSLATE("Lab");
		case PSD_COLOR_MODE_DUOTONE:
			return B_TRANSLATE("Duotone");
	}
	return "";
}


psd_color_format
PSDLoader::_ColorFormat(void)
{
	psd_color_format format = PSD_COLOR_FORMAT_UNSUPPORTED;
	if (!fLoaded)
		return format;

	switch (fColorFormat) {
		case PSD_COLOR_MODE_BITS:
			format = PSD_COLOR_FORMAT_BITMAP;
			break;
		case PSD_COLOR_MODE_RGB:
			if (fChannels == 3)
				format = PSD_COLOR_FORMAT_RGB;
			else if (fChannels >= 4)
				format = PSD_COLOR_FORMAT_RGB_A;
			break;
		case PSD_COLOR_MODE_GRAYSCALE:
			if (fChannels == 1)
				format = PSD_COLOR_FORMAT_GRAY;
			else if (fChannels == 2)
				format = PSD_COLOR_FORMAT_GRAY_A;
			break;		
		case PSD_COLOR_MODE_MULTICHANNEL:
			if (fChannels == 3)
				format = PSD_COLOR_FORMAT_MULTICHANNEL;
			break;
		case PSD_COLOR_MODE_CMYK:
			if (fChannels == 3)
				format = PSD_COLOR_FORMAT_MULTICHANNEL;
			else if (fChannels == 4)
				format = PSD_COLOR_FORMAT_CMYK;
			else if (fChannels > 4)
				format = PSD_COLOR_FORMAT_CMYK_A;
			break;
		case PSD_COLOR_MODE_LAB:
			if (fChannels == 3)
				format = PSD_COLOR_FORMAT_LAB;
			else if (fChannels > 3)
				format = PSD_COLOR_FORMAT_LAB_A;
			break;
		case PSD_COLOR_MODE_DUOTONE:
			if (fChannels >= 1)
				format = PSD_COLOR_FORMAT_DUOTONE;
			break;
		case PSD_COLOR_MODE_INDEXED:
			if (fChannels >= 1 && fColorModeDataSize >= 3)
				format = PSD_COLOR_FORMAT_INDEXED;
			break;
		default:
			break;
	};

	return format;
}


status_t
PSDLoader::Decode(BPositionIO *target)
{
	if (!IsSupported())
		return B_NO_TRANSLATOR;

	fStreamBuffer = new uint8[fStreamSize];
	fStream->Seek(0, SEEK_SET);
	fStream->Read(fStreamBuffer, fStreamSize);

	int32 depthBytes = fDepth / 8;
	int32 rowBytes = (fWidth * fDepth) / 8;
	int32 channelBytes = rowBytes * fHeight;
	
	uint8 *imageData[PSD_MAX_CHANNELS];
	for (int i = 0; i < fChannels; i++)
		imageData[i] = new uint8[channelBytes];

	
	switch (fCompression) {
		case PSD_COMPRESSED_RAW:
		{
			for (int channelIdx = 0; channelIdx < fChannels; channelIdx++) {
				uint8 *ptr = imageData[channelIdx];
				for (int i = 0; i < channelBytes; i++, ptr++)
					*ptr = (uint8)fStreamBuffer[fStreamPos++];
			}
			break;
		}
		case PSD_COMPRESSED_RLE:
		{
			if (fVersion == PSD_FILE)
				fStreamPos += fHeight * fChannels * 2;
			else if (fVersion == PSB_FILE)
				fStreamPos += fHeight * fChannels * 4;

			for (int channelIdx = 0; channelIdx < fChannels; channelIdx++) {
				uint8 *ptr = imageData[channelIdx];
				// Read the RLE data.
				int count = 0;
				while (count < channelBytes) {
					uint8 len = (uint8)fStreamBuffer[fStreamPos++];
					if (len == 128) {
						continue;
					} else if (len < 128) {
						len++;
						count += len;
						while (len) {
							*ptr++ = (int8)fStreamBuffer[fStreamPos++];
							len--;
						}
					} else if (len > 128) {
						int8 val = (int8)fStreamBuffer[fStreamPos++];
						len ^= 255;
						len += 2;
						count += len;
						while (len) {
							*ptr++ = val;
							len--;
						}
					}
				}
			}
			break;
		}
		default:
			delete[] fStreamBuffer;
			for (int i = 0; i < fChannels; i++)
				delete[] imageData[i];
			return 	B_NO_TRANSLATOR;
	}

	delete[] fStreamBuffer;

	TranslatorBitmap bitsHeader;
	bitsHeader.magic = B_TRANSLATOR_BITMAP;
	bitsHeader.bounds.left = 0;
	bitsHeader.bounds.top = 0;
	bitsHeader.bounds.right = fWidth - 1;
	bitsHeader.bounds.bottom = fHeight - 1;

	psd_color_format colorFormat = _ColorFormat();

	if (colorFormat == PSD_COLOR_FORMAT_BITMAP) {
		bitsHeader.rowBytes = rowBytes;
		bitsHeader.dataSize = channelBytes;
		bitsHeader.colors = B_GRAY1;
	} else {
		bitsHeader.rowBytes = sizeof(uint32) * fWidth;
		bitsHeader.colors = B_RGBA32;
		bitsHeader.dataSize = bitsHeader.rowBytes * fHeight;
	}

	if (swap_data(B_UINT32_TYPE, &bitsHeader,
		sizeof(TranslatorBitmap), B_SWAP_HOST_TO_BENDIAN) != B_OK) {
		return B_NO_TRANSLATOR;
	}

	target->Write(&bitsHeader, sizeof(TranslatorBitmap));
	
	uint8 *lineData = new uint8[fWidth * sizeof(uint32)];
		
	switch (colorFormat) {
		case PSD_COLOR_FORMAT_BITMAP:
		{			
			int32 rowBytes = (fWidth / 8 ) * fHeight;
			for (int32 i = 0; i < rowBytes; i++)
				imageData[0][i]^=255;
			target->Write(imageData[0], rowBytes);
			break;
		}
		case PSD_COLOR_FORMAT_INDEXED:
		{
			int32 paletteSize = fColorModeDataSize / 3;		

			uint8 *colorData = new uint8[fColorModeDataSize];
			fStream->Seek(fColorModeDataPos, SEEK_SET);
			fStream->Read(colorData, fColorModeDataSize);
			
			if (_ParseImageResources() != B_OK)
				fTransparentIndex = 256;

			uint8 *redPalette = colorData;
			uint8 *greenPalette = colorData + paletteSize;
			uint8 *bluePalette = colorData + paletteSize * 2;
			int32 index = 0;
			for (int h = 0; h < fHeight; h++) {
				uint8 *ptr = lineData;
				for (int w = 0; w < fWidth; w++) {
					uint8 colorIndex = imageData[0][index];
					ptr[0] = bluePalette[colorIndex];
					ptr[1] = greenPalette[colorIndex];
					ptr[2] = redPalette[colorIndex];
					ptr[3] = colorIndex == fTransparentIndex ? 0 : 255;

					ptr += sizeof(uint32);
					index++;
				}
				target->Write(lineData, fWidth * sizeof(uint32));
			}
			delete[] colorData;
			break;
		}
		case PSD_COLOR_FORMAT_DUOTONE:
		case PSD_COLOR_FORMAT_GRAY:
		case PSD_COLOR_FORMAT_GRAY_A:
		{
			bool isAlpha = colorFormat == PSD_COLOR_FORMAT_GRAY_A;
			int32 index = 0;
			for (int h = 0; h < fHeight; h++) {
				uint8 *ptr = lineData;
				for (int w = 0; w < fWidth; w++) {
					ptr[0] = imageData[0][index];
					ptr[1] = imageData[0][index];
					ptr[2] = imageData[0][index];
					ptr[3] = isAlpha ? imageData[1][index] : 255;

					ptr += sizeof(uint32);
					index += depthBytes;
				}
				target->Write(lineData, fWidth * sizeof(uint32));
			}
			break;
		}
		case PSD_COLOR_FORMAT_MULTICHANNEL:
		case PSD_COLOR_FORMAT_RGB:
		case PSD_COLOR_FORMAT_RGB_A:
		{
			bool isAlpha = colorFormat == PSD_COLOR_FORMAT_RGB_A;
			int32 index = 0;
			for (int h = 0; h < fHeight; h++) {
				uint8 *ptr = lineData;
				for (int w = 0; w < fWidth; w++) {
					ptr[0] = imageData[2][index];
					ptr[1] = imageData[1][index];
					ptr[2] = imageData[0][index];
					ptr[3] = isAlpha ? imageData[3][index] : 255;

					ptr += sizeof(uint32);
					index += depthBytes;
				}
				target->Write(lineData, fWidth * sizeof(uint32));
			}
			break;
		}
		case PSD_COLOR_FORMAT_CMYK:
		case PSD_COLOR_FORMAT_CMYK_A:
		{
			bool isAlpha = colorFormat == PSD_COLOR_FORMAT_CMYK_A;
			int32 index = 0;
			for (int h = 0; h < fHeight; h++) {
				uint8 *ptr = lineData;
				for (int w = 0; w < fWidth; w++) {
					double c = 1.0 - imageData[0][index] / 255.0;
					double m = 1.0 - imageData[1][index] / 255.0;
					double y = 1.0 - imageData[2][index] / 255.0;
					double k = 1.0 - imageData[3][index] / 255.0;
					ptr[0] = (uint8)((1.0 - (y * (1.0 - k) + k)) * 255.0);
					ptr[1] = (uint8)((1.0 - (m * (1.0 - k) + k)) * 255.0);
					ptr[2] = (uint8)((1.0 - (c * (1.0 - k) + k)) * 255.0);
					ptr[3] = isAlpha ?  imageData[4][index] : 255;
					
					ptr += sizeof(uint32);
					index += depthBytes;
				}
				target->Write(lineData, fWidth * sizeof(uint32));
			}
			break;
		}
		case PSD_COLOR_FORMAT_LAB:
		case PSD_COLOR_FORMAT_LAB_A:
		{
			bool isAlpha = colorFormat == PSD_COLOR_FORMAT_LAB_A;
			int32 index = 0;
			for (int h = 0; h < fHeight; h++) {
				uint8 *ptr = lineData;
				for (int w = 0; w < fWidth; w++) {
					double L = imageData[0][index] / 255.0 * 100.0;
					double a = imageData[1][index] - 128.0;
					double b = imageData[2][index] - 128.0;

					double Y = L * (1.0 / 116.0) + 16.0 / 116.0;
					double X = a * (1.0 / 500.0) + Y;
					double Z = b * (-1.0 / 200.0) + Y;

					X = X > 6.0 / 29.0 ? X * X * X : X * (108.0 / 841.0)
						- (432.0 / 24389.0);
					Y = L > 8.0 ? Y * Y * Y : L * (27.0 / 24389.0);
					Z = Z > 6.0 / 29.0 ? Z * Z * Z : Z * (108.0 / 841.0)
						- (432.0 / 24389.0);

					double R = X * (1219569.0 / 395920.0)
						+ Y * (-608687.0 / 395920.0)
						+ Z * (-107481.0 / 197960.0);
					double G = X * (-80960619.0 / 87888100.0)
						+ Y * (82435961.0 / 43944050.0)
						+ Z * (3976797.0 / 87888100.0);
					double B = X * (93813.0 / 1774030.0)
						+ Y * (-180961.0 / 887015.0)
						+ Z * (107481.0 / 93370.0);

					R = R > 0.0031308 ?	pow(R, 1.0 / 2.4) * 1.055 - 0.055
						: R * 12.92;
					G = G > 0.0031308 ?	pow(G, 1.0 / 2.4) * 1.055 - 0.055
						: G * 12.92;
					B = B > 0.0031308 ?	pow(B, 1.0 / 2.4) * 1.055 - 0.055
						: B * 12.92;

					R = (R < 0) ? 0 : ((R > 1) ? 1 : R);
					G = (G < 0) ? 0 : ((G > 1) ? 1 : G);
					B = (B < 0) ? 0 : ((B > 1) ? 1 : B);

					ptr[0] = (uint8)(B * 255.0);
					ptr[1] = (uint8)(G * 255.0);
					ptr[2] = (uint8)(R * 255.0);
					ptr[3] = isAlpha ? imageData[3][index] : 255;

					ptr += sizeof(uint32);
					index += depthBytes;
				}
				target->Write(lineData, fWidth * sizeof(uint32));
			}
			break;
		}
		default:
			break;
	};

	delete[] lineData;
	for (int i = 0; i < fChannels; i++)
		delete[] imageData[i];

	return B_OK;
}


int64
PSDLoader::_GetInt64FromStream(BPositionIO *in)
{
	int64 ret;
	in->Read(&ret, sizeof(int64));
	return B_BENDIAN_TO_HOST_INT64(ret);
}


int32
PSDLoader::_GetInt32FromStream(BPositionIO *in)
{
	int32 ret;
	in->Read(&ret, sizeof(int32));
	return B_BENDIAN_TO_HOST_INT32(ret);
}


int16
PSDLoader::_GetInt16FromStream(BPositionIO *in)
{
	int16 ret;
	in->Read(&ret, sizeof(int16));
	return B_BENDIAN_TO_HOST_INT16(ret);
}


int8
PSDLoader::_GetInt8FromStream(BPositionIO *in)
{
	int8 ret;
	in->Read(&ret, sizeof(int8));
	return ret;
}


uint8
PSDLoader::_GetUInt8FromStream(BPositionIO *in)
{
	uint8 ret;
	in->Read(&ret, sizeof(uint8));
	return ret;
}


void
PSDLoader::_SkipStreamBlock(BPositionIO *in, size_t count)
{	
	in->Seek(count, SEEK_CUR);
}


status_t
PSDLoader::_ParseImageResources(void)
{
	if (!fLoaded && fImageResourceSectionSize == 0)
		return B_ERROR;

	size_t currentPos = fStream->Position();
	fStream->Seek(fImageResourceSectionPos, SEEK_SET);
	
	while (fStream->Position() < currentPos + fImageResourceSectionSize) {
		int32 resBlockSignature = _GetInt32FromStream(fStream);		
		if (resBlockSignature != 0x3842494D) // 8BIM
			return B_ERROR;
	
		uint16 resID = _GetInt16FromStream(fStream);	
			
		BString resName, name;		
		int nameLength = 0;
		while (true) {
			int charData = _GetUInt8FromStream(fStream);
			nameLength++;
			if (charData == 0) {
				if (nameLength % 2 == 1) {
					_GetUInt8FromStream(fStream);
					nameLength++;
				}
				break;
			} else
				name += charData;
			resName = name;
		}

		uint32 resSize = _GetInt32FromStream(fStream);

		if (resSize % 2 == 1)
			resSize++;

		switch (resID) {
			case 0x0417:
				fTransparentIndex = _GetInt16FromStream(fStream);
				break;
			default:
				_SkipStreamBlock(fStream, resSize);
		}
	}

	fStream->Seek(currentPos, SEEK_SET);
	
	return B_OK;
}