/* 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 . * */ #include "ultima/nuvie/core/nuvie_defs.h" #include "ultima/nuvie/files/nuvie_bmp_file.h" namespace Ultima { namespace Nuvie { #define NUVIEBMPFILE_MAGIC 0x4d42 // 'BM' NuvieBmpFile::NuvieBmpFile() : data(nullptr), prev_width(0), prev_height(0), prev_bits(0), bmp_line_width(0) { memset(&header, 0, sizeof(header)); memset(&infoHeader, 0, sizeof(infoHeader)); ARRAYCLEAR(palette); } NuvieBmpFile::~NuvieBmpFile() { if (data != nullptr) free(data); } bool NuvieBmpFile::initNewBlankImage(sint32 width, sint32 height, const unsigned char *pal) { infoHeader.size = 40; infoHeader.width = width; infoHeader.height = height; infoHeader.planes = 1; infoHeader.bits = 8; infoHeader.compression = 0; infoHeader.imagesize = 0; infoHeader.xresolution = 0; //FIXME infoHeader.yresolution = 0; //FIXME infoHeader.ncolours = 256; infoHeader.importantcolours = 256; bmp_line_width = infoHeader.width; if (bmp_line_width % 4 != 0) { bmp_line_width += (4 - (bmp_line_width % 4)); } header.type = NUVIEBMPFILE_MAGIC; header.reserved1 = 0; header.reserved2 = 0; header.offset = NUVIEBMP_HEADER_SIZE + NUVIEBMP_INFOHEADER_SIZE + 256 * 4; header.size = header.offset + bmp_line_width * infoHeader.height; memcpy(&palette, pal, sizeof(palette)); data = (unsigned char *)malloc(infoHeader.width * infoHeader.height); if (!data) { return handleError("Allocating pixel data"); } memset(data, 0, infoHeader.width * infoHeader.height); return true; } bool NuvieBmpFile::load(const Common::Path &filename) { NuvieIOFileRead file; if (filename.empty()) return handleError("zero byte file"); if (!file.open(filename)) { return handleError("opening file"); } if (file.get_size() < 0x36) { //invalid header. return handleError("filesize < 0x36"); } header.type = file.read2(); header.size = file.read4(); header.reserved1 = file.read2(); header.reserved2 = file.read2(); header.offset = file.read4(); infoHeader.size = file.read4(); infoHeader.width = file.read4(); infoHeader.height = file.read4(); infoHeader.planes = file.read2(); infoHeader.bits = file.read2(); infoHeader.compression = file.read4(); infoHeader.imagesize = file.read4(); infoHeader.xresolution = file.read4(); infoHeader.yresolution = file.read4(); infoHeader.ncolours = file.read4(); infoHeader.importantcolours = file.read4(); if (header.type != NUVIEBMPFILE_MAGIC) { //invalid magic. return handleError("invalid BMP magic."); } if (infoHeader.bits != 8 && infoHeader.bits != 24) { return handleError("only 256 colour bitmaps supported."); } if (infoHeader.compression != 0) { // && infoHeader.compression != 2) return handleError("only uncompressed BMP files are supported"); //return handleError("only raw and bi_rle8 compression formats are supported."); //FIXME need to handle rle compression. } if (infoHeader.bits == 8) { for (uint32 i = 0; i < infoHeader.ncolours; i++) { uint8 b = file.read1(); uint8 g = file.read1(); uint8 r = file.read1(); file.read1(); // 0 palette[i] = (uint32)r | (uint32)(g << 8) | (uint32)(b << 16); } } file.seekStart(); file.seek(header.offset); uint16 bytes_per_pixel = infoHeader.bits / 8; bmp_line_width = infoHeader.width * bytes_per_pixel; if (bmp_line_width % 4 != 0) { bmp_line_width += (4 - (bmp_line_width % 4)); } if (data == nullptr || infoHeader.width != prev_width || infoHeader.height != prev_height || prev_bits != infoHeader.bits) { if (data) { free(data); } data = (unsigned char *)malloc(infoHeader.width * infoHeader.height * bytes_per_pixel); prev_width = infoHeader.width; prev_height = infoHeader.height; prev_bits = infoHeader.bits; if (data == nullptr) { return handleError("allocating memory for image"); } } uint32 end = header.offset + bmp_line_width * infoHeader.height; uint32 data_width = infoHeader.width * bytes_per_pixel; for (sint32 i = 0; i < infoHeader.height; i++) { file.seek(end - bmp_line_width - (bmp_line_width * i)); file.readToBuf(&data[i * data_width], data_width); } return true; } bool NuvieBmpFile::save(const Common::Path &filename) { NuvieIOFileWrite file; if (!file.open(filename)) { return handleError("Opening " + filename.toString() + "."); } file.write2(header.type); file.write4(header.size); file.write2(header.reserved1); file.write2(header.reserved2); file.write4(header.offset); file.write4(infoHeader.size); file.write4(infoHeader.width); file.write4(infoHeader.height); file.write2(infoHeader.planes); file.write2(infoHeader.bits); file.write4(infoHeader.compression); file.write4(infoHeader.imagesize); file.write4(infoHeader.xresolution); file.write4(infoHeader.yresolution); file.write4(infoHeader.ncolours); file.write4(infoHeader.importantcolours); if (infoHeader.bits == 8) { for (uint32 i = 0; i < infoHeader.ncolours; i++) { file.write1((palette[i] >> 16) & 0xff); //B file.write1((palette[i] >> 8) & 0xff); //G file.write1(palette[i] & 0xff); //R file.write1((palette[i] >> 24) & 0xff); //A } write8BitData(&file); } else { //FIXME write out 24bit data here. } file.close(); return true; } void NuvieBmpFile::write8BitData(NuvieIOFileWrite *file) { uint32 i; for (i = infoHeader.height; i > 0; i--) { file->writeBuf(&data[(i - 1)*infoHeader.width], infoHeader.width); if ((sint32)bmp_line_width > infoHeader.width) { //write out padding bytes. for (uint8 j = 0; j < bmp_line_width - infoHeader.width; j++) { file->write1(0); } } } } bool NuvieBmpFile::handleError(Std::string error) { if (data) { free(data); data = nullptr; } DEBUG(0, LEVEL_ERROR, error.c_str()); return false; } Tile *NuvieBmpFile::getTile() { if (infoHeader.width != 16 || infoHeader.height != 16 || infoHeader.bits != 8) { return nullptr; } Tile *t = (Tile *)malloc(sizeof(Tile)); if (t == nullptr) { return nullptr; } memset(t, 0, sizeof(Tile)); memcpy(t->data, data, 256); return t; } unsigned char *NuvieBmpFile::getRawIndexedData() { if (infoHeader.bits != 8) { return nullptr; } return data; } unsigned char *NuvieBmpFile::getRawIndexedDataCopy() { if (data == nullptr || infoHeader.bits != 8) { return nullptr; } unsigned char *copy = (unsigned char *)malloc(infoHeader.width * infoHeader.height); if (copy == nullptr) { return nullptr; } memcpy(copy, data, infoHeader.width * infoHeader.height); return copy; } Graphics::ManagedSurface *NuvieBmpFile::getSdlSurface32(const Common::Path &filename) { load(filename); return getSdlSurface32(); } Graphics::ManagedSurface *NuvieBmpFile::getSdlSurface32() { if (data == nullptr) { return nullptr; } Graphics::ManagedSurface *surface = new Graphics::ManagedSurface( infoHeader.width, infoHeader.height, Graphics::PixelFormat(4, 8, 8, 8, 0, 0, 8, 16, 24) ); unsigned char *src_buf = data; Graphics::Surface s = surface->getSubArea(Common::Rect(0, 0, surface->w, surface->h)); uint32 *pixels = (uint32 *)s.getPixels(); if (infoHeader.bits == 8) { for (sint32 i = 0; i < infoHeader.height; i++) { for (sint32 j = 0; j < infoHeader.width; j++) { pixels[j] = palette[src_buf[j]]; } src_buf += infoHeader.width; pixels += infoHeader.width; } } else { //bits == 24 for (sint32 i = 0; i < infoHeader.height; i++) { for (sint32 j = 0; j < infoHeader.width; j++) { pixels[j] = (uint32)src_buf[j * 3 + 2] | (uint32)(src_buf[j * 3 + 1] << 8) | (uint32)(src_buf[j * 3 + 0] << 16); } src_buf += infoHeader.width * 3; pixels += infoHeader.width; } } return surface; } } // End of namespace Nuvie } // End of namespace Ultima